package processor import ( "cook/utils" "encoding/json" "fmt" "time" logger "git.site.quack-lab.dev/dave/cylogger" lua "github.com/yuin/gopher-lua" ) // jsonLogger is a scoped logger for the processor/json package. var jsonLogger = logger.Default.WithPrefix("processor/json") // ProcessJSON applies Lua processing to JSON content func ProcessJSON(content string, command utils.ModifyCommand, filename string) ([]utils.ReplaceCommand, error) { processJsonLogger := jsonLogger.WithPrefix("ProcessJSON").WithField("commandName", command.Name).WithField("file", filename) processJsonLogger.Debug("Starting JSON processing for file") processJsonLogger.Trace("Initial file content length: %d", len(content)) var commands []utils.ReplaceCommand startTime := time.Now() // Parse JSON content var jsonData interface{} err := json.Unmarshal([]byte(content), &jsonData) if err != nil { processJsonLogger.Error("Failed to parse JSON content: %v", err) return commands, fmt.Errorf("failed to parse JSON: %v", err) } processJsonLogger.Debug("Successfully parsed JSON content") // Create Lua state L, err := NewLuaState() if err != nil { processJsonLogger.Error("Error creating Lua state: %v", err) return commands, fmt.Errorf("error creating Lua state: %v", err) } defer L.Close() // Set filename global L.SetGlobal("file", lua.LString(filename)) // Convert JSON data to Lua table luaTable, err := ToLuaTable(L, jsonData) if err != nil { processJsonLogger.Error("Failed to convert JSON to Lua table: %v", err) return commands, fmt.Errorf("failed to convert JSON to Lua table: %v", err) } // Set the JSON data as a global variable L.SetGlobal("data", luaTable) processJsonLogger.Debug("Set JSON data as Lua global 'data'") // Build and execute Lua script for JSON mode luaExpr := BuildJSONLuaScript(command.Lua) processJsonLogger.Debug("Built Lua script from expression: %q", command.Lua) processJsonLogger.Trace("Full Lua script: %q", utils.LimitString(luaExpr, 200)) if err := L.DoString(luaExpr); err != nil { processJsonLogger.Error("Lua script execution failed: %v\nScript: %s", err, utils.LimitString(luaExpr, 200)) return commands, fmt.Errorf("lua script execution failed: %v", err) } processJsonLogger.Debug("Lua script executed successfully") // Check if modification flag is set modifiedVal := L.GetGlobal("modified") if modifiedVal.Type() != lua.LTBool || !lua.LVAsBool(modifiedVal) { processJsonLogger.Debug("Skipping - no modifications indicated by Lua script") return commands, nil } // Get the modified data from Lua modifiedData := L.GetGlobal("data") if modifiedData.Type() != lua.LTTable { processJsonLogger.Error("Expected 'data' to be a table after Lua processing, got %s", modifiedData.Type().String()) return commands, fmt.Errorf("expected 'data' to be a table after Lua processing") } // Convert back to Go interface goData, err := FromLua(L, modifiedData) if err != nil { processJsonLogger.Error("Failed to convert Lua table back to Go: %v", err) return commands, fmt.Errorf("failed to convert Lua table back to Go: %v", err) } // Marshal back to JSON modifiedJSON, err := json.MarshalIndent(goData, "", " ") if err != nil { processJsonLogger.Error("Failed to marshal modified data to JSON: %v", err) return commands, fmt.Errorf("failed to marshal modified data to JSON: %v", err) } // Create replacement command for the entire file // For JSON mode, we always replace the entire content commands = append(commands, utils.ReplaceCommand{ From: 0, To: len(content), With: string(modifiedJSON), }) processJsonLogger.Debug("Total JSON processing time: %v", time.Since(startTime)) processJsonLogger.Debug("Generated %d total modifications", len(commands)) return commands, nil } // ToLuaTable converts a Go interface{} to a Lua table recursively func ToLuaTable(L *lua.LState, data interface{}) (*lua.LTable, error) { toLuaTableLogger := jsonLogger.WithPrefix("ToLuaTable") toLuaTableLogger.Debug("Converting Go interface to Lua table") toLuaTableLogger.Trace("Input data type: %T", data) switch v := data.(type) { case map[string]interface{}: toLuaTableLogger.Debug("Converting map to Lua table") table := L.CreateTable(0, len(v)) for key, value := range v { luaValue, err := ToLuaValue(L, value) if err != nil { toLuaTableLogger.Error("Failed to convert map value for key %q: %v", key, err) return nil, err } table.RawSetString(key, luaValue) } return table, nil case []interface{}: toLuaTableLogger.Debug("Converting slice to Lua table") table := L.CreateTable(len(v), 0) for i, value := range v { luaValue, err := ToLuaValue(L, value) if err != nil { toLuaTableLogger.Error("Failed to convert slice value at index %d: %v", i, err) return nil, err } table.RawSetInt(i+1, luaValue) // Lua arrays are 1-indexed } return table, nil case string: toLuaTableLogger.Debug("Converting string to Lua string") return nil, fmt.Errorf("expected table or array, got string") case float64: toLuaTableLogger.Debug("Converting float64 to Lua number") return nil, fmt.Errorf("expected table or array, got number") case bool: toLuaTableLogger.Debug("Converting bool to Lua boolean") return nil, fmt.Errorf("expected table or array, got boolean") case nil: toLuaTableLogger.Debug("Converting nil to Lua nil") return nil, fmt.Errorf("expected table or array, got nil") default: toLuaTableLogger.Error("Unsupported type for Lua table conversion: %T", v) return nil, fmt.Errorf("unsupported type for Lua table conversion: %T", v) } } // ToLuaValue converts a Go interface{} to a Lua value func ToLuaValue(L *lua.LState, data interface{}) (lua.LValue, error) { toLuaValueLogger := jsonLogger.WithPrefix("ToLuaValue") toLuaValueLogger.Debug("Converting Go interface to Lua value") toLuaValueLogger.Trace("Input data type: %T", data) switch v := data.(type) { case map[string]interface{}: toLuaValueLogger.Debug("Converting map to Lua table") table := L.CreateTable(0, len(v)) for key, value := range v { luaValue, err := ToLuaValue(L, value) if err != nil { toLuaValueLogger.Error("Failed to convert map value for key %q: %v", key, err) return lua.LNil, err } table.RawSetString(key, luaValue) } return table, nil case []interface{}: toLuaValueLogger.Debug("Converting slice to Lua table") table := L.CreateTable(len(v), 0) for i, value := range v { luaValue, err := ToLuaValue(L, value) if err != nil { toLuaValueLogger.Error("Failed to convert slice value at index %d: %v", i, err) return lua.LNil, err } table.RawSetInt(i+1, luaValue) // Lua arrays are 1-indexed } return table, nil case string: toLuaValueLogger.Debug("Converting string to Lua string") return lua.LString(v), nil case float64: toLuaValueLogger.Debug("Converting float64 to Lua number") return lua.LNumber(v), nil case bool: toLuaValueLogger.Debug("Converting bool to Lua boolean") return lua.LBool(v), nil case nil: toLuaValueLogger.Debug("Converting nil to Lua nil") return lua.LNil, nil default: toLuaValueLogger.Error("Unsupported type for Lua value conversion: %T", v) return lua.LNil, fmt.Errorf("unsupported type for Lua value conversion: %T", v) } }