package smartconfig import ( "fmt" "strconv" "strings" ) // findInterpolations finds all ${...} patterns in the string, handling nested cases func findInterpolations(s string) []struct{ start, end int } { var results []struct{ start, end int } for i := 0; i < len(s); i++ { if i+2 < len(s) && s[i:i+2] == "${" { // Found start of interpolation start := i braceCount := 1 j := i + 2 // Find matching closing brace for j < len(s) && braceCount > 0 { if j+2 <= len(s) && s[j:j+2] == "${" { braceCount++ j += 2 } else if s[j] == '}' { braceCount-- j++ } else { j++ } } if braceCount == 0 { results = append(results, struct{ start, end int }{start, j}) i = j - 1 // Skip past this interpolation } } } return results } // interpolate handles nested interpolations properly func (c *Config) interpolate(content string, depth int) (string, error) { return c.interpolateWithQuoting(content, depth, true) } // interpolateWithQuoting handles interpolation with control over quoting func (c *Config) interpolateWithQuoting(content string, depth int, shouldQuote bool) (string, error) { if depth >= maxRecursionDepth { return content, nil } result := content changed := true for changed && depth < maxRecursionDepth { changed = false positions := findInterpolations(result) // Process from end to beginning to maintain string positions for i := len(positions) - 1; i >= 0; i-- { pos := positions[i] fullMatch := result[pos.start:pos.end] // Extract the content inside ${} inner := fullMatch[2 : len(fullMatch)-1] // Find the resolver and value colonIdx := strings.Index(inner, ":") if colonIdx == -1 { continue } resolverName := inner[:colonIdx] value := inner[colonIdx+1:] // Check if value contains nested interpolations if strings.Contains(value, "${") { // Recursively interpolate the value first, without quoting interpolatedValue, err := c.interpolateWithQuoting(value, depth+1, false) if err != nil { return "", err } value = interpolatedValue } // Resolve the value resolver, ok := c.resolvers[resolverName] if !ok { return "", fmt.Errorf("unknown resolver: %s", resolverName) } resolved, err := resolver.Resolve(value) if err != nil { return "", fmt.Errorf("failed to resolve %s:%s: %w", resolverName, value, err) } // Only quote if requested (top-level interpolations) finalValue := resolved if shouldQuote && depth == 0 { finalValue = strconv.Quote(resolved) } // Replace the match result = result[:pos.start] + finalValue + result[pos.end:] changed = true } if changed { depth++ } } return result, nil }