新增: u-tpl SQL 模板引擎完整实现
- Lexer/Parser/Executor 三阶段架构
- #{param} 参数化 + ${raw} 原样替换 + 白名单安全策略
- @if/@for/@tpl/@include/@namespace 控制流
- 表达式引擎: 比较、逻辑、nil 检查、len() 内置函数
- 支持 ?/$1/:1 多数据库占位符风格
- 零依赖,纯 Go 标准库实现
This commit is contained in:
85
template.go
Normal file
85
template.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package utpl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"gitea.1216.top/lxy/u-tpl/internal"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
SQL string
|
||||
Args []any
|
||||
}
|
||||
|
||||
type Template struct {
|
||||
name string
|
||||
engine *Engine
|
||||
nodes []internal.Node
|
||||
blocks map[string][]internal.Node
|
||||
hasBlocks bool
|
||||
namespace string
|
||||
}
|
||||
|
||||
func (t *Template) Execute(vars map[string]any) (*Result, error) {
|
||||
if t.hasBlocks {
|
||||
return nil, &ExecError{
|
||||
Pos: Position{},
|
||||
Message: fmt.Sprintf("template %q has named blocks, use ExecuteBlock instead", t.name),
|
||||
}
|
||||
}
|
||||
return t.executeNodes(t.nodes, vars)
|
||||
}
|
||||
|
||||
func (t *Template) ExecuteBlock(blockName string, vars map[string]any) (*Result, error) {
|
||||
nodes, ok := t.blocks[blockName]
|
||||
if !ok {
|
||||
if t.namespace != "" {
|
||||
nodes, ok = t.blocks[t.namespace+"."+blockName]
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return nil, &ExecError{
|
||||
Pos: Position{},
|
||||
Message: fmt.Sprintf("block %q not found in template %q", blockName, t.name),
|
||||
}
|
||||
}
|
||||
return t.executeNodes(nodes, vars)
|
||||
}
|
||||
|
||||
func (t *Template) ExecuteString(vars map[string]any) (string, error) {
|
||||
result, err := t.Execute(vars)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return result.SQL, nil
|
||||
}
|
||||
|
||||
func (t *Template) ExecuteBlockString(blockName string, vars map[string]any) (string, error) {
|
||||
result, err := t.ExecuteBlock(blockName, vars)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return result.SQL, nil
|
||||
}
|
||||
|
||||
func (t *Template) executeNodes(nodes []internal.Node, vars map[string]any) (*Result, error) {
|
||||
executor := internal.NewExecutor(t.engine.style, t.engine.rawPolicy, t.engine.strict)
|
||||
result, err := executor.Execute(nodes, vars)
|
||||
if err != nil {
|
||||
return nil, wrapExecError(err)
|
||||
}
|
||||
return &Result{SQL: result.SQL, Args: result.Args}, nil
|
||||
}
|
||||
|
||||
func wrapExecError(err error) error {
|
||||
var execErr *ExecError
|
||||
if errors.As(err, &execErr) {
|
||||
return err
|
||||
}
|
||||
var unsafeErr *UnsafeRawError
|
||||
if errors.As(err, &unsafeErr) {
|
||||
return err
|
||||
}
|
||||
return &ExecError{Message: err.Error()}
|
||||
}
|
||||
Reference in New Issue
Block a user