Files
u-tpl/engine.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

122 lines
2.6 KiB
Go

package utpl
import (
"fmt"
"gitea.1216.top/lxy/u-tpl/internal"
)
type PlaceholderStyle = internal.PlaceholderStyle
const (
QuestionMark PlaceholderStyle = internal.QuestionMark
DollarNumber = internal.DollarNumber
ColonNumber = internal.ColonNumber
)
type IncludeResolver func(path string) (string, error)
type Option func(*Engine)
type Engine struct {
style internal.PlaceholderStyle
rawPolicy RawPolicy
includeResolver IncludeResolver
strict bool
}
func New(opts ...Option) *Engine {
e := &Engine{
style: internal.QuestionMark,
strict: true,
}
for _, opt := range opts {
opt(e)
}
return e
}
func WithPlaceholderStyle(style PlaceholderStyle) Option {
return func(e *Engine) { e.style = style }
}
func WithRawPolicy(policy RawPolicy) Option {
return func(e *Engine) { e.rawPolicy = policy }
}
func WithIncludeResolver(resolver IncludeResolver) Option {
return func(e *Engine) { e.includeResolver = resolver }
}
func WithStrictMode(strict bool) Option {
return func(e *Engine) { e.strict = strict }
}
func (e *Engine) Parse(name string, source string) (*Template, error) {
lexer := internal.NewLexer(source)
tokens, err := lexer.Tokenize()
if err != nil {
return nil, wrapParseError(err, name)
}
var includeMgr *internal.IncludeManager
if e.includeResolver != nil {
includeMgr = internal.NewIncludeManager(internal.IncludeResolver(e.includeResolver))
}
parser := internal.NewParser(source, tokens, includeMgr)
nodes, err := parser.Parse()
if err != nil {
return nil, wrapParseError(err, name)
}
namespace := ""
blocks := make(map[string][]internal.Node)
var bodyNodes []internal.Node
hasBlocks := false
for _, n := range nodes {
if ns, ok := n.(*internal.NamespaceNode); ok {
namespace = ns.Name
continue
}
if blk, ok := n.(*internal.BlockNode); ok {
hasBlocks = true
fullName := blk.Name
if namespace != "" {
fullName = namespace + "." + blk.Name
}
blocks[fullName] = blk.Body
continue
}
bodyNodes = append(bodyNodes, n)
}
return &Template{
name: name,
engine: e,
nodes: bodyNodes,
blocks: blocks,
hasBlocks: hasBlocks,
namespace: namespace,
}, nil
}
func (e *Engine) MustParse(name string, source string) *Template {
tpl, err := e.Parse(name, source)
if err != nil {
panic(err)
}
return tpl
}
func wrapParseError(err error, name string) error {
if _, ok := err.(*ParseError); ok {
return err
}
return &ParseError{
Pos: Position{Line: 0, Column: 0},
Message: fmt.Sprintf("template %q: %s", name, err.Error()),
}
}