package internal import ( "fmt" "strings" ) type IncludeResolver func(path string) (string, error) type IncludeManager struct { resolver IncludeResolver stack []string } func NewIncludeManager(resolver IncludeResolver) *IncludeManager { return &IncludeManager{resolver: resolver} } func (m *IncludeManager) Resolve(path string) (string, error) { return m.resolveInternal(path, m.stack) } func (m *IncludeManager) resolveInternal(path string, stack []string) (string, error) { for i, p := range stack { if p == path { return "", fmt.Errorf("circular include detected: %s is already in the include chain at depth %d", path, i) } } src, err := m.resolver(path) if err != nil { return "", fmt.Errorf("failed to resolve include %q: %w", path, err) } newStack := make([]string, len(stack)+1) copy(newStack, stack) newStack[len(stack)] = path return m.expandIncludes(src, newStack) } func (m *IncludeManager) expandIncludes(src string, stack []string) (string, error) { result := src offset := 0 for { start := strings.Index(result[offset:], `@include("`) if start < 0 { break } absStart := offset + start pathStart := absStart + len(`@include("`) end := strings.Index(result[pathStart:], `")`) if end < 0 { break } includePath := result[pathStart : pathStart+end] absEnd := pathStart + end + len(`")`) resolved, err := m.resolveInternal(includePath, stack) if err != nil { return "", err } result = result[:absStart] + resolved + result[absEnd:] offset = absStart + len(resolved) } return result, nil }