302 lines
7.7 KiB
Go
302 lines
7.7 KiB
Go
package processor
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
lua "github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
// Processor defines the interface for all file processors
|
|
type Processor interface {
|
|
// Process handles processing a file with the given pattern and Lua expression
|
|
// Now implemented as a base function in processor.go
|
|
// Process(filename string, pattern string, luaExpr string) (int, int, error)
|
|
|
|
// ProcessContent handles processing a string content directly with the given pattern and Lua expression
|
|
// Returns the modified content, modification count, match count, and any error
|
|
ProcessContent(content string, pattern string, luaExpr string) (string, int, int, error)
|
|
|
|
// ToLua converts processor-specific data to Lua variables
|
|
ToLua(L *lua.LState, data interface{}) error
|
|
|
|
// FromLua retrieves modified data from Lua
|
|
FromLua(L *lua.LState) (interface{}, error)
|
|
}
|
|
|
|
// ModificationRecord tracks a single value modification
|
|
type ModificationRecord struct {
|
|
File string
|
|
OldValue string
|
|
NewValue string
|
|
Operation string
|
|
Context string
|
|
}
|
|
|
|
func NewLuaState() (*lua.LState, error) {
|
|
L := lua.NewState()
|
|
// defer L.Close()
|
|
|
|
// Load math library
|
|
L.Push(L.GetGlobal("require"))
|
|
L.Push(lua.LString("math"))
|
|
if err := L.PCall(1, 1, nil); err != nil {
|
|
return nil, fmt.Errorf("error loading Lua math library: %v", err)
|
|
}
|
|
|
|
// Initialize helper functions
|
|
if err := InitLuaHelpers(L); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return L, nil
|
|
}
|
|
|
|
func Process(p Processor, filename string, pattern string, luaExpr string) (int, int, error) {
|
|
// Read file content
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("error getting current working directory: %v", err)
|
|
}
|
|
|
|
fullPath := filepath.Join(cwd, filename)
|
|
content, err := os.ReadFile(fullPath)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("error reading file: %v", err)
|
|
}
|
|
|
|
fileContent := string(content)
|
|
|
|
// Process the content
|
|
modifiedContent, modCount, matchCount, err := p.ProcessContent(fileContent, pattern, luaExpr)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
// If we made modifications, save the file
|
|
if modCount > 0 {
|
|
err = os.WriteFile(fullPath, []byte(modifiedContent), 0644)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("error writing file: %v", err)
|
|
}
|
|
}
|
|
|
|
return modCount, matchCount, nil
|
|
}
|
|
|
|
// ToLua converts a struct or map to a Lua table recursively
|
|
func ToLua(L *lua.LState, data interface{}) (lua.LValue, error) {
|
|
switch v := data.(type) {
|
|
case map[string]interface{}:
|
|
luaTable := L.NewTable()
|
|
for key, value := range v {
|
|
luaValue, err := ToLua(L, value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
luaTable.RawSetString(key, luaValue)
|
|
}
|
|
return luaTable, nil
|
|
case []interface{}:
|
|
luaTable := L.NewTable()
|
|
for i, value := range v {
|
|
luaValue, err := ToLua(L, value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
luaTable.RawSetInt(i+1, luaValue) // Lua arrays are 1-indexed
|
|
}
|
|
return luaTable, nil
|
|
case string:
|
|
return lua.LString(v), nil
|
|
case bool:
|
|
return lua.LBool(v), nil
|
|
case float64:
|
|
return lua.LNumber(v), nil
|
|
case nil:
|
|
return lua.LNil, nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported data type: %T", data)
|
|
}
|
|
}
|
|
|
|
// FromLua converts a Lua table to a struct or map recursively
|
|
func FromLua(L *lua.LState, luaValue lua.LValue) (interface{}, error) {
|
|
switch v := luaValue.(type) {
|
|
// Well shit...
|
|
// Tables in lua are both maps and arrays
|
|
// As arrays they are ordered and as maps, obviously, not
|
|
// So when we parse them to a go map we fuck up the order for arrays
|
|
// We have to find a better way....
|
|
case *lua.LTable:
|
|
isArray, err := IsLuaTableArray(L, v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if isArray {
|
|
result := make([]interface{}, 0)
|
|
v.ForEach(func(key lua.LValue, value lua.LValue) {
|
|
converted, _ := FromLua(L, value)
|
|
result = append(result, converted)
|
|
})
|
|
return result, nil
|
|
} else {
|
|
result := make(map[string]interface{})
|
|
v.ForEach(func(key lua.LValue, value lua.LValue) {
|
|
converted, _ := FromLua(L, value)
|
|
result[key.String()] = converted
|
|
})
|
|
return result, nil
|
|
}
|
|
case lua.LString:
|
|
return string(v), nil
|
|
case lua.LBool:
|
|
return bool(v), nil
|
|
case lua.LNumber:
|
|
return float64(v), nil
|
|
default:
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
func IsLuaTableArray(L *lua.LState, v *lua.LTable) (bool, error) {
|
|
L.SetGlobal("table_to_check", v)
|
|
|
|
// Use our predefined helper function from InitLuaHelpers
|
|
err := L.DoString(`is_array = isArray(table_to_check)`)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error determining if table is array: %w", err)
|
|
}
|
|
|
|
// Check the result of our Lua function
|
|
isArray := L.GetGlobal("is_array")
|
|
// LVIsFalse returns true if a given LValue is a nil or false otherwise false.
|
|
if !lua.LVIsFalse(isArray) {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// InitLuaHelpers initializes common Lua helper functions
|
|
func InitLuaHelpers(L *lua.LState) error {
|
|
helperScript := `
|
|
-- Custom Lua helpers for math operations
|
|
function min(a, b) return math.min(a, b) end
|
|
function max(a, b) return math.max(a, b) end
|
|
function round(x, n)
|
|
if n == nil then n = 0 end
|
|
return math.floor(x * 10^n + 0.5) / 10^n
|
|
end
|
|
function floor(x) return math.floor(x) end
|
|
function ceil(x) return math.ceil(x) end
|
|
function upper(s) return string.upper(s) end
|
|
function lower(s) return string.lower(s) end
|
|
|
|
-- String to number conversion helper
|
|
function num(str)
|
|
return tonumber(str) or 0
|
|
end
|
|
|
|
-- Number to string conversion
|
|
function str(num)
|
|
return tostring(num)
|
|
end
|
|
|
|
-- Check if string is numeric
|
|
function is_number(str)
|
|
return tonumber(str) ~= nil
|
|
end
|
|
|
|
function isArray(t)
|
|
if type(t) ~= "table" then return false end
|
|
local max = 0
|
|
local count = 0
|
|
for k, _ in pairs(t) do
|
|
if type(k) ~= "number" or k < 1 or math.floor(k) ~= k then
|
|
return false
|
|
end
|
|
max = math.max(max, k)
|
|
count = count + 1
|
|
end
|
|
return max == count
|
|
end
|
|
|
|
modified = false
|
|
`
|
|
if err := L.DoString(helperScript); err != nil {
|
|
return fmt.Errorf("error loading helper functions: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Helper utility functions
|
|
|
|
// LimitString truncates a string to maxLen and adds "..." if truncated
|
|
func LimitString(s string, maxLen int) string {
|
|
s = strings.ReplaceAll(s, "\n", "\\n")
|
|
if len(s) <= maxLen {
|
|
return s
|
|
}
|
|
return s[:maxLen-3] + "..."
|
|
}
|
|
|
|
func PrependLuaAssignment(luaExpr string) string {
|
|
// Auto-prepend v1 for expressions starting with operators
|
|
if strings.HasPrefix(luaExpr, "*") ||
|
|
strings.HasPrefix(luaExpr, "/") ||
|
|
strings.HasPrefix(luaExpr, "+") ||
|
|
strings.HasPrefix(luaExpr, "-") ||
|
|
strings.HasPrefix(luaExpr, "^") ||
|
|
strings.HasPrefix(luaExpr, "%") {
|
|
luaExpr = "v1 = v1" + luaExpr
|
|
} else if strings.HasPrefix(luaExpr, "=") {
|
|
// Handle direct assignment with = operator
|
|
luaExpr = "v1 " + luaExpr
|
|
}
|
|
|
|
// Add assignment if needed
|
|
if !strings.Contains(luaExpr, "=") {
|
|
luaExpr = "v1 = " + luaExpr
|
|
}
|
|
return luaExpr
|
|
}
|
|
|
|
// BuildLuaScript prepares a Lua expression from shorthand notation
|
|
func BuildLuaScript(luaExpr string) string {
|
|
luaExpr = PrependLuaAssignment(luaExpr)
|
|
|
|
// This allows the user to specify whether or not they modified a value
|
|
// If they do nothing we assume they did modify (no return at all)
|
|
// If they return before our return then they themselves specify what they did
|
|
// If nothing is returned lua assumes nil
|
|
// So we can say our value was modified if the return value is either nil or true
|
|
// If the return value is false then the user wants to keep the original
|
|
fullScript := fmt.Sprintf(`
|
|
function run()
|
|
%s
|
|
end
|
|
local res = run()
|
|
modified = res == nil or res
|
|
`, luaExpr)
|
|
|
|
return fullScript
|
|
}
|
|
|
|
// Max returns the maximum of two integers
|
|
func Max(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Min returns the minimum of two integers
|
|
func Min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|