remove template implementation
This commit is contained in:
parent
6322a893ef
commit
a055638e4b
@ -40,9 +40,6 @@ var timeLuaCode string
|
|||||||
//go:embed lua/math.lua
|
//go:embed lua/math.lua
|
||||||
var mathLuaCode string
|
var mathLuaCode string
|
||||||
|
|
||||||
//go:embed lua/templates.lua
|
|
||||||
var templateLuaCode string
|
|
||||||
|
|
||||||
// ModuleInfo holds information about an embeddable Lua module
|
// ModuleInfo holds information about an embeddable Lua module
|
||||||
type ModuleInfo struct {
|
type ModuleInfo struct {
|
||||||
Name string // Module name
|
Name string // Module name
|
||||||
@ -63,7 +60,6 @@ var (
|
|||||||
{Name: "crypto", Code: cryptoLuaCode},
|
{Name: "crypto", Code: cryptoLuaCode},
|
||||||
{Name: "time", Code: timeLuaCode},
|
{Name: "time", Code: timeLuaCode},
|
||||||
{Name: "math", Code: mathLuaCode},
|
{Name: "math", Code: mathLuaCode},
|
||||||
{Name: "tmpl", Code: templateLuaCode},
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
local template = {}
|
|
||||||
|
|
||||||
function template.render(path, data)
|
|
||||||
local result = __template_render(path, data)
|
|
||||||
if type(result) == "string" and result:find("^template%.") then
|
|
||||||
error(result, 2)
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
function template.include(path, data)
|
|
||||||
local result = __template_include(path, data)
|
|
||||||
if type(result) == "string" and result:find("^template%.") then
|
|
||||||
error(result, 2)
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
function template.exists(path)
|
|
||||||
return __template_exists(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
function template.clear_cache(path)
|
|
||||||
return __template_clear_cache(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
function template.cache_size()
|
|
||||||
return __template_cache_size()
|
|
||||||
end
|
|
||||||
|
|
||||||
return template
|
|
@ -288,7 +288,6 @@ cleanup:
|
|||||||
|
|
||||||
CleanupFS()
|
CleanupFS()
|
||||||
CleanupSQLite()
|
CleanupSQLite()
|
||||||
CleanupTemplate()
|
|
||||||
|
|
||||||
logger.Debug("Runner closed")
|
logger.Debug("Runner closed")
|
||||||
return nil
|
return nil
|
||||||
|
@ -121,10 +121,6 @@ func (s *Sandbox) registerCoreFunctions(state *luajit.State) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := RegisterTemplateFunctions(state); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,440 +0,0 @@
|
|||||||
package runner
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"html"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"maps"
|
|
||||||
|
|
||||||
luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CachedTemplate holds compiled Lua code with metadata
|
|
||||||
type CachedTemplate struct {
|
|
||||||
CompiledLua []byte // Compiled Lua bytecode
|
|
||||||
ModTime time.Time
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TemplateCache manages template caching with fast lookups
|
|
||||||
type TemplateCache struct {
|
|
||||||
templates sync.Map // map[string]*CachedTemplate
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
templateCache = &TemplateCache{}
|
|
||||||
simpleVarRe = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// htmlEscape escapes HTML special characters
|
|
||||||
func htmlEscape(s string) string {
|
|
||||||
return html.EscapeString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// compileTemplate converts template string to Lua code
|
|
||||||
func compileTemplate(templateStr string) (string, error) {
|
|
||||||
pos := 1
|
|
||||||
chunks := []any{}
|
|
||||||
templateLen := len(templateStr)
|
|
||||||
|
|
||||||
for pos <= templateLen {
|
|
||||||
// Find next template tag
|
|
||||||
unescapedStart := strings.Index(templateStr[pos-1:], "{{{")
|
|
||||||
escapedStart := strings.Index(templateStr[pos-1:], "{{")
|
|
||||||
|
|
||||||
// Adjust positions to be absolute
|
|
||||||
if unescapedStart != -1 {
|
|
||||||
unescapedStart += pos - 1
|
|
||||||
}
|
|
||||||
if escapedStart != -1 {
|
|
||||||
escapedStart += pos - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var start, openLen int
|
|
||||||
var tagType string
|
|
||||||
|
|
||||||
// Determine which tag comes first
|
|
||||||
if unescapedStart != -1 && (escapedStart == -1 || unescapedStart <= escapedStart) {
|
|
||||||
start, tagType, openLen = unescapedStart, "-", 3
|
|
||||||
} else if escapedStart != -1 {
|
|
||||||
start, tagType, openLen = escapedStart, "=", 2
|
|
||||||
} else {
|
|
||||||
// No more tags, add remaining text
|
|
||||||
if pos <= templateLen {
|
|
||||||
chunks = append(chunks, templateStr[pos-1:])
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add text before tag
|
|
||||||
if start > pos-1 {
|
|
||||||
chunks = append(chunks, templateStr[pos-1:start])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find closing tag
|
|
||||||
pos = start + openLen + 1
|
|
||||||
var closeTag string
|
|
||||||
if tagType == "-" {
|
|
||||||
closeTag = "}}}"
|
|
||||||
} else {
|
|
||||||
closeTag = "}}"
|
|
||||||
}
|
|
||||||
|
|
||||||
closeStart := strings.Index(templateStr[pos-1:], closeTag)
|
|
||||||
if closeStart == -1 {
|
|
||||||
return "", fmt.Errorf("failed to find closing tag at position %d", pos)
|
|
||||||
}
|
|
||||||
closeStart += pos - 1
|
|
||||||
|
|
||||||
// Extract and trim code
|
|
||||||
code := strings.TrimSpace(templateStr[pos-1 : closeStart])
|
|
||||||
|
|
||||||
// Check if it's a simple variable for escaped output
|
|
||||||
isSimpleVar := tagType == "=" && simpleVarRe.MatchString(code)
|
|
||||||
|
|
||||||
chunks = append(chunks, []any{tagType, code, pos, isSimpleVar})
|
|
||||||
|
|
||||||
// Move past closing tag
|
|
||||||
pos = closeStart + len(closeTag) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate Lua code
|
|
||||||
buffer := []string{"local _tostring, _escape, _b, _b_i = ...\n"}
|
|
||||||
|
|
||||||
for _, chunk := range chunks {
|
|
||||||
switch v := chunk.(type) {
|
|
||||||
case string:
|
|
||||||
// Literal text
|
|
||||||
buffer = append(buffer, "_b_i = _b_i + 1\n")
|
|
||||||
buffer = append(buffer, fmt.Sprintf("_b[_b_i] = %q\n", v))
|
|
||||||
case []any:
|
|
||||||
tagType := v[0].(string)
|
|
||||||
code := v[1].(string)
|
|
||||||
pos := v[2].(int)
|
|
||||||
isSimpleVar := v[3].(bool)
|
|
||||||
|
|
||||||
switch tagType {
|
|
||||||
case "=":
|
|
||||||
if isSimpleVar {
|
|
||||||
buffer = append(buffer, "_b_i = _b_i + 1\n")
|
|
||||||
buffer = append(buffer, fmt.Sprintf("--[[%d]] _b[_b_i] = _escape(_tostring(%s))\n", pos, code))
|
|
||||||
} else {
|
|
||||||
buffer = append(buffer, fmt.Sprintf("--[[%d]] %s\n", pos, code))
|
|
||||||
}
|
|
||||||
case "-":
|
|
||||||
buffer = append(buffer, "_b_i = _b_i + 1\n")
|
|
||||||
buffer = append(buffer, fmt.Sprintf("--[[%d]] _b[_b_i] = _tostring(%s)\n", pos, code))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer = append(buffer, "return _b")
|
|
||||||
return strings.Join(buffer, ""), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTemplate loads or retrieves a cached template
|
|
||||||
func getTemplate(templatePath string, state *luajit.State) ([]byte, error) {
|
|
||||||
// Resolve the path using the fs system
|
|
||||||
fullPath, err := ResolvePath(templatePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("template path resolution failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if file exists and get mod time
|
|
||||||
info, err := os.Stat(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("template file not found: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fast path: check cache first
|
|
||||||
if cached, ok := templateCache.templates.Load(templatePath); ok {
|
|
||||||
cachedTpl := cached.(*CachedTemplate)
|
|
||||||
// Compare mod times for cache validity
|
|
||||||
if cachedTpl.ModTime.Equal(info.ModTime()) {
|
|
||||||
return cachedTpl.CompiledLua, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache miss or file changed - load and compile template
|
|
||||||
content, err := os.ReadFile(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read template: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile template to Lua code
|
|
||||||
luaCode, err := compileTemplate(string(content))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("template compile error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile Lua code to bytecode
|
|
||||||
bytecode, err := state.CompileBytecode(luaCode, templatePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("lua compile error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store in cache
|
|
||||||
cachedTpl := &CachedTemplate{
|
|
||||||
CompiledLua: bytecode,
|
|
||||||
ModTime: info.ModTime(),
|
|
||||||
Path: fullPath,
|
|
||||||
}
|
|
||||||
templateCache.templates.Store(templatePath, cachedTpl)
|
|
||||||
|
|
||||||
return bytecode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// goHtmlEscape provides HTML escaping from Go
|
|
||||||
func goHtmlEscape(state *luajit.State) int {
|
|
||||||
if !state.IsString(1) {
|
|
||||||
state.PushString("")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
input := state.ToString(1)
|
|
||||||
escaped := htmlEscape(input)
|
|
||||||
state.PushString(escaped)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// templateInclude renders a template with auto-merged data
|
|
||||||
func templateInclude(state *luajit.State) int {
|
|
||||||
// Get template path
|
|
||||||
if !state.IsString(1) {
|
|
||||||
state.PushString("template.include: path must be a string")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
templatePath := state.ToString(1)
|
|
||||||
|
|
||||||
// Get current template data
|
|
||||||
state.GetGlobal("__template_data")
|
|
||||||
currentData, _ := state.ToTable(-1)
|
|
||||||
state.Pop(1)
|
|
||||||
|
|
||||||
// Get new data (optional)
|
|
||||||
var newData map[string]any
|
|
||||||
if state.GetTop() >= 2 && !state.IsNil(2) {
|
|
||||||
if envValue, err := state.ToValue(2); err == nil {
|
|
||||||
if envMap, ok := envValue.(map[string]any); ok {
|
|
||||||
newData = envMap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge data
|
|
||||||
mergedData := make(map[string]any)
|
|
||||||
if currentData != nil {
|
|
||||||
maps.Copy(mergedData, currentData)
|
|
||||||
}
|
|
||||||
if newData != nil {
|
|
||||||
maps.Copy(mergedData, newData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call templateRender with merged data
|
|
||||||
state.PushString(templatePath)
|
|
||||||
state.PushTable(mergedData)
|
|
||||||
return templateRender(state)
|
|
||||||
}
|
|
||||||
func templateRender(state *luajit.State) int {
|
|
||||||
// Get template path (required)
|
|
||||||
if !state.IsString(1) {
|
|
||||||
state.PushString("template.render: template path must be a string")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
templatePath := state.ToString(1)
|
|
||||||
|
|
||||||
// Get data (optional)
|
|
||||||
var env map[string]any
|
|
||||||
if state.GetTop() >= 2 && !state.IsNil(2) {
|
|
||||||
var err error
|
|
||||||
envValue, err := state.ToValue(2)
|
|
||||||
if err != nil {
|
|
||||||
state.PushString("template.render: invalid data: " + err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if envMap, ok := envValue.(map[string]any); ok {
|
|
||||||
env = envMap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load compiled template from cache
|
|
||||||
bytecode, err := getTemplate(templatePath, state)
|
|
||||||
if err != nil {
|
|
||||||
state.PushString("template.render: " + err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load bytecode
|
|
||||||
if err := state.LoadBytecode(bytecode, templatePath); err != nil {
|
|
||||||
state.PushString("template.render: load error: " + err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create runtime environment
|
|
||||||
runtimeEnv := make(map[string]any)
|
|
||||||
if env != nil {
|
|
||||||
maps.Copy(runtimeEnv, env)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add current template data for nested calls
|
|
||||||
runtimeEnv["__template_data"] = env
|
|
||||||
|
|
||||||
// Get current global environment
|
|
||||||
state.GetGlobal("_G")
|
|
||||||
globalEnv, err := state.ToTable(-1)
|
|
||||||
if err == nil {
|
|
||||||
for k, v := range globalEnv {
|
|
||||||
if _, exists := runtimeEnv[k]; !exists {
|
|
||||||
runtimeEnv[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.Pop(1)
|
|
||||||
|
|
||||||
// Set up runtime environment
|
|
||||||
if err := state.PushTable(runtimeEnv); err != nil {
|
|
||||||
state.Pop(1) // Pop bytecode
|
|
||||||
state.PushString("template.render: env error: " + err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create metatable for environment
|
|
||||||
state.NewTable()
|
|
||||||
state.GetGlobal("_G")
|
|
||||||
state.SetField(-2, "__index")
|
|
||||||
state.SetMetatable(-2)
|
|
||||||
|
|
||||||
// Set environment using setfenv
|
|
||||||
state.GetGlobal("setfenv")
|
|
||||||
state.PushCopy(-3) // Template function
|
|
||||||
state.PushCopy(-3) // Environment
|
|
||||||
state.Call(2, 1) // setfenv(fn, env) returns the function
|
|
||||||
|
|
||||||
// Prepare arguments for template execution
|
|
||||||
state.GetGlobal("tostring") // tostring function
|
|
||||||
state.PushGoFunction(goHtmlEscape) // HTML escape function
|
|
||||||
state.NewTable() // output buffer
|
|
||||||
state.PushNumber(0) // buffer index
|
|
||||||
|
|
||||||
// Execute template (4 args, 1 result)
|
|
||||||
if err := state.Call(4, 1); err != nil {
|
|
||||||
state.Pop(1) // Pop environment
|
|
||||||
state.PushString("template.render: execution error: " + err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get result buffer
|
|
||||||
buffer, err := state.ToTable(-1)
|
|
||||||
if err != nil {
|
|
||||||
state.Pop(2) // Pop buffer and environment
|
|
||||||
state.PushString("template.render: result error: " + err.Error())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert buffer to string
|
|
||||||
var result strings.Builder
|
|
||||||
i := 1
|
|
||||||
for {
|
|
||||||
if val, exists := buffer[fmt.Sprintf("%d", i)]; exists {
|
|
||||||
result.WriteString(fmt.Sprintf("%v", val))
|
|
||||||
i++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Pop(2) // Pop buffer and environment
|
|
||||||
state.PushString(result.String())
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// templateExists checks if a template file exists
|
|
||||||
func templateExists(state *luajit.State) int {
|
|
||||||
if !state.IsString(1) {
|
|
||||||
state.PushBoolean(false)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
templatePath := state.ToString(1)
|
|
||||||
|
|
||||||
// Resolve path
|
|
||||||
fullPath, err := ResolvePath(templatePath)
|
|
||||||
if err != nil {
|
|
||||||
state.PushBoolean(false)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if file exists
|
|
||||||
_, err = os.Stat(fullPath)
|
|
||||||
state.PushBoolean(err == nil)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// templateClearCache clears the template cache
|
|
||||||
func templateClearCache(state *luajit.State) int {
|
|
||||||
// Optional: clear specific template
|
|
||||||
if state.GetTop() >= 1 && state.IsString(1) {
|
|
||||||
templatePath := state.ToString(1)
|
|
||||||
templateCache.templates.Delete(templatePath)
|
|
||||||
state.PushBoolean(true)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear entire cache
|
|
||||||
templateCache.templates.Range(func(key, value any) bool {
|
|
||||||
templateCache.templates.Delete(key)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
state.PushBoolean(true)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// templateCacheSize returns the number of templates in cache
|
|
||||||
func templateCacheSize(state *luajit.State) int {
|
|
||||||
count := 0
|
|
||||||
templateCache.templates.Range(func(key, value any) bool {
|
|
||||||
count++
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
state.PushNumber(float64(count))
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterTemplateFunctions registers template functions with the Lua state
|
|
||||||
func RegisterTemplateFunctions(state *luajit.State) error {
|
|
||||||
if err := state.RegisterGoFunction("__template_render", templateRender); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := state.RegisterGoFunction("__template_include", templateInclude); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := state.RegisterGoFunction("__template_exists", templateExists); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := state.RegisterGoFunction("__template_clear_cache", templateClearCache); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := state.RegisterGoFunction("__template_cache_size", templateCacheSize); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanupTemplate clears the template cache
|
|
||||||
func CleanupTemplate() {
|
|
||||||
templateCache.templates.Range(func(key, value any) bool {
|
|
||||||
templateCache.templates.Delete(key)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvalidateTemplate removes a specific template from cache
|
|
||||||
func InvalidateTemplate(templatePath string) {
|
|
||||||
templateCache.templates.Delete(templatePath)
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user