Implement a more better logging solution

This commit is contained in:
2025-03-27 17:47:39 +01:00
parent 81d8259dfc
commit 9cea103042
7 changed files with 579 additions and 78 deletions

357
logger/logger.go Normal file
View File

@@ -0,0 +1,357 @@
package logger
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
)
// LogLevel defines the severity of log messages
type LogLevel int
const (
// LevelError is for critical errors that should always be displayed
LevelError LogLevel = iota
// LevelWarning is for important warnings
LevelWarning
// LevelInfo is for informational messages
LevelInfo
// LevelDebug is for detailed debugging information
LevelDebug
// LevelTrace is for very detailed tracing information
LevelTrace
)
var levelNames = map[LogLevel]string{
LevelError: "ERROR",
LevelWarning: "WARNING",
LevelInfo: "INFO",
LevelDebug: "DEBUG",
LevelTrace: "TRACE",
}
var levelColors = map[LogLevel]string{
LevelError: "\033[1;31m", // Bold Red
LevelWarning: "\033[1;33m", // Bold Yellow
LevelInfo: "\033[1;32m", // Bold Green
LevelDebug: "\033[1;36m", // Bold Cyan
LevelTrace: "\033[1;35m", // Bold Magenta
}
// ResetColor is the ANSI code to reset text color
const ResetColor = "\033[0m"
// Logger is our custom logger with level support
type Logger struct {
mu sync.Mutex
out io.Writer
currentLevel LogLevel
prefix string
flag int
useColors bool
callerOffset int
defaultFields map[string]interface{}
}
var (
// DefaultLogger is the global logger instance
DefaultLogger *Logger
// defaultLogLevel is the default log level if not specified
defaultLogLevel = LevelInfo
// Global mutex for DefaultLogger initialization
initMutex sync.Mutex
)
// ParseLevel converts a string log level to LogLevel
func ParseLevel(levelStr string) LogLevel {
switch strings.ToUpper(levelStr) {
case "ERROR":
return LevelError
case "WARNING", "WARN":
return LevelWarning
case "INFO":
return LevelInfo
case "DEBUG":
return LevelDebug
case "TRACE":
return LevelTrace
default:
return defaultLogLevel
}
}
// String returns the string representation of the log level
func (l LogLevel) String() string {
if name, ok := levelNames[l]; ok {
return name
}
return fmt.Sprintf("Level(%d)", l)
}
// New creates a new Logger instance
func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{
out: out,
currentLevel: defaultLogLevel,
prefix: prefix,
flag: flag,
useColors: true,
callerOffset: 0,
defaultFields: make(map[string]interface{}),
}
}
// Init initializes the DefaultLogger
func Init(level LogLevel) {
initMutex.Lock()
defer initMutex.Unlock()
if DefaultLogger == nil {
DefaultLogger = New(os.Stdout, "", log.Lmicroseconds|log.Lshortfile)
}
DefaultLogger.SetLevel(level)
}
// SetLevel sets the current log level
func (l *Logger) SetLevel(level LogLevel) {
l.mu.Lock()
defer l.mu.Unlock()
l.currentLevel = level
}
// GetLevel returns the current log level
func (l *Logger) GetLevel() LogLevel {
l.mu.Lock()
defer l.mu.Unlock()
return l.currentLevel
}
// SetCallerOffset sets the caller offset for correct file and line reporting
func (l *Logger) SetCallerOffset(offset int) {
l.mu.Lock()
defer l.mu.Unlock()
l.callerOffset = offset
}
// WithField adds a field to the logger's context
func (l *Logger) WithField(key string, value interface{}) *Logger {
newLogger := &Logger{
out: l.out,
currentLevel: l.currentLevel,
prefix: l.prefix,
flag: l.flag,
useColors: l.useColors,
callerOffset: l.callerOffset,
defaultFields: make(map[string]interface{}),
}
// Copy existing fields
for k, v := range l.defaultFields {
newLogger.defaultFields[k] = v
}
// Add new field
newLogger.defaultFields[key] = value
return newLogger
}
// WithFields adds multiple fields to the logger's context
func (l *Logger) WithFields(fields map[string]interface{}) *Logger {
newLogger := &Logger{
out: l.out,
currentLevel: l.currentLevel,
prefix: l.prefix,
flag: l.flag,
useColors: l.useColors,
callerOffset: l.callerOffset,
defaultFields: make(map[string]interface{}),
}
// Copy existing fields
for k, v := range l.defaultFields {
newLogger.defaultFields[k] = v
}
// Add new fields
for k, v := range fields {
newLogger.defaultFields[k] = v
}
return newLogger
}
// formatMessage formats a log message with level, time, file, and line information
func (l *Logger) formatMessage(level LogLevel, format string, args ...interface{}) string {
var msg string
if len(args) > 0 {
msg = fmt.Sprintf(format, args...)
} else {
msg = format
}
// Format default fields if any
var fields string
if len(l.defaultFields) > 0 {
var pairs []string
for k, v := range l.defaultFields {
pairs = append(pairs, fmt.Sprintf("%s=%v", k, v))
}
fields = " " + strings.Join(pairs, " ")
}
var levelColor, resetColor string
if l.useColors {
levelColor = levelColors[level]
resetColor = ResetColor
}
var caller string
if l.flag&log.Lshortfile != 0 || l.flag&log.Llongfile != 0 {
_, file, line, ok := runtime.Caller(3 + l.callerOffset)
if !ok {
file = "???"
line = 0
}
if l.flag&log.Lshortfile != 0 {
file = filepath.Base(file)
}
caller = fmt.Sprintf("%s:%d ", file, line)
}
var timeStr string
if l.flag&(log.Ldate|log.Ltime|log.Lmicroseconds) != 0 {
t := time.Now()
if l.flag&log.Ldate != 0 {
timeStr += fmt.Sprintf("%04d/%02d/%02d ", t.Year(), t.Month(), t.Day())
}
if l.flag&(log.Ltime|log.Lmicroseconds) != 0 {
timeStr += fmt.Sprintf("%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second())
if l.flag&log.Lmicroseconds != 0 {
timeStr += fmt.Sprintf(".%06d", t.Nanosecond()/1000)
}
timeStr += " "
}
}
return fmt.Sprintf("%s%s%s%s[%s%s%s]%s %s\n",
l.prefix, timeStr, caller, levelColor, levelNames[level], resetColor, fields, resetColor, msg)
}
// log logs a message at the specified level
func (l *Logger) log(level LogLevel, format string, args ...interface{}) {
if level > l.currentLevel {
return
}
l.mu.Lock()
defer l.mu.Unlock()
msg := l.formatMessage(level, format, args...)
fmt.Fprint(l.out, msg)
}
// Error logs an error message
func (l *Logger) Error(format string, args ...interface{}) {
l.log(LevelError, format, args...)
}
// Warning logs a warning message
func (l *Logger) Warning(format string, args ...interface{}) {
l.log(LevelWarning, format, args...)
}
// Info logs an informational message
func (l *Logger) Info(format string, args ...interface{}) {
l.log(LevelInfo, format, args...)
}
// Debug logs a debug message
func (l *Logger) Debug(format string, args ...interface{}) {
l.log(LevelDebug, format, args...)
}
// Trace logs a trace message
func (l *Logger) Trace(format string, args ...interface{}) {
l.log(LevelTrace, format, args...)
}
// Global log functions that use DefaultLogger
// Error logs an error message using the default logger
func Error(format string, args ...interface{}) {
if DefaultLogger == nil {
Init(defaultLogLevel)
}
DefaultLogger.Error(format, args...)
}
// Warning logs a warning message using the default logger
func Warning(format string, args ...interface{}) {
if DefaultLogger == nil {
Init(defaultLogLevel)
}
DefaultLogger.Warning(format, args...)
}
// Info logs an informational message using the default logger
func Info(format string, args ...interface{}) {
if DefaultLogger == nil {
Init(defaultLogLevel)
}
DefaultLogger.Info(format, args...)
}
// Debug logs a debug message using the default logger
func Debug(format string, args ...interface{}) {
if DefaultLogger == nil {
Init(defaultLogLevel)
}
DefaultLogger.Debug(format, args...)
}
// Trace logs a trace message using the default logger
func Trace(format string, args ...interface{}) {
if DefaultLogger == nil {
Init(defaultLogLevel)
}
DefaultLogger.Trace(format, args...)
}
// SetLevel sets the log level for the default logger
func SetLevel(level LogLevel) {
if DefaultLogger == nil {
Init(level)
return
}
DefaultLogger.SetLevel(level)
}
// GetLevel gets the log level for the default logger
func GetLevel() LogLevel {
if DefaultLogger == nil {
Init(defaultLogLevel)
}
return DefaultLogger.GetLevel()
}
// WithField returns a new logger with the field added to the default logger's context
func WithField(key string, value interface{}) *Logger {
if DefaultLogger == nil {
Init(defaultLogLevel)
}
return DefaultLogger.WithField(key, value)
}
// WithFields returns a new logger with the fields added to the default logger's context
func WithFields(fields map[string]interface{}) *Logger {
if DefaultLogger == nil {
Init(defaultLogLevel)
}
return DefaultLogger.WithFields(fields)
}

89
main.go
View File

@@ -13,6 +13,7 @@ import (
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/object"
"modify/logger"
"modify/processor" "modify/processor"
) )
@@ -24,20 +25,22 @@ type GlobalStats struct {
} }
var stats GlobalStats var stats GlobalStats
var logger *log.Logger var stdLogger *log.Logger // Legacy logger for compatibility
var ( var (
jsonFlag = flag.Bool("json", false, "Process JSON files") jsonFlag = flag.Bool("json", false, "Process JSON files")
xmlFlag = flag.Bool("xml", false, "Process XML files") xmlFlag = flag.Bool("xml", false, "Process XML files")
gitFlag = flag.Bool("git", false, "Use git to manage files") gitFlag = flag.Bool("git", false, "Use git to manage files")
resetFlag = flag.Bool("reset", false, "Reset files to their original state") resetFlag = flag.Bool("reset", false, "Reset files to their original state")
logLevel = flag.String("loglevel", "INFO", "Set log level: ERROR, WARNING, INFO, DEBUG, TRACE")
repo *git.Repository repo *git.Repository
worktree *git.Worktree worktree *git.Worktree
) )
func init() { func init() {
// Keep standard logger setup for compatibility with legacy code
log.SetFlags(log.Lmicroseconds | log.Lshortfile) log.SetFlags(log.Lmicroseconds | log.Lshortfile)
logger = log.New(os.Stdout, "", log.Lmicroseconds|log.Lshortfile) stdLogger = log.New(os.Stdout, "", log.Lmicroseconds|log.Lshortfile)
stats = GlobalStats{} stats = GlobalStats{}
} }
@@ -66,6 +69,8 @@ func main() {
fmt.Fprintf(os.Stderr, " Use git to manage files\n") fmt.Fprintf(os.Stderr, " Use git to manage files\n")
fmt.Fprintf(os.Stderr, " -reset\n") fmt.Fprintf(os.Stderr, " -reset\n")
fmt.Fprintf(os.Stderr, " Reset files to their original state\n") fmt.Fprintf(os.Stderr, " Reset files to their original state\n")
fmt.Fprintf(os.Stderr, " -loglevel string\n")
fmt.Fprintf(os.Stderr, " Set logging level: ERROR, WARNING, INFO, DEBUG, TRACE (default \"INFO\")\n")
fmt.Fprintf(os.Stderr, " -mode string\n") fmt.Fprintf(os.Stderr, " -mode string\n")
fmt.Fprintf(os.Stderr, " Processing mode: regex, xml, json (default \"regex\")\n") fmt.Fprintf(os.Stderr, " Processing mode: regex, xml, json (default \"regex\")\n")
fmt.Fprintf(os.Stderr, "\nExamples:\n") fmt.Fprintf(os.Stderr, "\nExamples:\n")
@@ -86,13 +91,19 @@ func main() {
} }
flag.Parse() flag.Parse()
// Initialize logger with the specified log level
level := logger.ParseLevel(*logLevel)
logger.Init(level)
logger.Info("Initializing with log level: %s", level.String())
args := flag.Args() args := flag.Args()
if *resetFlag { if *resetFlag {
*gitFlag = true *gitFlag = true
} }
if len(args) < 3 { if len(args) < 3 {
log.Printf("At least %d arguments are required", 3) logger.Error("At least %d arguments are required", 3)
flag.Usage() flag.Usage()
return return
} }
@@ -109,37 +120,45 @@ func main() {
originalLuaExpr := luaExpr originalLuaExpr := luaExpr
luaExpr = processor.BuildLuaScript(luaExpr) luaExpr = processor.BuildLuaScript(luaExpr)
if originalLuaExpr != luaExpr { if originalLuaExpr != luaExpr {
logger.Printf("Transformed Lua expression from %q to %q", originalLuaExpr, luaExpr) logger.Debug("Transformed Lua expression from %q to %q", originalLuaExpr, luaExpr)
} }
if *gitFlag { if *gitFlag {
logger.Info("Git integration enabled, setting up git repository")
err := setupGit() err := setupGit()
if err != nil { if err != nil {
logger.Error("Failed to setup git: %v", err)
fmt.Fprintf(os.Stderr, "Error setting up git: %v\n", err) fmt.Fprintf(os.Stderr, "Error setting up git: %v\n", err)
return return
} }
} }
// Expand file patterns with glob support // Expand file patterns with glob support
logger.Debug("Expanding file patterns: %v", filePatterns)
files, err := expandFilePatterns(filePatterns) files, err := expandFilePatterns(filePatterns)
if err != nil { if err != nil {
logger.Error("Failed to expand file patterns: %v", err)
fmt.Fprintf(os.Stderr, "Error expanding file patterns: %v\n", err) fmt.Fprintf(os.Stderr, "Error expanding file patterns: %v\n", err)
return return
} }
if len(files) == 0 { if len(files) == 0 {
logger.Warning("No files found matching the specified patterns")
fmt.Fprintf(os.Stderr, "No files found matching the specified patterns\n") fmt.Fprintf(os.Stderr, "No files found matching the specified patterns\n")
return return
} }
if *gitFlag { if *gitFlag {
logger.Info("Cleaning up git files before processing")
err := cleanupGitFiles(files) err := cleanupGitFiles(files)
if err != nil { if err != nil {
logger.Error("Failed to cleanup git files: %v", err)
fmt.Fprintf(os.Stderr, "Error cleaning up git files: %v\n", err) fmt.Fprintf(os.Stderr, "Error cleaning up git files: %v\n", err)
return return
} }
} }
if *resetFlag { if *resetFlag {
logger.Info("Files reset to their original state, nothing more to do")
log.Printf("Files reset to their original state, nothing more to do") log.Printf("Files reset to their original state, nothing more to do")
return return
} }
@@ -149,15 +168,15 @@ func main() {
switch { switch {
case *xmlFlag: case *xmlFlag:
proc = &processor.XMLProcessor{} proc = &processor.XMLProcessor{}
logger.Printf("Starting XML modifier with XPath %q, expression %q on %d files", logger.Info("Starting XML modifier with XPath %q, expression %q on %d files",
pattern, luaExpr, len(files)) pattern, luaExpr, len(files))
case *jsonFlag: case *jsonFlag:
proc = &processor.JSONProcessor{} proc = &processor.JSONProcessor{}
logger.Printf("Starting JSON modifier with JSONPath %q, expression %q on %d files", logger.Info("Starting JSON modifier with JSONPath %q, expression %q on %d files",
pattern, luaExpr, len(files)) pattern, luaExpr, len(files))
default: default:
proc = &processor.RegexProcessor{} proc = &processor.RegexProcessor{}
logger.Printf("Starting regex modifier with pattern %q, expression %q on %d files", logger.Info("Starting regex modifier with pattern %q, expression %q on %d files",
pattern, luaExpr, len(files)) pattern, luaExpr, len(files))
} }
@@ -167,15 +186,24 @@ func main() {
wg.Add(1) wg.Add(1)
go func(file string) { go func(file string) {
defer wg.Done() defer wg.Done()
logger.Printf("Processing file: %s", file) logger.Debug("Processing file: %s", file)
// It's a bit fucked, maybe I could do better to call it from proc... But it'll do for now // It's a bit fucked, maybe I could do better to call it from proc... But it'll do for now
modCount, matchCount, err := processor.Process(proc, file, pattern, luaExpr) modCount, matchCount, err := processor.Process(proc, file, pattern, luaExpr)
if err != nil { if err != nil {
logger.Error("Failed to process file %s: %v", file, err)
fmt.Fprintf(os.Stderr, "Failed to process file %s: %v\n", file, err) fmt.Fprintf(os.Stderr, "Failed to process file %s: %v\n", file, err)
stats.FailedFiles++ stats.FailedFiles++
} else { } else {
logger.Printf("Successfully processed file: %s", file) if modCount > 0 {
logger.Info("Successfully processed file %s: %d modifications from %d matches",
file, modCount, matchCount)
} else if matchCount > 0 {
logger.Info("Found %d matches in file %s but made no modifications",
matchCount, file)
} else {
logger.Debug("No matches found in file: %s", file)
}
stats.ProcessedFiles++ stats.ProcessedFiles++
stats.TotalMatches += matchCount stats.TotalMatches += matchCount
stats.TotalModifications += modCount stats.TotalModifications += modCount
@@ -186,8 +214,11 @@ func main() {
// Print summary // Print summary
if stats.TotalModifications == 0 { if stats.TotalModifications == 0 {
logger.Warning("No modifications were made in any files")
fmt.Fprintf(os.Stderr, "No modifications were made in any files\n") fmt.Fprintf(os.Stderr, "No modifications were made in any files\n")
} else { } else {
logger.Info("Operation complete! Modified %d values in %d/%d files",
stats.TotalModifications, stats.ProcessedFiles, stats.ProcessedFiles+stats.FailedFiles)
fmt.Printf("Operation complete! Modified %d values in %d/%d files\n", fmt.Printf("Operation complete! Modified %d values in %d/%d files\n",
stats.TotalModifications, stats.ProcessedFiles, stats.ProcessedFiles+stats.FailedFiles) stats.TotalModifications, stats.ProcessedFiles, stats.ProcessedFiles+stats.FailedFiles)
} }
@@ -198,27 +229,27 @@ func setupGit() error {
if err != nil { if err != nil {
return fmt.Errorf("failed to get current working directory: %w", err) return fmt.Errorf("failed to get current working directory: %w", err)
} }
logger.Printf("Current working directory obtained: %s", cwd) logger.Debug("Current working directory obtained: %s", cwd)
logger.Printf("Attempting to open git repository at %s", cwd) logger.Debug("Attempting to open git repository at %s", cwd)
repo, err = git.PlainOpen(cwd) repo, err = git.PlainOpen(cwd)
if err != nil { if err != nil {
logger.Printf("No existing git repository found at %s, attempting to initialize a new git repository.", cwd) logger.Debug("No existing git repository found at %s, attempting to initialize a new git repository.", cwd)
repo, err = git.PlainInit(cwd, false) repo, err = git.PlainInit(cwd, false)
if err != nil { if err != nil {
return fmt.Errorf("failed to initialize a new git repository at %s: %w", cwd, err) return fmt.Errorf("failed to initialize a new git repository at %s: %w", cwd, err)
} }
logger.Printf("Successfully initialized a new git repository at %s", cwd) logger.Info("Successfully initialized a new git repository at %s", cwd)
} else { } else {
logger.Printf("Successfully opened existing git repository at %s", cwd) logger.Info("Successfully opened existing git repository at %s", cwd)
} }
logger.Printf("Attempting to obtain worktree for repository at %s", cwd) logger.Debug("Attempting to obtain worktree for repository at %s", cwd)
worktree, err = repo.Worktree() worktree, err = repo.Worktree()
if err != nil { if err != nil {
return fmt.Errorf("failed to obtain worktree for repository at %s: %w", cwd, err) return fmt.Errorf("failed to obtain worktree for repository at %s: %w", cwd, err)
} }
logger.Printf("Successfully obtained worktree for repository at %s", cwd) logger.Debug("Successfully obtained worktree for repository at %s", cwd)
return nil return nil
} }
@@ -230,45 +261,51 @@ func expandFilePatterns(patterns []string) ([]string, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get current working directory: %w", err) return nil, fmt.Errorf("failed to get current working directory: %w", err)
} }
logger.Debug("Expanding patterns from directory: %s", cwd)
for _, pattern := range patterns { for _, pattern := range patterns {
logger.Trace("Processing pattern: %s", pattern)
matches, _ := doublestar.Glob(os.DirFS(cwd), pattern) matches, _ := doublestar.Glob(os.DirFS(cwd), pattern)
log.Printf("Found %d matches for pattern %s", len(matches), pattern) logger.Debug("Found %d matches for pattern %s", len(matches), pattern)
for _, m := range matches { for _, m := range matches {
info, err := os.Stat(m) info, err := os.Stat(m)
if err != nil { if err != nil {
logger.Printf("Error getting file info for %s: %v", m, err) logger.Warning("Error getting file info for %s: %v", m, err)
continue continue
} }
if !info.IsDir() && !filesMap[m] { if !info.IsDir() && !filesMap[m] {
logger.Trace("Adding file to process list: %s", m)
filesMap[m], files = true, append(files, m) filesMap[m], files = true, append(files, m)
} }
} }
} }
if len(files) > 0 { if len(files) > 0 {
logger.Printf("Found %d files to process", len(files)) logger.Debug("Found %d files to process: %v", len(files), files)
} }
return files, nil return files, nil
} }
func cleanupGitFiles(files []string) error { func cleanupGitFiles(files []string) error {
for _, file := range files { for _, file := range files {
logger.Printf("Checking file: %s", file) logger.Debug("Checking git status for file: %s", file)
status, err := worktree.Status() status, err := worktree.Status()
if err != nil { if err != nil {
logger.Error("Error getting worktree status: %v", err)
fmt.Fprintf(os.Stderr, "Error getting worktree status: %v\n", err) fmt.Fprintf(os.Stderr, "Error getting worktree status: %v\n", err)
return fmt.Errorf("error getting worktree status: %w", err) return fmt.Errorf("error getting worktree status: %w", err)
} }
if status.IsUntracked(file) { if status.IsUntracked(file) {
logger.Printf("Detected untracked file: %s. Attempting to add it to the git index.", file) logger.Info("Detected untracked file: %s. Adding to git index.", file)
_, err = worktree.Add(file) _, err = worktree.Add(file)
if err != nil { if err != nil {
logger.Error("Error adding file to git: %v", err)
fmt.Fprintf(os.Stderr, "Error adding file to git: %v\n", err) fmt.Fprintf(os.Stderr, "Error adding file to git: %v\n", err)
return fmt.Errorf("error adding file to git: %w", err) return fmt.Errorf("error adding file to git: %w", err)
} }
filename := filepath.Base(file) filename := filepath.Base(file)
logger.Printf("File %s added successfully. Now committing it with message: 'Track %s'", filename, filename) logger.Info("File %s added successfully. Committing with message: 'Track %s'", filename, filename)
_, err = worktree.Commit("Track "+filename, &git.CommitOptions{ _, err = worktree.Commit("Track "+filename, &git.CommitOptions{
Author: &object.Signature{ Author: &object.Signature{
Name: "Big Chef", Name: "Big Chef",
@@ -277,22 +314,24 @@ func cleanupGitFiles(files []string) error {
}, },
}) })
if err != nil { if err != nil {
logger.Error("Error committing file: %v", err)
fmt.Fprintf(os.Stderr, "Error committing file: %v\n", err) fmt.Fprintf(os.Stderr, "Error committing file: %v\n", err)
return fmt.Errorf("error committing file: %w", err) return fmt.Errorf("error committing file: %w", err)
} }
logger.Printf("Successfully committed file: %s with message: 'Track %s'", filename, filename) logger.Info("Successfully committed file: %s", filename)
} else { } else {
logger.Printf("File %s is already tracked. Restoring it to the working tree.", file) logger.Info("File %s is already tracked. Restoring it to the working tree.", file)
err := worktree.Restore(&git.RestoreOptions{ err := worktree.Restore(&git.RestoreOptions{
Files: []string{file}, Files: []string{file},
Staged: true, Staged: true,
Worktree: true, Worktree: true,
}) })
if err != nil { if err != nil {
logger.Error("Error restoring file: %v", err)
fmt.Fprintf(os.Stderr, "Error restoring file: %v\n", err) fmt.Fprintf(os.Stderr, "Error restoring file: %v\n", err)
return fmt.Errorf("error restoring file: %w", err) return fmt.Errorf("error restoring file: %w", err)
} }
logger.Printf("File %s restored successfully.", file) logger.Info("File %s restored successfully", file)
} }
} }
return nil return nil

View File

@@ -3,7 +3,7 @@ package processor
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "modify/logger"
"modify/processor/jsonpath" "modify/processor/jsonpath"
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
@@ -14,80 +14,98 @@ type JSONProcessor struct{}
// ProcessContent implements the Processor interface for JSONProcessor // ProcessContent implements the Processor interface for JSONProcessor
func (p *JSONProcessor) ProcessContent(content string, pattern string, luaExpr string) (string, int, int, error) { func (p *JSONProcessor) ProcessContent(content string, pattern string, luaExpr string) (string, int, int, error) {
logger.Debug("Processing JSON content with JSONPath: %s", pattern)
// Parse JSON document // Parse JSON document
logger.Trace("Parsing JSON document")
var jsonData interface{} var jsonData interface{}
err := json.Unmarshal([]byte(content), &jsonData) err := json.Unmarshal([]byte(content), &jsonData)
if err != nil { if err != nil {
logger.Error("Failed to parse JSON: %v", err)
return content, 0, 0, fmt.Errorf("error parsing JSON: %v", err) return content, 0, 0, fmt.Errorf("error parsing JSON: %v", err)
} }
// Find nodes matching the JSONPath pattern // Find nodes matching the JSONPath pattern
logger.Debug("Executing JSONPath query: %s", pattern)
nodes, err := jsonpath.Get(jsonData, pattern) nodes, err := jsonpath.Get(jsonData, pattern)
if err != nil { if err != nil {
logger.Error("Failed to execute JSONPath: %v", err)
return content, 0, 0, fmt.Errorf("error getting nodes: %v", err) return content, 0, 0, fmt.Errorf("error getting nodes: %v", err)
} }
matchCount := len(nodes) matchCount := len(nodes)
logger.Debug("Found %d nodes matching JSONPath", matchCount)
if matchCount == 0 { if matchCount == 0 {
logger.Warning("No nodes matched the JSONPath pattern: %s", pattern)
return content, 0, 0, nil return content, 0, 0, nil
} }
modCount := 0 modCount := 0
for _, node := range nodes { for i, node := range nodes {
log.Printf("Processing node at path: %s with value: %v", node.Path, node.Value) logger.Trace("Processing node #%d at path: %s with value: %v", i+1, node.Path, node.Value)
// Initialize Lua // Initialize Lua
L, err := NewLuaState() L, err := NewLuaState()
if err != nil { if err != nil {
logger.Error("Failed to create Lua state: %v", err)
return content, len(nodes), 0, fmt.Errorf("error creating Lua state: %v", err) return content, len(nodes), 0, fmt.Errorf("error creating Lua state: %v", err)
} }
defer L.Close() defer L.Close()
log.Println("Lua state initialized successfully.") logger.Trace("Lua state initialized successfully")
err = p.ToLua(L, node.Value) err = p.ToLua(L, node.Value)
if err != nil { if err != nil {
logger.Error("Failed to convert value to Lua: %v", err)
return content, len(nodes), 0, fmt.Errorf("error converting to Lua: %v", err) return content, len(nodes), 0, fmt.Errorf("error converting to Lua: %v", err)
} }
log.Printf("Converted node value to Lua: %v", node.Value) logger.Trace("Converted node value to Lua: %v", node.Value)
originalScript := luaExpr originalScript := luaExpr
fullScript := BuildLuaScript(luaExpr) fullScript := BuildLuaScript(luaExpr)
log.Printf("Original script: %q, Full script: %q", originalScript, fullScript) logger.Debug("Original script: %q, Full script: %q", originalScript, fullScript)
// Execute Lua script // Execute Lua script
log.Printf("Executing Lua script: %q", fullScript) logger.Trace("Executing Lua script: %q", fullScript)
if err := L.DoString(fullScript); err != nil { if err := L.DoString(fullScript); err != nil {
logger.Error("Failed to execute Lua script: %v", err)
return content, len(nodes), 0, fmt.Errorf("error executing Lua %q: %v", fullScript, err) return content, len(nodes), 0, fmt.Errorf("error executing Lua %q: %v", fullScript, err)
} }
log.Println("Lua script executed successfully.") logger.Trace("Lua script executed successfully")
// Get modified value // Get modified value
result, err := p.FromLua(L) result, err := p.FromLua(L)
if err != nil { if err != nil {
logger.Error("Failed to get result from Lua: %v", err)
return content, len(nodes), 0, fmt.Errorf("error getting result from Lua: %v", err) return content, len(nodes), 0, fmt.Errorf("error getting result from Lua: %v", err)
} }
log.Printf("Retrieved modified value from Lua: %v", result) logger.Trace("Retrieved modified value from Lua: %v", result)
modified := false modified := false
modified = L.GetGlobal("modified").String() == "true" modified = L.GetGlobal("modified").String() == "true"
if !modified { if !modified {
log.Printf("No changes made to node at path: %s", node.Path) logger.Debug("No changes made to node at path: %s", node.Path)
continue continue
} }
// Apply the modification to the JSON data // Apply the modification to the JSON data
logger.Debug("Updating JSON at path: %s with new value: %v", node.Path, result)
err = p.updateJSONValue(jsonData, node.Path, result) err = p.updateJSONValue(jsonData, node.Path, result)
if err != nil { if err != nil {
logger.Error("Failed to update JSON at path %s: %v", node.Path, err)
return content, len(nodes), 0, fmt.Errorf("error updating JSON: %v", err) return content, len(nodes), 0, fmt.Errorf("error updating JSON: %v", err)
} }
log.Printf("Updated JSON at path: %s with new value: %v", node.Path, result) logger.Debug("Updated JSON at path: %s successfully", node.Path)
modCount++ modCount++
} }
logger.Info("JSON processing complete: %d modifications from %d matches", modCount, matchCount)
// Convert the modified JSON back to a string with same formatting // Convert the modified JSON back to a string with same formatting
logger.Trace("Marshalling JSON data back to string")
var jsonBytes []byte var jsonBytes []byte
jsonBytes, err = json.MarshalIndent(jsonData, "", " ") jsonBytes, err = json.MarshalIndent(jsonData, "", " ")
if err != nil { if err != nil {
logger.Error("Failed to marshal JSON: %v", err)
return content, modCount, matchCount, fmt.Errorf("error marshalling JSON: %v", err) return content, modCount, matchCount, fmt.Errorf("error marshalling JSON: %v", err)
} }
return string(jsonBytes), modCount, matchCount, nil return string(jsonBytes), modCount, matchCount, nil
@@ -95,8 +113,11 @@ func (p *JSONProcessor) ProcessContent(content string, pattern string, luaExpr s
// updateJSONValue updates a value in the JSON structure based on its JSONPath // updateJSONValue updates a value in the JSON structure based on its JSONPath
func (p *JSONProcessor) updateJSONValue(jsonData interface{}, path string, newValue interface{}) error { func (p *JSONProcessor) updateJSONValue(jsonData interface{}, path string, newValue interface{}) error {
logger.Trace("Updating JSON value at path: %s", path)
// Special handling for root node // Special handling for root node
if path == "$" { if path == "$" {
logger.Debug("Handling special case for root node update")
// For the root node, we'll copy the value to the jsonData reference // For the root node, we'll copy the value to the jsonData reference
// This is a special case since we can't directly replace the interface{} variable // This is a special case since we can't directly replace the interface{} variable
@@ -108,15 +129,18 @@ func (p *JSONProcessor) updateJSONValue(jsonData interface{}, path string, newVa
if !ok { if !ok {
// If the original wasn't a map, completely replace it with the new map // If the original wasn't a map, completely replace it with the new map
// This is handled by the jsonpath.Set function // This is handled by the jsonpath.Set function
logger.Debug("Root was not a map, replacing entire root")
return jsonpath.Set(jsonData, path, newValue) return jsonpath.Set(jsonData, path, newValue)
} }
// Clear the original map // Clear the original map
logger.Trace("Clearing original root map")
for k := range rootMap { for k := range rootMap {
delete(rootMap, k) delete(rootMap, k)
} }
// Copy all keys from the new map // Copy all keys from the new map
logger.Trace("Copying keys to root map")
for k, v := range rootValue { for k, v := range rootValue {
rootMap[k] = v rootMap[k] = v
} }
@@ -127,22 +151,27 @@ func (p *JSONProcessor) updateJSONValue(jsonData interface{}, path string, newVa
rootArray, ok := jsonData.([]interface{}) rootArray, ok := jsonData.([]interface{})
if !ok { if !ok {
// If the original wasn't an array, use jsonpath.Set // If the original wasn't an array, use jsonpath.Set
logger.Debug("Root was not an array, replacing entire root")
return jsonpath.Set(jsonData, path, newValue) return jsonpath.Set(jsonData, path, newValue)
} }
// Clear and recreate the array // Clear and recreate the array
logger.Trace("Replacing root array")
*&rootArray = rootValue *&rootArray = rootValue
return nil return nil
default: default:
// For other types, use jsonpath.Set // For other types, use jsonpath.Set
logger.Debug("Replacing root with primitive value")
return jsonpath.Set(jsonData, path, newValue) return jsonpath.Set(jsonData, path, newValue)
} }
} }
// For non-root paths, use the regular Set method // For non-root paths, use the regular Set method
logger.Trace("Using regular Set method for non-root path")
err := jsonpath.Set(jsonData, path, newValue) err := jsonpath.Set(jsonData, path, newValue)
if err != nil { if err != nil {
logger.Error("Failed to set JSON value at path %s: %v", path, err)
return fmt.Errorf("failed to update JSON value at path '%s': %w", path, err) return fmt.Errorf("failed to update JSON value at path '%s': %w", path, err)
} }
return nil return nil

View File

@@ -2,13 +2,14 @@ package processor
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/antchfx/xmlquery" "github.com/antchfx/xmlquery"
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
"modify/logger"
) )
// Processor defines the interface for all file processors // Processor defines the interface for all file processors
@@ -42,14 +43,18 @@ func NewLuaState() (*lua.LState, error) {
// defer L.Close() // defer L.Close()
// Load math library // Load math library
logger.Debug("Loading Lua math library")
L.Push(L.GetGlobal("require")) L.Push(L.GetGlobal("require"))
L.Push(lua.LString("math")) L.Push(lua.LString("math"))
if err := L.PCall(1, 1, nil); err != nil { if err := L.PCall(1, 1, nil); err != nil {
logger.Error("Failed to load Lua math library: %v", err)
return nil, fmt.Errorf("error loading Lua math library: %v", err) return nil, fmt.Errorf("error loading Lua math library: %v", err)
} }
// Initialize helper functions // Initialize helper functions
logger.Debug("Initializing Lua helper functions")
if err := InitLuaHelpers(L); err != nil { if err := InitLuaHelpers(L); err != nil {
logger.Error("Failed to initialize Lua helper functions: %v", err)
return nil, err return nil, err
} }
@@ -60,29 +65,40 @@ func Process(p Processor, filename string, pattern string, luaExpr string) (int,
// Read file content // Read file content
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {
logger.Error("Failed to get current working directory: %v", err)
return 0, 0, fmt.Errorf("error getting current working directory: %v", err) return 0, 0, fmt.Errorf("error getting current working directory: %v", err)
} }
fullPath := filepath.Join(cwd, filename) fullPath := filepath.Join(cwd, filename)
logger.Trace("Reading file content from: %s", fullPath)
content, err := os.ReadFile(fullPath) content, err := os.ReadFile(fullPath)
if err != nil { if err != nil {
logger.Error("Failed to read file %s: %v", fullPath, err)
return 0, 0, fmt.Errorf("error reading file: %v", err) return 0, 0, fmt.Errorf("error reading file: %v", err)
} }
fileContent := string(content) fileContent := string(content)
logger.Trace("File %s read successfully, size: %d bytes", fullPath, len(content))
// Process the content // Process the content
logger.Debug("Processing content for file: %s", filename)
modifiedContent, modCount, matchCount, err := p.ProcessContent(fileContent, pattern, luaExpr) modifiedContent, modCount, matchCount, err := p.ProcessContent(fileContent, pattern, luaExpr)
if err != nil { if err != nil {
logger.Error("Error processing content for file %s: %v", filename, err)
return 0, 0, err return 0, 0, err
} }
// If we made modifications, save the file // If we made modifications, save the file
if modCount > 0 { if modCount > 0 {
logger.Info("Writing %d modifications to file: %s", modCount, filename)
err = os.WriteFile(fullPath, []byte(modifiedContent), 0644) err = os.WriteFile(fullPath, []byte(modifiedContent), 0644)
if err != nil { if err != nil {
logger.Error("Failed to write to file %s: %v", fullPath, err)
return 0, 0, fmt.Errorf("error writing file: %v", err) return 0, 0, fmt.Errorf("error writing file: %v", err)
} }
logger.Debug("File %s written successfully", filename)
} else {
logger.Debug("No modifications to write for file: %s", filename)
} }
return modCount, matchCount, nil return modCount, matchCount, nil
@@ -174,25 +190,28 @@ func FromLua(L *lua.LState, luaValue lua.LValue) (interface{}, error) {
} }
func IsLuaTableArray(L *lua.LState, v *lua.LTable) (bool, error) { func IsLuaTableArray(L *lua.LState, v *lua.LTable) (bool, error) {
logger.Trace("Checking if Lua table is an array")
L.SetGlobal("table_to_check", v) L.SetGlobal("table_to_check", v)
// Use our predefined helper function from InitLuaHelpers // Use our predefined helper function from InitLuaHelpers
err := L.DoString(`is_array = isArray(table_to_check)`) err := L.DoString(`is_array = isArray(table_to_check)`)
if err != nil { if err != nil {
logger.Error("Error determining if table is an array: %v", err)
return false, fmt.Errorf("error determining if table is array: %w", err) return false, fmt.Errorf("error determining if table is array: %w", err)
} }
// Check the result of our Lua function // Check the result of our Lua function
isArray := L.GetGlobal("is_array") isArray := L.GetGlobal("is_array")
// LVIsFalse returns true if a given LValue is a nil or false otherwise false. // LVIsFalse returns true if a given LValue is a nil or false otherwise false.
if !lua.LVIsFalse(isArray) { result := !lua.LVIsFalse(isArray)
return true, nil logger.Trace("Lua table is array: %v", result)
} return result, nil
return false, nil
} }
// InitLuaHelpers initializes common Lua helper functions // InitLuaHelpers initializes common Lua helper functions
func InitLuaHelpers(L *lua.LState) error { func InitLuaHelpers(L *lua.LState) error {
logger.Debug("Loading Lua helper functions")
helperScript := ` helperScript := `
-- Custom Lua helpers for math operations -- Custom Lua helpers for math operations
function min(a, b) return math.min(a, b) end function min(a, b) return math.min(a, b) end
@@ -239,9 +258,11 @@ end
modified = false modified = false
` `
if err := L.DoString(helperScript); err != nil { if err := L.DoString(helperScript); err != nil {
logger.Error("Failed to load Lua helper functions: %v", err)
return fmt.Errorf("error loading helper functions: %v", err) return fmt.Errorf("error loading helper functions: %v", err)
} }
logger.Debug("Setting up Lua print function to Go")
L.SetGlobal("print", L.NewFunction(printToGo)) L.SetGlobal("print", L.NewFunction(printToGo))
return nil return nil
} }
@@ -280,6 +301,8 @@ func PrependLuaAssignment(luaExpr string) string {
// BuildLuaScript prepares a Lua expression from shorthand notation // BuildLuaScript prepares a Lua expression from shorthand notation
func BuildLuaScript(luaExpr string) string { func BuildLuaScript(luaExpr string) string {
logger.Debug("Building Lua script from expression: %s", luaExpr)
luaExpr = PrependLuaAssignment(luaExpr) luaExpr = PrependLuaAssignment(luaExpr)
// This allows the user to specify whether or not they modified a value // This allows the user to specify whether or not they modified a value
@@ -300,17 +323,14 @@ func BuildLuaScript(luaExpr string) string {
} }
func printToGo(L *lua.LState) int { func printToGo(L *lua.LState) int {
// Get the number of arguments passed to the Lua print function top := L.GetTop()
n := L.GetTop() args := make([]interface{}, top)
// Create a slice to hold the arguments for i := 1; i <= top; i++ {
args := make([]interface{}, n) args[i-1] = L.Get(i)
for i := 1; i <= n; i++ {
args[i-1] = L.Get(i) // Get the argument from Lua stack
} }
// Print the arguments to Go's stdout message := fmt.Sprint(args...)
log.Print("Lua: ") logger.Info("[Lua] %s", message)
log.Println(args...) return 0
return 0 // No return values
} }
// Max returns the maximum of two integers // Max returns the maximum of two integers

View File

@@ -2,13 +2,14 @@ package processor
import ( import (
"fmt" "fmt"
"log"
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
lua "github.com/yuin/gopher-lua" lua "github.com/yuin/gopher-lua"
"modify/logger"
) )
// RegexProcessor implements the Processor interface using regex patterns // RegexProcessor implements the Processor interface using regex patterns
@@ -98,16 +99,17 @@ type ReplaceCommand struct {
// ProcessContent applies regex replacement with Lua processing // ProcessContent applies regex replacement with Lua processing
func (p *RegexProcessor) ProcessContent(content string, pattern string, luaExpr string) (string, int, int, error) { func (p *RegexProcessor) ProcessContent(content string, pattern string, luaExpr string) (string, int, int, error) {
pattern = ResolveRegexPlaceholders(pattern) pattern = ResolveRegexPlaceholders(pattern)
logger.Debug("Compiling regex pattern: %s", pattern)
compiledPattern, err := regexp.Compile(pattern) compiledPattern, err := regexp.Compile(pattern)
if err != nil { if err != nil {
log.Printf("Error compiling pattern: %v", err) logger.Error("Error compiling pattern: %v", err)
return "", 0, 0, fmt.Errorf("error compiling pattern: %v", err) return "", 0, 0, fmt.Errorf("error compiling pattern: %v", err)
} }
log.Printf("Compiled pattern successfully: %s", pattern) logger.Debug("Compiled pattern successfully: %s", pattern)
previous := luaExpr previous := luaExpr
luaExpr = BuildLuaScript(luaExpr) luaExpr = BuildLuaScript(luaExpr)
log.Printf("Changing Lua expression from: %s to: %s", previous, luaExpr) logger.Debug("Changing Lua expression from: %s to: %s", previous, luaExpr)
// Initialize Lua environment // Initialize Lua environment
modificationCount := 0 modificationCount := 0
@@ -115,7 +117,7 @@ func (p *RegexProcessor) ProcessContent(content string, pattern string, luaExpr
// Process all regex matches // Process all regex matches
result := content result := content
indices := compiledPattern.FindAllStringSubmatchIndex(content, -1) indices := compiledPattern.FindAllStringSubmatchIndex(content, -1)
log.Printf("Found %d matches in the content", len(indices)) logger.Debug("Found %d matches in the content", len(indices))
// We walk backwards because we're replacing something with something else that might be longer // We walk backwards because we're replacing something with something else that might be longer
// And in the case it is longer than the original all indicces past that change will be fucked up // And in the case it is longer than the original all indicces past that change will be fucked up
@@ -124,17 +126,17 @@ func (p *RegexProcessor) ProcessContent(content string, pattern string, luaExpr
for i := len(indices) - 1; i >= 0; i-- { for i := len(indices) - 1; i >= 0; i-- {
L, err := NewLuaState() L, err := NewLuaState()
if err != nil { if err != nil {
log.Printf("Error creating Lua state: %v", err) logger.Error("Error creating Lua state: %v", err)
return "", 0, 0, fmt.Errorf("error creating Lua state: %v", err) return "", 0, 0, fmt.Errorf("error creating Lua state: %v", err)
} }
// Hmm... Maybe we don't want to defer this.. // Hmm... Maybe we don't want to defer this..
// Maybe we want to close them every iteration // Maybe we want to close them every iteration
// We'll leave it as is for now // We'll leave it as is for now
defer L.Close() defer L.Close()
log.Printf("Lua state created successfully") logger.Trace("Lua state created successfully")
matchIndices := indices[i] matchIndices := indices[i]
log.Printf("Processing match indices: %v", matchIndices) logger.Trace("Processing match indices: %v", matchIndices)
// Why we're doing this whole song and dance of indices is to properly handle empty matches // Why we're doing this whole song and dance of indices is to properly handle empty matches
// Plus it's a little cleaner to surgically replace our matches // Plus it's a little cleaner to surgically replace our matches
@@ -144,21 +146,21 @@ func (p *RegexProcessor) ProcessContent(content string, pattern string, luaExpr
// As if concatenating in the middle of the array // As if concatenating in the middle of the array
// Plus it supports lookarounds // Plus it supports lookarounds
match := content[matchIndices[0]:matchIndices[1]] match := content[matchIndices[0]:matchIndices[1]]
log.Printf("Matched content: %s", match) logger.Trace("Matched content: %s", match)
groups := matchIndices[2:] groups := matchIndices[2:]
if len(groups) <= 0 { if len(groups) <= 0 {
log.Println("No capture groups for lua to chew on") logger.Warning("No capture groups for lua to chew on")
continue continue
} }
if len(groups)%2 == 1 { if len(groups)%2 == 1 {
log.Println("Odd number of indices of groups, what the fuck?") logger.Warning("Odd number of indices of groups, what the fuck?")
continue continue
} }
for _, index := range groups { for _, index := range groups {
if index == -1 { if index == -1 {
// return "", 0, 0, fmt.Errorf("negative indices encountered: %v. This indicates that there was an issue with the match indices, possibly due to an empty match or an unexpected pattern. Please check the regex pattern and input content.", matchIndices) // return "", 0, 0, fmt.Errorf("negative indices encountered: %v. This indicates that there was an issue with the match indices, possibly due to an empty match or an unexpected pattern. Please check the regex pattern and input content.", matchIndices)
log.Printf("Negative indices encountered: %v. This indicates that there was an issue with the match indices, possibly due to an empty match or an unexpected pattern. This is not an error but it's possibly not what you want.", matchIndices) logger.Warning("Negative indices encountered: %v. This indicates that there was an issue with the match indices, possibly due to an empty match or an unexpected pattern. This is not an error but it's possibly not what you want.", matchIndices)
continue continue
} }
} }
@@ -187,25 +189,25 @@ func (p *RegexProcessor) ProcessContent(content string, pattern string, luaExpr
} }
for _, capture := range captureGroups { for _, capture := range captureGroups {
log.Printf("Capture group: %+v", *capture) logger.Trace("Capture group: %+v", *capture)
} }
if err := p.ToLua(L, captureGroups); err != nil { if err := p.ToLua(L, captureGroups); err != nil {
log.Printf("Error setting Lua variables: %v", err) logger.Error("Error setting Lua variables: %v", err)
continue continue
} }
log.Println("Lua variables set successfully") logger.Trace("Lua variables set successfully")
if err := L.DoString(luaExpr); err != nil { if err := L.DoString(luaExpr); err != nil {
log.Printf("Error executing Lua code %s for groups %+v: %v", luaExpr, captureGroups, err) logger.Error("Error executing Lua code %s for groups %+v: %v", luaExpr, captureGroups, err)
continue continue
} }
log.Println("Lua code executed successfully") logger.Trace("Lua code executed successfully")
// Get modifications from Lua // Get modifications from Lua
captureGroups, err = p.FromLuaCustom(L, captureGroups) captureGroups, err = p.FromLuaCustom(L, captureGroups)
if err != nil { if err != nil {
log.Printf("Error getting modifications: %v", err) logger.Error("Error getting modifications: %v", err)
continue continue
} }
@@ -214,12 +216,20 @@ func (p *RegexProcessor) ProcessContent(content string, pattern string, luaExpr
if replacementVar.Type() != lua.LTNil { if replacementVar.Type() != lua.LTNil {
replacement = replacementVar.String() replacement = replacementVar.String()
} }
// Check if modification flag is set
modifiedVal := L.GetGlobal("modified")
if modifiedVal.Type() != lua.LTBool || !lua.LVAsBool(modifiedVal) {
logger.Debug("No modifications made by Lua script")
continue
}
if replacement == "" { if replacement == "" {
commands := make([]ReplaceCommand, 0, len(captureGroups)) commands := make([]ReplaceCommand, 0, len(captureGroups))
// Apply the modifications to the original match // Apply the modifications to the original match
replacement = match replacement = match
for _, capture := range captureGroups { for _, capture := range captureGroups {
log.Printf("Applying modification: %s", capture.Updated) logger.Debug("Applying modification: %s", capture.Updated)
// Indices of the group are relative to content // Indices of the group are relative to content
// To relate them to match we have to subtract the match start index // To relate them to match we have to subtract the match start index
// replacement = replacement[:groupStart] + newVal + replacement[groupEnd:] // replacement = replacement[:groupStart] + newVal + replacement[groupEnd:]
@@ -238,12 +248,13 @@ func (p *RegexProcessor) ProcessContent(content string, pattern string, luaExpr
replacement = replacement[:command.From] + command.With + replacement[command.To:] replacement = replacement[:command.From] + command.With + replacement[command.To:]
} }
} }
modificationCount++ modificationCount++
result = result[:matchIndices[0]] + replacement + result[matchIndices[1]:] result = result[:matchIndices[0]] + replacement + result[matchIndices[1]:]
log.Printf("Modification count updated: %d", modificationCount) logger.Debug("Modification count updated: %d", modificationCount)
} }
log.Printf("Process completed with %d modifications", modificationCount) logger.Debug("Process completed with %d modifications", modificationCount)
return result, modificationCount, len(indices), nil return result, modificationCount, len(indices), nil
} }
@@ -256,7 +267,8 @@ func ResolveRegexPlaceholders(pattern string) string {
// Handle special pattern modifications // Handle special pattern modifications
if !strings.HasPrefix(pattern, "(?s)") { if !strings.HasPrefix(pattern, "(?s)") {
pattern = "(?s)" + pattern pattern = "(?s)" + pattern
log.Printf("Pattern modified to include (?s): %s", pattern) // Use fmt.Printf for test compatibility
fmt.Printf("Pattern modified to include (?s): %s\n", pattern)
} }
namedGroupNum := regexp.MustCompile(`(?:(\?<[^>]+>)(!num))`) namedGroupNum := regexp.MustCompile(`(?:(\?<[^>]+>)(!num))`)

22
processor/test_helper.go Normal file
View File

@@ -0,0 +1,22 @@
package processor
import (
"io/ioutil"
"modify/logger"
"os"
)
func init() {
// Initialize logger with ERROR level for tests
// to minimize noise in test output
logger.Init(logger.LevelError)
// Optionally redirect logger output to discard
// This prevents logger output from interfering with test output
disableTestLogs := os.Getenv("ENABLE_TEST_LOGS") != "1"
if disableTestLogs {
// Create a new logger that writes to nowhere
silentLogger := logger.New(ioutil.Discard, "", 0)
logger.DefaultLogger = silentLogger
}
}

View File

@@ -2,7 +2,7 @@ package processor
import ( import (
"fmt" "fmt"
"log" "modify/logger"
"modify/processor/xpath" "modify/processor/xpath"
"strings" "strings"
@@ -15,69 +15,91 @@ type XMLProcessor struct{}
// ProcessContent implements the Processor interface for XMLProcessor // ProcessContent implements the Processor interface for XMLProcessor
func (p *XMLProcessor) ProcessContent(content string, path string, luaExpr string) (string, int, int, error) { func (p *XMLProcessor) ProcessContent(content string, path string, luaExpr string) (string, int, int, error) {
logger.Debug("Processing XML content with XPath: %s", path)
// Parse XML document // Parse XML document
// We can't really use encoding/xml here because it requires a pre defined struct // We can't really use encoding/xml here because it requires a pre defined struct
// And we HAVE TO parse dynamic unknown XML // And we HAVE TO parse dynamic unknown XML
logger.Trace("Parsing XML document")
doc, err := xmlquery.Parse(strings.NewReader(content)) doc, err := xmlquery.Parse(strings.NewReader(content))
if err != nil { if err != nil {
logger.Error("Failed to parse XML: %v", err)
return content, 0, 0, fmt.Errorf("error parsing XML: %v", err) return content, 0, 0, fmt.Errorf("error parsing XML: %v", err)
} }
// Find nodes matching the XPath pattern // Find nodes matching the XPath pattern
logger.Debug("Executing XPath query: %s", path)
nodes, err := xpath.Get(doc, path) nodes, err := xpath.Get(doc, path)
if err != nil { if err != nil {
logger.Error("Failed to execute XPath: %v", err)
return content, 0, 0, fmt.Errorf("error executing XPath: %v", err) return content, 0, 0, fmt.Errorf("error executing XPath: %v", err)
} }
matchCount := len(nodes) matchCount := len(nodes)
logger.Debug("Found %d nodes matching XPath", matchCount)
if matchCount == 0 { if matchCount == 0 {
logger.Warning("No nodes matched the XPath pattern: %s", path)
return content, 0, 0, nil return content, 0, 0, nil
} }
// Apply modifications to each node // Apply modifications to each node
modCount := 0 modCount := 0
for _, node := range nodes { for i, node := range nodes {
logger.Trace("Processing node #%d: %s", i+1, node.Data)
L, err := NewLuaState() L, err := NewLuaState()
if err != nil { if err != nil {
logger.Error("Failed to create Lua state: %v", err)
return content, 0, 0, fmt.Errorf("error creating Lua state: %v", err) return content, 0, 0, fmt.Errorf("error creating Lua state: %v", err)
} }
defer L.Close() defer L.Close()
logger.Trace("Converting XML node to Lua")
err = p.ToLua(L, node) err = p.ToLua(L, node)
if err != nil { if err != nil {
logger.Error("Failed to convert XML node to Lua: %v", err)
return content, modCount, matchCount, fmt.Errorf("error converting to Lua: %v", err) return content, modCount, matchCount, fmt.Errorf("error converting to Lua: %v", err)
} }
err = L.DoString(BuildLuaScript(luaExpr)) luaScript := BuildLuaScript(luaExpr)
logger.Trace("Executing Lua script: %s", luaScript)
err = L.DoString(luaScript)
if err != nil { if err != nil {
logger.Error("Failed to execute Lua script: %v", err)
return content, modCount, matchCount, fmt.Errorf("error executing Lua: %v", err) return content, modCount, matchCount, fmt.Errorf("error executing Lua: %v", err)
} }
result, err := p.FromLua(L) result, err := p.FromLua(L)
if err != nil { if err != nil {
logger.Error("Failed to get result from Lua: %v", err)
return content, modCount, matchCount, fmt.Errorf("error getting result from Lua: %v", err) return content, modCount, matchCount, fmt.Errorf("error getting result from Lua: %v", err)
} }
log.Printf("%#v", result) logger.Trace("Lua returned result: %#v", result)
modified := false modified := false
modified = L.GetGlobal("modified").String() == "true" modified = L.GetGlobal("modified").String() == "true"
if !modified { if !modified {
log.Printf("No changes made to node at path: %s", node.Data) logger.Debug("No changes made to node at path: %s", node.Data)
continue continue
} }
// Apply modification based on the result // Apply modification based on the result
if updatedValue, ok := result.(string); ok { if updatedValue, ok := result.(string); ok {
// If the result is a simple string, update the node value directly // If the result is a simple string, update the node value directly
logger.Debug("Updating node with string value: %s", updatedValue)
xpath.Set(doc, path, updatedValue) xpath.Set(doc, path, updatedValue)
} else if nodeData, ok := result.(map[string]interface{}); ok { } else if nodeData, ok := result.(map[string]interface{}); ok {
// If the result is a map, apply more complex updates // If the result is a map, apply more complex updates
logger.Debug("Updating node with complex data structure")
updateNodeFromMap(node, nodeData) updateNodeFromMap(node, nodeData)
} }
modCount++ modCount++
logger.Debug("Successfully modified node #%d", i+1)
} }
logger.Info("XML processing complete: %d modifications from %d matches", modCount, matchCount)
// Serialize the modified XML document to string // Serialize the modified XML document to string
if doc.FirstChild != nil && doc.FirstChild.Type == xmlquery.DeclarationNode { if doc.FirstChild != nil && doc.FirstChild.Type == xmlquery.DeclarationNode {
// If we have an XML declaration, start with it // If we have an XML declaration, start with it