Clean up after claude

This commit is contained in:
2025-03-24 16:23:24 +01:00
parent 17bb3d4f71
commit 430234dd3b
10 changed files with 2947 additions and 2160 deletions

View File

@@ -5,30 +5,18 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/PaesslerAG/jsonpath"
lua "github.com/yuin/gopher-lua"
)
// JSONProcessor implements the Processor interface using JSONPath
type JSONProcessor struct {
Logger Logger
}
// NewJSONProcessor creates a new JSONProcessor
func NewJSONProcessor(logger Logger) *JSONProcessor {
return &JSONProcessor{
Logger: logger,
}
}
// JSONProcessor implements the Processor interface for JSON documents
type JSONProcessor struct{}
// Process implements the Processor interface for JSONProcessor
func (p *JSONProcessor) Process(filename string, pattern string, luaExpr string, originalExpr string) (int, int, error) {
// Use pattern as JSONPath expression
jsonPathExpr := pattern
func (p *JSONProcessor) Process(filename string, pattern string, luaExpr string) (int, int, error) {
// Read file content
fullPath := filepath.Join(".", filename)
content, err := os.ReadFile(fullPath)
@@ -37,12 +25,9 @@ func (p *JSONProcessor) Process(filename string, pattern string, luaExpr string,
}
fileContent := string(content)
if p.Logger != nil {
p.Logger.Printf("File %s loaded: %d bytes", fullPath, len(content))
}
// Process the content
modifiedContent, modCount, matchCount, err := p.ProcessContent(fileContent, jsonPathExpr, luaExpr, originalExpr)
modifiedContent, modCount, matchCount, err := p.ProcessContent(fileContent, pattern, luaExpr)
if err != nil {
return 0, 0, err
}
@@ -53,557 +38,271 @@ func (p *JSONProcessor) Process(filename string, pattern string, luaExpr string,
if err != nil {
return 0, 0, fmt.Errorf("error writing file: %v", err)
}
if p.Logger != nil {
p.Logger.Printf("Made %d JSON value modifications to %s and saved (%d bytes)",
modCount, fullPath, len(modifiedContent))
}
} else if p.Logger != nil {
p.Logger.Printf("No modifications made to %s", fullPath)
}
return modCount, matchCount, nil
}
// ToLua implements the Processor interface for JSONProcessor
func (p *JSONProcessor) ToLua(L *lua.LState, data interface{}) error {
// For JSON, convert different types to appropriate Lua types
return nil
}
// FromLua implements the Processor interface for JSONProcessor
func (p *JSONProcessor) FromLua(L *lua.LState) (interface{}, error) {
// Extract changes from Lua environment
return nil, nil
}
// ProcessContent implements the Processor interface for JSONProcessor
// It processes JSON content directly without file I/O
func (p *JSONProcessor) ProcessContent(content string, pattern string, luaExpr string, originalExpr string) (string, int, int, error) {
// Parse JSON
var jsonDoc interface{}
err := json.Unmarshal([]byte(content), &jsonDoc)
func (p *JSONProcessor) ProcessContent(content string, pattern string, luaExpr string) (string, int, int, error) {
// Parse JSON document
var jsonData interface{}
err := json.Unmarshal([]byte(content), &jsonData)
if err != nil {
return "", 0, 0, fmt.Errorf("error parsing JSON: %v", err)
return content, 0, 0, fmt.Errorf("error parsing JSON: %v", err)
}
// Log the JSONPath expression we're using
if p.Logger != nil {
p.Logger.Printf("JSON mode selected with JSONPath expression: %s", pattern)
}
// Initialize Lua state
L := lua.NewState()
defer L.Close()
// Setup Lua helper functions
if err := InitLuaHelpers(L); err != nil {
return "", 0, 0, err
}
// Setup JSON helpers
p.SetupJSONHelpers(L)
// Find matching nodes with simple JSONPath implementation
matchingPaths, err := p.findNodePaths(jsonDoc, pattern)
// Find nodes matching the JSONPath pattern
paths, values, err := p.findJSONPaths(jsonData, pattern)
if err != nil {
return "", 0, 0, fmt.Errorf("error finding JSON nodes: %v", err)
return content, 0, 0, fmt.Errorf("error executing JSONPath: %v", err)
}
if len(matchingPaths) == 0 {
if p.Logger != nil {
p.Logger.Printf("No JSON nodes matched JSONPath expression: %s", pattern)
}
matchCount := len(paths)
if matchCount == 0 {
return content, 0, 0, nil
}
if p.Logger != nil {
p.Logger.Printf("Found %d JSON nodes matching the path", len(matchingPaths))
// Initialize Lua
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 content, 0, 0, fmt.Errorf("error loading Lua math library: %v", err)
}
// Process each node
matchCount := len(matchingPaths)
modificationCount := 0
modifications := []ModificationRecord{}
// Clone the document for modification
var modifiedDoc interface{}
modifiedBytes, err := json.Marshal(jsonDoc)
if err != nil {
return "", 0, 0, fmt.Errorf("error cloning JSON document: %v", err)
// Load helper functions
if err := InitLuaHelpers(L); err != nil {
return content, 0, 0, err
}
err = json.Unmarshal(modifiedBytes, &modifiedDoc)
if err != nil {
return "", 0, 0, fmt.Errorf("error cloning JSON document: %v", err)
}
// Apply modifications to each node
modCount := 0
for i, value := range values {
// Reset Lua state for each node
L.SetGlobal("v1", lua.LNil)
L.SetGlobal("s1", lua.LNil)
// For each matching path, extract value, apply Lua script, and update
for i, path := range matchingPaths {
// Extract the original value
originalValue, err := p.getValueAtPath(jsonDoc, path)
if err != nil || originalValue == nil {
if p.Logger != nil {
p.Logger.Printf("Error getting value at path %v: %v", path, err)
}
continue
}
if p.Logger != nil {
p.Logger.Printf("Processing node #%d at path %v with value: %v", i+1, path, originalValue)
}
// Process based on the value type
switch val := originalValue.(type) {
case float64:
// Set up Lua environment for numeric value
L.SetGlobal("v1", lua.LNumber(val))
L.SetGlobal("s1", lua.LString(fmt.Sprintf("%v", val)))
// Execute Lua script
if err := L.DoString(luaExpr); err != nil {
if p.Logger != nil {
p.Logger.Printf("Lua execution failed for node #%d: %v", i+1, err)
}
continue
}
// Extract modified value
modVal := L.GetGlobal("v1")
if v, ok := modVal.(lua.LNumber); ok {
newValue := float64(v)
// Update the value in the document only if it changed
if newValue != val {
err := p.setValueAtPath(modifiedDoc, path, newValue)
if err != nil {
if p.Logger != nil {
p.Logger.Printf("Error updating value at path %v: %v", path, err)
}
continue
}
modificationCount++
modifications = append(modifications, ModificationRecord{
File: "",
OldValue: fmt.Sprintf("%v", val),
NewValue: fmt.Sprintf("%v", newValue),
Operation: originalExpr,
Context: fmt.Sprintf("(JSONPath: %s)", pattern),
})
if p.Logger != nil {
p.Logger.Printf("Modified numeric node #%d: %v -> %v", i+1, val, newValue)
}
}
}
case string:
// Set up Lua environment for string value
L.SetGlobal("s1", lua.LString(val))
// Try to convert to number if possible
if floatVal, err := strconv.ParseFloat(val, 64); err == nil {
L.SetGlobal("v1", lua.LNumber(floatVal))
} else {
L.SetGlobal("v1", lua.LNumber(0)) // Default to 0 if not numeric
}
// Execute Lua script
if err := L.DoString(luaExpr); err != nil {
if p.Logger != nil {
p.Logger.Printf("Lua execution failed for node #%d: %v", i+1, err)
}
continue
}
// Check for modifications in string (s1) or numeric (v1) values
var newValue interface{}
modified := false
// Check if s1 was modified
sVal := L.GetGlobal("s1")
if s, ok := sVal.(lua.LString); ok && string(s) != val {
newValue = string(s)
modified = true
} else {
// Check if v1 was modified to a number
vVal := L.GetGlobal("v1")
if v, ok := vVal.(lua.LNumber); ok {
numStr := strconv.FormatFloat(float64(v), 'f', -1, 64)
if numStr != val {
newValue = numStr
modified = true
}
}
}
// Apply the modification if anything changed
if modified {
err := p.setValueAtPath(modifiedDoc, path, newValue)
if err != nil {
if p.Logger != nil {
p.Logger.Printf("Error updating value at path %v: %v", path, err)
}
continue
}
modificationCount++
modifications = append(modifications, ModificationRecord{
File: "",
OldValue: val,
NewValue: fmt.Sprintf("%v", newValue),
Operation: originalExpr,
Context: fmt.Sprintf("(JSONPath: %s)", pattern),
})
if p.Logger != nil {
p.Logger.Printf("Modified string node #%d: '%s' -> '%s'",
i+1, LimitString(val, 30), LimitString(fmt.Sprintf("%v", newValue), 30))
}
}
}
}
// Marshal the modified document back to JSON with indentation
if modificationCount > 0 {
modifiedJSON, err := json.MarshalIndent(modifiedDoc, "", " ")
// Convert to Lua variables
err = p.ToLua(L, value)
if err != nil {
return "", 0, 0, fmt.Errorf("error marshaling modified JSON: %v", err)
return content, modCount, matchCount, fmt.Errorf("error converting to Lua: %v", err)
}
if p.Logger != nil {
p.Logger.Printf("Made %d JSON node modifications", modificationCount)
// Execute Lua script
if err := L.DoString(luaExpr); err != nil {
return content, modCount, matchCount, fmt.Errorf("error executing Lua: %v", err)
}
return string(modifiedJSON), modificationCount, matchCount, nil
}
// Get modified value
result, err := p.FromLua(L)
if err != nil {
return content, modCount, matchCount, fmt.Errorf("error getting result from Lua: %v", err)
}
// If no modifications were made, return the original content
return content, 0, matchCount, nil
}
// findNodePaths implements a simplified JSONPath for finding paths to nodes
func (p *JSONProcessor) findNodePaths(doc interface{}, path string) ([][]interface{}, error) {
// Validate the path has proper syntax
if strings.Contains(path, "[[") || strings.Contains(path, "]]") {
return nil, fmt.Errorf("invalid JSONPath syntax: %s", path)
}
// Handle root element special case
if path == "$" {
return [][]interface{}{{doc}}, nil
}
// Split path into segments
segments := strings.Split(strings.TrimPrefix(path, "$."), ".")
// Start with the root
current := [][]interface{}{{doc}}
// Process each segment
for _, segment := range segments {
var next [][]interface{}
// Handle array notation [*]
if segment == "[*]" || strings.HasSuffix(segment, "[*]") {
baseName := strings.TrimSuffix(segment, "[*]")
for _, path := range current {
item := path[len(path)-1] // Get the last item in the path
switch v := item.(type) {
case map[string]interface{}:
if baseName == "" {
// [*] means all elements at this level
for _, val := range v {
if arr, ok := val.([]interface{}); ok {
for i, elem := range arr {
newPath := make([]interface{}, len(path)+2)
copy(newPath, path)
newPath[len(path)] = i // Array index
newPath[len(path)+1] = elem
next = append(next, newPath)
}
}
}
} else if arr, ok := v[baseName].([]interface{}); ok {
for i, elem := range arr {
newPath := make([]interface{}, len(path)+3)
copy(newPath, path)
newPath[len(path)] = baseName
newPath[len(path)+1] = i // Array index
newPath[len(path)+2] = elem
next = append(next, newPath)
}
}
case []interface{}:
for i, elem := range v {
newPath := make([]interface{}, len(path)+1)
copy(newPath, path)
newPath[len(path)-1] = i // Replace last elem with index
newPath[len(path)] = elem
next = append(next, newPath)
}
}
}
current = next
// Skip if value didn't change
if fmt.Sprintf("%v", value) == fmt.Sprintf("%v", result) {
continue
}
// Handle specific array indices
if strings.Contains(segment, "[") && strings.Contains(segment, "]") {
// Validate proper array syntax
if !regexp.MustCompile(`\[\d+\]$`).MatchString(segment) {
return nil, fmt.Errorf("invalid array index in JSONPath: %s", segment)
}
// Extract base name and index
baseName := segment[:strings.Index(segment, "[")]
idxStr := segment[strings.Index(segment, "[")+1 : strings.Index(segment, "]")]
idx, err := strconv.Atoi(idxStr)
if err != nil {
return nil, fmt.Errorf("invalid array index: %s", idxStr)
}
for _, path := range current {
item := path[len(path)-1] // Get the last item in the path
if obj, ok := item.(map[string]interface{}); ok {
if arr, ok := obj[baseName].([]interface{}); ok && idx < len(arr) {
newPath := make([]interface{}, len(path)+3)
copy(newPath, path)
newPath[len(path)] = baseName
newPath[len(path)+1] = idx
newPath[len(path)+2] = arr[idx]
next = append(next, newPath)
}
}
}
current = next
continue
// Apply the modification to the JSON data
err = p.updateJSONValue(jsonData, paths[i], result)
if err != nil {
return content, modCount, matchCount, fmt.Errorf("error updating JSON: %v", err)
}
// Handle regular object properties
for _, path := range current {
item := path[len(path)-1] // Get the last item in the path
if obj, ok := item.(map[string]interface{}); ok {
if val, exists := obj[segment]; exists {
newPath := make([]interface{}, len(path)+2)
copy(newPath, path)
newPath[len(path)] = segment
newPath[len(path)+1] = val
next = append(next, newPath)
}
}
}
current = next
modCount++
}
return current, nil
// Convert the modified JSON back to a string
jsonBytes, err := json.MarshalIndent(jsonData, "", " ")
if err != nil {
return content, modCount, matchCount, fmt.Errorf("error serializing JSON: %v", err)
}
return string(jsonBytes), modCount, matchCount, nil
}
// getValueAtPath extracts a value from a JSON document at the specified path
func (p *JSONProcessor) getValueAtPath(doc interface{}, path []interface{}) (interface{}, error) {
if len(path) == 0 {
return nil, fmt.Errorf("empty path")
// findJSONPaths finds all JSON paths and their values that match the given JSONPath expression
func (p *JSONProcessor) findJSONPaths(jsonData interface{}, pattern string) ([]string, []interface{}, error) {
// Extract all matching values using JSONPath
values, err := jsonpath.Get(pattern, jsonData)
if err != nil {
return nil, nil, err
}
// The last element in the path is the value itself
return path[len(path)-1], nil
}
// Convert values to a slice if it's not already
valuesSlice := []interface{}{}
paths := []string{}
// setValueAtPath updates a value in a JSON document at the specified path
func (p *JSONProcessor) setValueAtPath(doc interface{}, path []interface{}, newValue interface{}) error {
if len(path) < 2 {
return fmt.Errorf("path too short to update value")
}
// The path structure alternates: object/key/object/key/.../finalObject/finalKey/value
// We need to navigate to the object containing our key
// We'll get the parent object and the key to modify
// Find the parent object (second to last object) and the key (last object's property name)
// For the path structure, the parent is at index len-3 and key at len-2
if len(path) < 3 {
// Simple case: directly update the root object
rootObj, ok := doc.(map[string]interface{})
if !ok {
return fmt.Errorf("root is not an object, cannot update")
}
// Key should be a string
key, ok := path[len(path)-2].(string)
if !ok {
return fmt.Errorf("key is not a string: %v", path[len(path)-2])
}
rootObj[key] = newValue
return nil
}
// More complex case: we need to navigate to the parent object
parentIdx := len(path) - 3
keyIdx := len(path) - 2
// The actual key we need to modify
key, isString := path[keyIdx].(string)
keyInt, isInt := path[keyIdx].(int)
if !isString && !isInt {
return fmt.Errorf("key must be string or int, got %T", path[keyIdx])
}
// Get the parent object that contains the key
parent := path[parentIdx]
// If parent is a map, use string key
if parentMap, ok := parent.(map[string]interface{}); ok && isString {
parentMap[key] = newValue
return nil
}
// If parent is an array, use int key
if parentArray, ok := parent.([]interface{}); ok && isInt {
if keyInt < 0 || keyInt >= len(parentArray) {
return fmt.Errorf("array index %d out of bounds [0,%d)", keyInt, len(parentArray))
}
parentArray[keyInt] = newValue
return nil
}
return fmt.Errorf("cannot update value: parent is %T and key is %T", parent, path[keyIdx])
}
// SetupJSONHelpers adds JSON-specific helper functions to Lua
func (p *JSONProcessor) SetupJSONHelpers(L *lua.LState) {
// Helper to get type of JSON value
L.SetGlobal("json_type", L.NewFunction(func(L *lua.LState) int {
// Get the value passed to the function
val := L.Get(1)
// Determine type
switch val.Type() {
case lua.LTNil:
L.Push(lua.LString("null"))
case lua.LTBool:
L.Push(lua.LString("boolean"))
case lua.LTNumber:
L.Push(lua.LString("number"))
case lua.LTString:
L.Push(lua.LString("string"))
case lua.LTTable:
// Could be object or array - check for numeric keys
isArray := true
table := val.(*lua.LTable)
table.ForEach(func(key, value lua.LValue) {
if key.Type() != lua.LTNumber {
isArray = false
}
})
if isArray {
L.Push(lua.LString("array"))
} else {
L.Push(lua.LString("object"))
}
default:
L.Push(lua.LString("unknown"))
}
return 1
}))
}
// jsonToLua converts a Go JSON value to a Lua value
func (p *JSONProcessor) jsonToLua(L *lua.LState, val interface{}) lua.LValue {
if val == nil {
return lua.LNil
}
switch v := val.(type) {
case bool:
return lua.LBool(v)
case float64:
return lua.LNumber(v)
case string:
return lua.LString(v)
switch v := values.(type) {
case []interface{}:
arr := L.NewTable()
for i, item := range v {
arr.RawSetInt(i+1, p.jsonToLua(L, item))
valuesSlice = v
// Generate paths for array elements
// This is simplified - for complex JSONPath expressions you might
// need a more robust approach to generate the exact path
basePath := pattern
if strings.Contains(pattern, "[*]") || strings.HasSuffix(pattern, ".*") {
basePath = strings.Replace(pattern, "[*]", "", -1)
basePath = strings.Replace(basePath, ".*", "", -1)
for i := 0; i < len(v); i++ {
paths = append(paths, fmt.Sprintf("%s[%d]", basePath, i))
}
} else {
for range v {
paths = append(paths, pattern)
}
}
return arr
case map[string]interface{}:
obj := L.NewTable()
for k, item := range v {
obj.RawSetString(k, p.jsonToLua(L, item))
}
return obj
default:
// For unknown types, convert to string representation
return lua.LString(fmt.Sprintf("%v", val))
valuesSlice = append(valuesSlice, v)
paths = append(paths, pattern)
}
return paths, valuesSlice, nil
}
// luaToJSON converts a Lua value to a Go JSON-compatible value
func (p *JSONProcessor) luaToJSON(val lua.LValue) interface{} {
switch val.Type() {
case lua.LTNil:
return nil
case lua.LTBool:
return lua.LVAsBool(val)
case lua.LTNumber:
return float64(val.(lua.LNumber))
case lua.LTString:
return val.String()
case lua.LTTable:
table := val.(*lua.LTable)
// updateJSONValue updates a value in the JSON data structure at the given path
func (p *JSONProcessor) updateJSONValue(jsonData interface{}, path string, newValue interface{}) error {
// This is a simplified approach - for a production system you'd need a more robust solution
// that can handle all JSONPath expressions
parts := strings.Split(path, ".")
current := jsonData
// Check if it's an array or an object
isArray := true
maxN := 0
// Traverse the JSON structure
for i, part := range parts {
if i == len(parts)-1 {
// Last part, set the value
if strings.HasSuffix(part, "]") {
// Handle array access
arrayPart := part[:strings.Index(part, "[")]
indexPart := part[strings.Index(part, "[")+1 : strings.Index(part, "]")]
index, err := strconv.Atoi(indexPart)
if err != nil {
return fmt.Errorf("invalid array index: %s", indexPart)
}
table.ForEach(func(key, _ lua.LValue) {
if key.Type() == lua.LTNumber {
n := int(key.(lua.LNumber))
if n > maxN {
maxN = n
// Get the array
var array []interface{}
if arrayPart == "" {
// Direct array access
array, _ = current.([]interface{})
} else {
// Access array property
obj, _ := current.(map[string]interface{})
array, _ = obj[arrayPart].([]interface{})
}
// Set the value
if index >= 0 && index < len(array) {
array[index] = newValue
}
} else {
isArray = false
// Handle object property
obj, _ := current.(map[string]interface{})
obj[part] = newValue
}
})
if isArray && maxN > 0 {
// It's an array
arr := make([]interface{}, maxN)
for i := 1; i <= maxN; i++ {
item := table.RawGetInt(i)
if item != lua.LNil {
arr[i-1] = p.luaToJSON(item)
}
}
return arr
} else {
// It's an object
obj := make(map[string]interface{})
table.ForEach(func(key, value lua.LValue) {
if key.Type() == lua.LTString {
obj[key.String()] = p.luaToJSON(value)
} else {
// Convert key to string if it's not already
obj[fmt.Sprintf("%v", key)] = p.luaToJSON(value)
}
})
return obj
break
}
// Not the last part, continue traversing
if strings.HasSuffix(part, "]") {
// Handle array access
arrayPart := part[:strings.Index(part, "[")]
indexPart := part[strings.Index(part, "[")+1 : strings.Index(part, "]")]
index, err := strconv.Atoi(indexPart)
if err != nil {
return fmt.Errorf("invalid array index: %s", indexPart)
}
// Get the array
var array []interface{}
if arrayPart == "" {
// Direct array access
array, _ = current.([]interface{})
} else {
// Access array property
obj, _ := current.(map[string]interface{})
array, _ = obj[arrayPart].([]interface{})
}
// Continue with the array element
if index >= 0 && index < len(array) {
current = array[index]
}
} else {
// Handle object property
obj, _ := current.(map[string]interface{})
current = obj[part]
}
default:
// For functions, userdata, etc., convert to string
return val.String()
}
return nil
}
// ToLua converts JSON values to Lua variables
func (p *JSONProcessor) ToLua(L *lua.LState, data interface{}) error {
switch v := data.(type) {
case float64:
L.SetGlobal("v1", lua.LNumber(v))
L.SetGlobal("s1", lua.LString(fmt.Sprintf("%v", v)))
case int:
L.SetGlobal("v1", lua.LNumber(v))
L.SetGlobal("s1", lua.LString(fmt.Sprintf("%d", v)))
case string:
L.SetGlobal("s1", lua.LString(v))
// Try to convert to number if possible
if val, err := strconv.ParseFloat(v, 64); err == nil {
L.SetGlobal("v1", lua.LNumber(val))
} else {
L.SetGlobal("v1", lua.LNumber(0))
}
case bool:
if v {
L.SetGlobal("v1", lua.LNumber(1))
} else {
L.SetGlobal("v1", lua.LNumber(0))
}
L.SetGlobal("s1", lua.LString(fmt.Sprintf("%v", v)))
default:
// For complex types, convert to string
L.SetGlobal("s1", lua.LString(fmt.Sprintf("%v", v)))
L.SetGlobal("v1", lua.LNumber(0))
}
return nil
}
// FromLua retrieves values from Lua
func (p *JSONProcessor) FromLua(L *lua.LState) (interface{}, error) {
// Check if string variable was modified
s1 := L.GetGlobal("s1")
if s1 != lua.LNil {
if s1Str, ok := s1.(lua.LString); ok {
// Try to convert to number if it's numeric
if val, err := strconv.ParseFloat(string(s1Str), 64); err == nil {
return val, nil
}
// If it's "true" or "false", convert to boolean
if string(s1Str) == "true" {
return true, nil
}
if string(s1Str) == "false" {
return false, nil
}
return string(s1Str), nil
}
}
// Check if numeric variable was modified
v1 := L.GetGlobal("v1")
if v1 != lua.LNil {
if v1Num, ok := v1.(lua.LNumber); ok {
return float64(v1Num), nil
}
}
// Default return nil
return nil, nil
}