Files
u-tpl/internal/include.go
绝尘 861d58d718 新增: u-tpl SQL 模板引擎完整实现
- Lexer/Parser/Executor 三阶段架构
- #{param} 参数化 + ${raw} 原样替换 + 白名单安全策略
- @if/@for/@tpl/@include/@namespace 控制流
- 表达式引擎: 比较、逻辑、nil 检查、len() 内置函数
- 支持 ?/$1/:1 多数据库占位符风格
- 零依赖,纯 Go 标准库实现
2026-04-01 00:27:50 +08:00

74 lines
1.5 KiB
Go

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
}