render function fixes, http optimizations
This commit is contained in:
parent
ca85e735b0
commit
9f71fd7c0f
@ -2,6 +2,7 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"Moonshark/routers"
|
"Moonshark/routers"
|
||||||
@ -26,6 +27,7 @@ type Server struct {
|
|||||||
config *config.Config
|
config *config.Config
|
||||||
sessionManager *sessions.SessionManager
|
sessionManager *sessions.SessionManager
|
||||||
errorConfig utils.ErrorPageConfig
|
errorConfig utils.ErrorPageConfig
|
||||||
|
ctxPool sync.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new HTTP server
|
// New creates a new HTTP server
|
||||||
@ -45,6 +47,11 @@ func New(luaRouter *routers.LuaRouter, staticRouter *routers.StaticRouter,
|
|||||||
OverrideDir: overrideDir,
|
OverrideDir: overrideDir,
|
||||||
DebugMode: debugMode,
|
DebugMode: debugMode,
|
||||||
},
|
},
|
||||||
|
ctxPool: sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
return make(map[string]any, 8)
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
server.fasthttpServer = &fasthttp.Server{
|
server.fasthttpServer = &fasthttp.Server{
|
||||||
@ -78,53 +85,49 @@ func (s *Server) Shutdown(ctx context.Context) error {
|
|||||||
// handleRequest processes the HTTP request
|
// handleRequest processes the HTTP request
|
||||||
func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) {
|
func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
method := string(ctx.Method())
|
methodBytes := ctx.Method()
|
||||||
path := string(ctx.Path())
|
pathBytes := ctx.Path()
|
||||||
|
|
||||||
|
// Only convert to string once
|
||||||
|
method := string(methodBytes)
|
||||||
|
path := string(pathBytes)
|
||||||
|
|
||||||
if s.debugMode && path == "/debug/stats" {
|
if s.debugMode && path == "/debug/stats" {
|
||||||
s.handleDebugStats(ctx)
|
s.handleDebugStats(ctx)
|
||||||
|
|
||||||
if s.loggingEnabled {
|
if s.loggingEnabled {
|
||||||
duration := time.Since(start)
|
LogRequest(ctx.Response.StatusCode(), method, path, time.Since(start))
|
||||||
LogRequest(ctx.Response.StatusCode(), method, path, duration)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.processRequest(ctx)
|
s.processRequest(ctx, method, path)
|
||||||
|
|
||||||
if s.loggingEnabled {
|
if s.loggingEnabled {
|
||||||
duration := time.Since(start)
|
LogRequest(ctx.Response.StatusCode(), method, path, time.Since(start))
|
||||||
LogRequest(ctx.Response.StatusCode(), method, path, duration)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In server.go, modify the processRequest method
|
// In server.go, modify the processRequest method
|
||||||
func (s *Server) processRequest(ctx *fasthttp.RequestCtx) {
|
func (s *Server) processRequest(ctx *fasthttp.RequestCtx, method, path string) {
|
||||||
method := string(ctx.Method())
|
|
||||||
path := string(ctx.Path())
|
|
||||||
|
|
||||||
logger.Debug("Processing request %s %s", method, path)
|
logger.Debug("Processing request %s %s", method, path)
|
||||||
|
|
||||||
params := &routers.Params{}
|
params := &routers.Params{}
|
||||||
bytecode, scriptPath, routeErr, found := s.luaRouter.GetRouteInfo(method, path, params)
|
bytecode, scriptPath, routeErr, found := s.luaRouter.GetRouteInfo(method, path, params)
|
||||||
|
|
||||||
// Check if we found a route but it has an error or no valid bytecode
|
|
||||||
if found && (len(bytecode) == 0 || routeErr != nil) {
|
if found && (len(bytecode) == 0 || routeErr != nil) {
|
||||||
errorMsg := "Route exists but failed to compile. Check server logs for details."
|
errorMsg := "Route exists but failed to compile. Check server logs for details."
|
||||||
|
|
||||||
if routeErr != nil {
|
if routeErr != nil {
|
||||||
errorMsg = routeErr.Error()
|
errorMsg = routeErr.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error("%s %s - %s", method, path, errorMsg)
|
logger.Error("%s %s - %s", method, path, errorMsg)
|
||||||
|
|
||||||
ctx.SetContentType("text/html; charset=utf-8")
|
ctx.SetContentType("text/html; charset=utf-8")
|
||||||
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
||||||
errorHTML := utils.InternalErrorPage(s.errorConfig, path, errorMsg)
|
ctx.SetBody([]byte(utils.InternalErrorPage(s.errorConfig, path, errorMsg)))
|
||||||
ctx.SetBody([]byte(errorHTML))
|
|
||||||
return
|
return
|
||||||
} else if found {
|
}
|
||||||
|
|
||||||
|
if found {
|
||||||
logger.Debug("Found Lua route match for %s %s with %d params", method, path, params.Count)
|
logger.Debug("Found Lua route match for %s %s with %d params", method, path, params.Count)
|
||||||
s.handleLuaRoute(ctx, bytecode, scriptPath, params, method, path)
|
s.handleLuaRoute(ctx, bytecode, scriptPath, params, method, path)
|
||||||
return
|
return
|
||||||
@ -143,58 +146,62 @@ func (s *Server) processRequest(ctx *fasthttp.RequestCtx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleLuaRoute executes a Lua route
|
// handleLuaRoute executes a Lua route
|
||||||
func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scriptPath string, params *routers.Params, method string, path string) {
|
func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scriptPath string, params *routers.Params, method, path string) {
|
||||||
// Create context for Lua execution
|
|
||||||
luaCtx := runner.NewHTTPContext(ctx)
|
luaCtx := runner.NewHTTPContext(ctx)
|
||||||
defer luaCtx.Release()
|
defer luaCtx.Release()
|
||||||
|
|
||||||
host := string(ctx.Host())
|
// Get pooled map for session data
|
||||||
|
sessionMap := s.ctxPool.Get().(map[string]any)
|
||||||
|
defer func() {
|
||||||
|
// Clear and return to pool
|
||||||
|
for k := range sessionMap {
|
||||||
|
delete(sessionMap, k)
|
||||||
|
}
|
||||||
|
s.ctxPool.Put(sessionMap)
|
||||||
|
}()
|
||||||
|
|
||||||
|
session := s.sessionManager.GetSessionFromRequest(ctx)
|
||||||
|
sessionMap["id"] = session.ID
|
||||||
|
sessionMap["data"] = session.Data
|
||||||
|
|
||||||
luaCtx.Set("method", method)
|
luaCtx.Set("method", method)
|
||||||
luaCtx.Set("path", path)
|
luaCtx.Set("path", path)
|
||||||
luaCtx.Set("host", host)
|
luaCtx.Set("host", string(ctx.Host())) // Only convert when needed
|
||||||
|
luaCtx.Set("session", sessionMap)
|
||||||
session := s.sessionManager.GetSessionFromRequest(ctx)
|
|
||||||
luaCtx.Set("session", map[string]any{
|
|
||||||
"id": session.ID,
|
|
||||||
"data": session.Data,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
// Optimize params handling
|
||||||
if params.Count > 0 {
|
if params.Count > 0 {
|
||||||
paramMap := make(map[string]any, params.Count)
|
paramMap := make(map[string]any, params.Count) // Pre-size
|
||||||
for i, key := range params.Keys {
|
for i, key := range params.Keys {
|
||||||
paramMap[key] = params.Values[i]
|
paramMap[key] = params.Values[i]
|
||||||
}
|
}
|
||||||
luaCtx.Set("params", paramMap)
|
luaCtx.Set("params", paramMap)
|
||||||
} else {
|
} else {
|
||||||
luaCtx.Set("params", make(map[string]any))
|
luaCtx.Set("params", emptyMap) // Reuse empty map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optimize form handling for POST methods
|
||||||
if method == "POST" || method == "PUT" || method == "PATCH" {
|
if method == "POST" || method == "PUT" || method == "PATCH" {
|
||||||
formData, err := ParseForm(ctx)
|
if formData, err := ParseForm(ctx); err == nil {
|
||||||
if err == nil && len(formData) > 0 {
|
|
||||||
luaCtx.Set("form", formData)
|
luaCtx.Set("form", formData)
|
||||||
} else if err != nil {
|
|
||||||
logger.Warning("Error parsing form: %v", err)
|
|
||||||
luaCtx.Set("form", make(map[string]any))
|
|
||||||
} else {
|
} else {
|
||||||
luaCtx.Set("form", make(map[string]any))
|
logger.Warning("Error parsing form: %v", err)
|
||||||
|
luaCtx.Set("form", emptyMap)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
luaCtx.Set("form", make(map[string]any))
|
luaCtx.Set("form", emptyMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := s.luaRunner.Run(bytecode, luaCtx, scriptPath)
|
response, err := s.luaRunner.Run(bytecode, luaCtx, scriptPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error executing Lua route: %v", err)
|
logger.Error("Error executing Lua route: %v", err)
|
||||||
|
|
||||||
ctx.SetContentType("text/html; charset=utf-8")
|
ctx.SetContentType("text/html; charset=utf-8")
|
||||||
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
||||||
errorHTML := utils.InternalErrorPage(s.errorConfig, path, err.Error())
|
ctx.SetBody([]byte(utils.InternalErrorPage(s.errorConfig, path, err.Error())))
|
||||||
ctx.SetBody([]byte(errorHTML))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Session handling optimization
|
||||||
if _, clearAll := response.SessionData["__clear_all"]; clearAll {
|
if _, clearAll := response.SessionData["__clear_all"]; clearAll {
|
||||||
session.Clear()
|
session.Clear()
|
||||||
delete(response.SessionData, "__clear_all")
|
delete(response.SessionData, "__clear_all")
|
||||||
@ -207,8 +214,8 @@ func (s *Server) handleLuaRoute(ctx *fasthttp.RequestCtx, bytecode []byte, scrip
|
|||||||
session.Set(k, v)
|
session.Set(k, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.sessionManager.ApplySessionCookie(ctx, session)
|
|
||||||
|
|
||||||
|
s.sessionManager.ApplySessionCookie(ctx, session)
|
||||||
runner.ApplyResponse(response, ctx)
|
runner.ApplyResponse(response, ctx)
|
||||||
runner.ReleaseResponse(response)
|
runner.ReleaseResponse(response)
|
||||||
}
|
}
|
||||||
|
145
http/utils.go
145
http/utils.go
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"Moonshark/utils/logger"
|
"Moonshark/utils/logger"
|
||||||
@ -13,63 +14,91 @@ import (
|
|||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var emptyMap = make(map[string]any)
|
||||||
|
|
||||||
|
var (
|
||||||
|
stringPool = sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
return make([]string, 0, 4)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
formDataPool = sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
return make(map[string]any, 16)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// LogRequest logs an HTTP request with its status code and duration
|
// LogRequest logs an HTTP request with its status code and duration
|
||||||
func LogRequest(statusCode int, method, path string, duration time.Duration) {
|
func LogRequest(statusCode int, method, path string, duration time.Duration) {
|
||||||
var statusColor, resetColor, methodColor string
|
var statusColor, methodColor string
|
||||||
|
|
||||||
if statusCode >= 200 && statusCode < 300 {
|
// Simplified color assignment
|
||||||
|
switch {
|
||||||
|
case statusCode < 300:
|
||||||
statusColor = "\u001b[32m" // Green for 2xx
|
statusColor = "\u001b[32m" // Green for 2xx
|
||||||
} else if statusCode >= 300 && statusCode < 400 {
|
case statusCode < 400:
|
||||||
statusColor = "\u001b[36m" // Cyan for 3xx
|
statusColor = "\u001b[36m" // Cyan for 3xx
|
||||||
} else if statusCode >= 400 && statusCode < 500 {
|
case statusCode < 500:
|
||||||
statusColor = "\u001b[33m" // Yellow for 4xx
|
statusColor = "\u001b[33m" // Yellow for 4xx
|
||||||
} else {
|
default:
|
||||||
statusColor = "\u001b[31m" // Red for 5xx and others
|
statusColor = "\u001b[31m" // Red for 5xx+
|
||||||
}
|
}
|
||||||
|
|
||||||
switch method {
|
switch method {
|
||||||
case "GET":
|
case "GET":
|
||||||
methodColor = "\u001b[32m" // Green
|
methodColor = "\u001b[32m"
|
||||||
case "POST":
|
case "POST":
|
||||||
methodColor = "\u001b[34m" // Blue
|
methodColor = "\u001b[34m"
|
||||||
case "PUT":
|
case "PUT":
|
||||||
methodColor = "\u001b[33m" // Yellow
|
methodColor = "\u001b[33m"
|
||||||
case "DELETE":
|
case "DELETE":
|
||||||
methodColor = "\u001b[31m" // Red
|
methodColor = "\u001b[31m"
|
||||||
default:
|
default:
|
||||||
methodColor = "\u001b[35m" // Magenta for others
|
methodColor = "\u001b[35m"
|
||||||
}
|
}
|
||||||
|
|
||||||
resetColor = "\u001b[0m"
|
// Optimized duration formatting
|
||||||
|
|
||||||
var durationStr string
|
var durationStr string
|
||||||
if duration.Milliseconds() < 1 {
|
micros := duration.Microseconds()
|
||||||
durationStr = fmt.Sprintf("%.2fµs", float64(duration.Microseconds()))
|
if micros < 1000 {
|
||||||
} else if duration.Milliseconds() < 1000 {
|
durationStr = fmt.Sprintf("%.0fµs", float64(micros))
|
||||||
durationStr = fmt.Sprintf("%.2fms", float64(duration.Microseconds())/1000)
|
} else if micros < 1000000 {
|
||||||
|
durationStr = fmt.Sprintf("%.1fms", float64(micros)/1000)
|
||||||
} else {
|
} else {
|
||||||
durationStr = fmt.Sprintf("%.2fs", duration.Seconds())
|
durationStr = fmt.Sprintf("%.2fs", duration.Seconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Server("%s%d%s %s%s%s %s %s",
|
logger.Server("%s%d\u001b[0m %s%s\u001b[0m %s %s",
|
||||||
statusColor, statusCode, resetColor,
|
statusColor, statusCode,
|
||||||
methodColor, method, resetColor,
|
methodColor, method,
|
||||||
path, durationStr)
|
path, durationStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryToLua converts HTTP query args to a Lua-friendly map
|
// QueryToLua converts HTTP query args to a Lua-friendly map
|
||||||
func QueryToLua(ctx *fasthttp.RequestCtx) map[string]any {
|
func QueryToLua(ctx *fasthttp.RequestCtx) map[string]any {
|
||||||
queryMap := make(map[string]any)
|
args := ctx.QueryArgs()
|
||||||
|
if args.Len() == 0 {
|
||||||
|
return emptyMap
|
||||||
|
}
|
||||||
|
|
||||||
ctx.QueryArgs().VisitAll(func(key, value []byte) {
|
queryMap := make(map[string]any, args.Len()) // Pre-size
|
||||||
|
|
||||||
|
args.VisitAll(func(key, value []byte) {
|
||||||
k := string(key)
|
k := string(key)
|
||||||
v := string(value)
|
v := string(value)
|
||||||
|
|
||||||
if existing, ok := queryMap[k]; ok {
|
if existing, exists := queryMap[k]; exists {
|
||||||
if arr, ok := existing.([]string); ok {
|
// Handle multiple values more efficiently
|
||||||
queryMap[k] = append(arr, v)
|
switch typed := existing.(type) {
|
||||||
} else if str, ok := existing.(string); ok {
|
case []string:
|
||||||
queryMap[k] = []string{str, v}
|
queryMap[k] = append(typed, v)
|
||||||
|
case string:
|
||||||
|
// Get slice from pool
|
||||||
|
slice := stringPool.Get().([]string)
|
||||||
|
slice = slice[:0] // Reset length
|
||||||
|
slice = append(slice, typed, v)
|
||||||
|
queryMap[k] = slice
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
queryMap[k] = v
|
queryMap[k] = v
|
||||||
@ -81,21 +110,36 @@ func QueryToLua(ctx *fasthttp.RequestCtx) map[string]any {
|
|||||||
|
|
||||||
// ParseForm extracts form data from a request
|
// ParseForm extracts form data from a request
|
||||||
func ParseForm(ctx *fasthttp.RequestCtx) (map[string]any, error) {
|
func ParseForm(ctx *fasthttp.RequestCtx) (map[string]any, error) {
|
||||||
formData := make(map[string]any)
|
contentType := string(ctx.Request.Header.ContentType())
|
||||||
|
|
||||||
if strings.Contains(string(ctx.Request.Header.ContentType()), "multipart/form-data") {
|
if strings.Contains(contentType, "multipart/form-data") {
|
||||||
return parseMultipartForm(ctx)
|
return parseMultipartForm(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.PostArgs().VisitAll(func(key, value []byte) {
|
args := ctx.PostArgs()
|
||||||
|
if args.Len() == 0 {
|
||||||
|
return emptyMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
formData := formDataPool.Get().(map[string]any)
|
||||||
|
// Clear the map (should already be clean from pool)
|
||||||
|
for k := range formData {
|
||||||
|
delete(formData, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
args.VisitAll(func(key, value []byte) {
|
||||||
k := string(key)
|
k := string(key)
|
||||||
v := string(value)
|
v := string(value)
|
||||||
|
|
||||||
if existing, ok := formData[k]; ok {
|
if existing, exists := formData[k]; exists {
|
||||||
if arr, ok := existing.([]string); ok {
|
switch typed := existing.(type) {
|
||||||
formData[k] = append(arr, v)
|
case []string:
|
||||||
} else if str, ok := existing.(string); ok {
|
formData[k] = append(typed, v)
|
||||||
formData[k] = []string{str, v}
|
case string:
|
||||||
|
slice := stringPool.Get().([]string)
|
||||||
|
slice = slice[:0]
|
||||||
|
slice = append(slice, typed, v)
|
||||||
|
formData[k] = slice
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
formData[k] = v
|
formData[k] = v
|
||||||
@ -107,36 +151,43 @@ func ParseForm(ctx *fasthttp.RequestCtx) (map[string]any, error) {
|
|||||||
|
|
||||||
// parseMultipartForm handles multipart/form-data requests
|
// parseMultipartForm handles multipart/form-data requests
|
||||||
func parseMultipartForm(ctx *fasthttp.RequestCtx) (map[string]any, error) {
|
func parseMultipartForm(ctx *fasthttp.RequestCtx) (map[string]any, error) {
|
||||||
formData := make(map[string]any)
|
|
||||||
|
|
||||||
form, err := ctx.MultipartForm()
|
form, err := ctx.MultipartForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formData := formDataPool.Get().(map[string]any)
|
||||||
|
for k := range formData {
|
||||||
|
delete(formData, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process form values
|
||||||
for key, values := range form.Value {
|
for key, values := range form.Value {
|
||||||
if len(values) == 1 {
|
switch len(values) {
|
||||||
|
case 0:
|
||||||
|
// Skip empty
|
||||||
|
case 1:
|
||||||
formData[key] = values[0]
|
formData[key] = values[0]
|
||||||
} else if len(values) > 1 {
|
default:
|
||||||
formData[key] = values
|
formData[key] = values
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process files if present
|
||||||
if len(form.File) > 0 {
|
if len(form.File) > 0 {
|
||||||
files := make(map[string]any)
|
files := make(map[string]any, len(form.File))
|
||||||
|
|
||||||
for fieldName, fileHeaders := range form.File {
|
for fieldName, fileHeaders := range form.File {
|
||||||
if len(fileHeaders) == 1 {
|
switch len(fileHeaders) {
|
||||||
|
case 1:
|
||||||
files[fieldName] = fileInfoToMap(fileHeaders[0])
|
files[fieldName] = fileInfoToMap(fileHeaders[0])
|
||||||
} else if len(fileHeaders) > 1 {
|
default:
|
||||||
fileInfos := make([]map[string]any, 0, len(fileHeaders))
|
fileInfos := make([]map[string]any, len(fileHeaders))
|
||||||
for _, fh := range fileHeaders {
|
for i, fh := range fileHeaders {
|
||||||
fileInfos = append(fileInfos, fileInfoToMap(fh))
|
fileInfos[i] = fileInfoToMap(fh)
|
||||||
}
|
}
|
||||||
files[fieldName] = fileInfos
|
files[fieldName] = fileInfos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
formData["_files"] = files
|
formData["_files"] = files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,17 +438,14 @@ _G.render = function(template_str, env)
|
|||||||
|
|
||||||
local pos, chunks = 1, {}
|
local pos, chunks = 1, {}
|
||||||
while pos <= #template_str do
|
while pos <= #template_str do
|
||||||
local escaped_start = template_str:find("<%?", pos, true)
|
local unescaped_start = template_str:find("{{{", pos, true)
|
||||||
local unescaped_start = template_str:find("<!", pos, true)
|
local escaped_start = template_str:find("{{", pos, true)
|
||||||
local code_start = template_str:find("<%", pos, true)
|
|
||||||
|
|
||||||
local start, tag_type, open_len
|
local start, tag_type, open_len
|
||||||
if escaped_start and (not unescaped_start or escaped_start < unescaped_start) and (not code_start or escaped_start < code_start) then
|
if unescaped_start and (not escaped_start or unescaped_start <= escaped_start) then
|
||||||
|
start, tag_type, open_len = unescaped_start, "-", 3
|
||||||
|
elseif escaped_start then
|
||||||
start, tag_type, open_len = escaped_start, "=", 2
|
start, tag_type, open_len = escaped_start, "=", 2
|
||||||
elseif unescaped_start and (not code_start or unescaped_start < code_start) then
|
|
||||||
start, tag_type, open_len = unescaped_start, "-", 2
|
|
||||||
elseif code_start then
|
|
||||||
start, tag_type, open_len = code_start, "code", 2
|
|
||||||
else
|
else
|
||||||
table.insert(chunks, template_str:sub(pos))
|
table.insert(chunks, template_str:sub(pos))
|
||||||
break
|
break
|
||||||
@ -459,25 +456,19 @@ _G.render = function(template_str, env)
|
|||||||
end
|
end
|
||||||
|
|
||||||
pos = start + open_len
|
pos = start + open_len
|
||||||
local close_tag = tag_type == "=" and "?>" or tag_type == "-" and "!>" or "%>"
|
local close_tag = tag_type == "-" and "}}}" or "}}"
|
||||||
local close_start, close_stop = template_str:find(close_tag, pos, true)
|
local close_start, close_stop = template_str:find(close_tag, pos, true)
|
||||||
if not close_start then
|
if not close_start then
|
||||||
error("Failed to find closing tag at position " .. pos)
|
error("Failed to find closing tag at position " .. pos)
|
||||||
end
|
end
|
||||||
|
|
||||||
local trim_newline = false
|
local code = template_str:sub(pos, close_start-1):match("^%s*(.-)%s*$")
|
||||||
if template_str:sub(close_start-1, close_start-1) == "-" then
|
|
||||||
close_start = close_start - 1
|
|
||||||
trim_newline = true
|
|
||||||
end
|
|
||||||
|
|
||||||
local code = template_str:sub(pos, close_start-1)
|
-- Check if it's a simple variable name for escaped output
|
||||||
table.insert(chunks, {tag_type, code, pos})
|
local is_simple_var = tag_type == "=" and code:match("^[%w_]+$")
|
||||||
|
|
||||||
|
table.insert(chunks, {tag_type, code, pos, is_simple_var})
|
||||||
pos = close_stop + 1
|
pos = close_stop + 1
|
||||||
if trim_newline and template_str:sub(pos, pos) == "\n" then
|
|
||||||
pos = pos + 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local buffer = {"local _tostring, _escape, _b, _b_i = ...\n"}
|
local buffer = {"local _tostring, _escape, _b, _b_i = ...\n"}
|
||||||
@ -488,16 +479,16 @@ _G.render = function(template_str, env)
|
|||||||
table.insert(buffer, "_b[_b_i] = " .. string.format("%q", chunk) .. "\n")
|
table.insert(buffer, "_b[_b_i] = " .. string.format("%q", chunk) .. "\n")
|
||||||
else
|
else
|
||||||
t = chunk[1]
|
t = chunk[1]
|
||||||
if t == "code" then
|
|
||||||
table.insert(buffer, "--[[" .. chunk[3] .. "]] " .. chunk[2] .. "\n")
|
|
||||||
elseif t == "=" or t == "-" then
|
|
||||||
table.insert(buffer, "_b_i = _b_i + 1\n")
|
|
||||||
table.insert(buffer, "--[[" .. chunk[3] .. "]] _b[_b_i] = ")
|
|
||||||
if t == "=" then
|
if t == "=" then
|
||||||
table.insert(buffer, "_escape(_tostring(" .. chunk[2] .. "))\n")
|
if chunk[4] then -- is_simple_var
|
||||||
|
table.insert(buffer, "_b_i = _b_i + 1\n")
|
||||||
|
table.insert(buffer, "--[[" .. chunk[3] .. "]] _b[_b_i] = _escape(_tostring(" .. chunk[2] .. "))\n")
|
||||||
else
|
else
|
||||||
table.insert(buffer, "_tostring(" .. chunk[2] .. ")\n")
|
table.insert(buffer, "--[[" .. chunk[3] .. "]] " .. chunk[2] .. "\n")
|
||||||
end
|
end
|
||||||
|
elseif t == "-" then
|
||||||
|
table.insert(buffer, "_b_i = _b_i + 1\n")
|
||||||
|
table.insert(buffer, "--[[" .. chunk[3] .. "]] _b[_b_i] = _tostring(" .. chunk[2] .. ")\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -521,14 +512,14 @@ _G.parse = function(template_str, env)
|
|||||||
env = env or {}
|
env = env or {}
|
||||||
|
|
||||||
while pos <= #template_str do
|
while pos <= #template_str do
|
||||||
local escaped_start, escaped_end, escaped_name = template_str:find("<%?%s*([%w_]+)%s*%?>", pos)
|
local unescaped_start, unescaped_end, unescaped_name = template_str:find("{{{%s*([%w_]+)%s*}}}", pos)
|
||||||
local unescaped_start, unescaped_end, unescaped_name = template_str:find("<!%s*([%w_]+)%s*!>", pos)
|
local escaped_start, escaped_end, escaped_name = template_str:find("{{%s*([%w_]+)%s*}}", pos)
|
||||||
|
|
||||||
local next_pos, placeholder_end, name, escaped
|
local next_pos, placeholder_end, name, escaped
|
||||||
if escaped_start and (not unescaped_start or escaped_start < unescaped_start) then
|
if unescaped_start and (not escaped_start or unescaped_start <= escaped_start) then
|
||||||
next_pos, placeholder_end, name, escaped = escaped_start, escaped_end, escaped_name, true
|
|
||||||
elseif unescaped_start then
|
|
||||||
next_pos, placeholder_end, name, escaped = unescaped_start, unescaped_end, unescaped_name, false
|
next_pos, placeholder_end, name, escaped = unescaped_start, unescaped_end, unescaped_name, false
|
||||||
|
elseif escaped_start then
|
||||||
|
next_pos, placeholder_end, name, escaped = escaped_start, escaped_end, escaped_name, true
|
||||||
else
|
else
|
||||||
local text = template_str:sub(pos)
|
local text = template_str:sub(pos)
|
||||||
if text and #text > 0 then
|
if text and #text > 0 then
|
||||||
@ -561,14 +552,14 @@ _G.iparse = function(template_str, values)
|
|||||||
values = values or {}
|
values = values or {}
|
||||||
|
|
||||||
while pos <= #template_str do
|
while pos <= #template_str do
|
||||||
local escaped_start, escaped_end = template_str:find("<%?>", pos, true)
|
local unescaped_start, unescaped_end = template_str:find("{{{}}}", pos, true)
|
||||||
local unescaped_start, unescaped_end = template_str:find("<!>", pos, true)
|
local escaped_start, escaped_end = template_str:find("{{}}", pos, true)
|
||||||
|
|
||||||
local next_pos, placeholder_end, escaped
|
local next_pos, placeholder_end, escaped
|
||||||
if escaped_start and (not unescaped_start or escaped_start < unescaped_start) then
|
if unescaped_start and (not escaped_start or unescaped_start <= escaped_start) then
|
||||||
next_pos, placeholder_end, escaped = escaped_start, escaped_end, true
|
|
||||||
elseif unescaped_start then
|
|
||||||
next_pos, placeholder_end, escaped = unescaped_start, unescaped_end, false
|
next_pos, placeholder_end, escaped = unescaped_start, unescaped_end, false
|
||||||
|
elseif escaped_start then
|
||||||
|
next_pos, placeholder_end, escaped = escaped_start, escaped_end, true
|
||||||
else
|
else
|
||||||
local text = template_str:sub(pos)
|
local text = template_str:sub(pos)
|
||||||
if text and #text > 0 then
|
if text and #text > 0 then
|
||||||
|
Loading…
x
Reference in New Issue
Block a user