package processor import ( "encoding/json" "fmt" "modify/processor/jsonpath" "os" "path/filepath" "strings" lua "github.com/yuin/gopher-lua" ) // 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) (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 } // ProcessContent implements the Processor interface for JSONProcessor 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 content, 0, 0, fmt.Errorf("error parsing JSON: %v", err) } // Find nodes matching the JSONPath pattern nodes := jsonpath.Get(jsonData, pattern) if err != nil { return content, 0, 0, fmt.Errorf("error executing JSONPath: %v", err) } matchCount := len(nodes) if matchCount == 0 { return content, 0, 0, nil } // Initialize Lua L, err := NewLuaState() if err != nil { return content, len(nodes), 0, fmt.Errorf("error creating Lua state: %v", err) } defer L.Close() err = p.ToLua(L, nodes) if err != nil { return content, len(nodes), 0, fmt.Errorf("error converting to Lua: %v", err) } // Execute Lua script if err := L.DoString(luaExpr); err != nil { return content, len(nodes), 0, fmt.Errorf("error executing Lua %s: %v", luaExpr, err) } // Get modified value result, err := p.FromLua(L) if err != nil { return content, len(nodes), 0, fmt.Errorf("error getting result from Lua: %v", err) } // Apply the modification to the JSON data err = p.updateJSONValue(jsonData, pattern, result) if err != nil { return content, len(nodes), 0, fmt.Errorf("error updating JSON: %v", err) } // Convert the modified JSON back to a string with same formatting var jsonBytes []byte if indent, err := detectJsonIndentation(content); err == nil && indent != "" { // Use detected indentation for output formatting jsonBytes, err = json.MarshalIndent(jsonData, "", indent) } else { // Fall back to standard 2-space indent jsonBytes, err = json.MarshalIndent(jsonData, "", " ") } // We changed all the nodes trust me bro return string(jsonBytes), len(nodes), len(nodes), nil } // detectJsonIndentation tries to determine the indentation used in the original JSON func detectJsonIndentation(content string) (string, error) { lines := strings.Split(content, "\n") if len(lines) < 2 { return "", fmt.Errorf("not enough lines to detect indentation") } // Look for the first indented line for i := 1; i < len(lines); i++ { line := lines[i] trimmed := strings.TrimSpace(line) if trimmed == "" { continue } // Calculate leading whitespace indent := line[:len(line)-len(trimmed)] if len(indent) > 0 { return indent, nil } } return "", fmt.Errorf("no indentation detected") } // / Selects from the root node // // Selects nodes in the document from the current node that match the selection no matter where they are // . Selects the current node // @ Selects attributes // /bookstore/* Selects all the child element nodes of the bookstore element // //* Selects all elements in the document // /bookstore/book[1] Selects the first book element that is the child of the bookstore element. // /bookstore/book[last()] Selects the last book element that is the child of the bookstore element // /bookstore/book[last()-1] Selects the last but one book element that is the child of the bookstore element // /bookstore/book[position()<3] Selects the first two book elements that are children of the bookstore element // //title[@lang] Selects all the title elements that have an attribute named lang // //title[@lang='en'] Selects all the title elements that have a "lang" attribute with a value of "en" // /bookstore/book[price>35.00] Selects all the book elements of the bookstore element that have a price element with a value greater than 35.00 // /bookstore/book[price>35.00]/title Selects all the title elements of the book elements of the bookstore element that have a price element with a value greater than 35.00 // updateJSONValue updates a value in the JSON structure based on its JSONPath func (p *JSONProcessor) updateJSONValue(jsonData interface{}, path string, newValue interface{}) error { return nil } // ToLua converts JSON values to Lua variables func (p *JSONProcessor) ToLua(L *lua.LState, data interface{}) error { table, err := ToLuaTable(L, data) if err != nil { return err } L.SetGlobal("v", table) return nil } // FromLua retrieves values from Lua func (p *JSONProcessor) FromLua(L *lua.LState) (interface{}, error) { luaValue := L.GetGlobal("v") return FromLuaTable(L, luaValue.(*lua.LTable)) }