Hallucinate some logs

Hallucinate more logs

fix(utils/db.go): handle auto migration errors gracefully

fix(utils/modifycommand.go): improve file matching accuracy

fix(processor/regex.go): improve capture group deduplication logic

fix(utils/db.go): add logging for database wrapper initialization

feat(processor/regex.go): preserve input order when deduplicating capture groups

fix(utils/modifycommand.go): add logging for file matching skips

feat(processor/regex.go): add logging for capture group processing

feat(main.go): add trace logging for arguments and parallel workers

fix(main.go): add trace logging for file content

fix(utils/db.go): add logging for database opening

fix(main.go): add trace logging for file processing

fix(utils/modifycommand.go): improve file matching by using absolute paths

feat(modifycommand.go): add trace logging for file matching in AssociateFilesWithCommands

feat(main.go): add per-file association summary for better visibility when debugging
This commit is contained in:
2025-08-07 23:33:19 +02:00
parent 8ffd8af13c
commit 4b58e00c26
8 changed files with 702 additions and 328 deletions

View File

@@ -1,15 +1,17 @@
package utils
import (
"fmt"
"path/filepath"
"time"
"git.site.quack-lab.dev/dave/cylogger"
logger "git.site.quack-lab.dev/dave/cylogger"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// dbLogger is a scoped logger for the utils/db package.
var dbLogger = logger.Default.WithPrefix("utils/db")
type DB interface {
DB() *gorm.DB
Raw(sql string, args ...any) *gorm.DB
@@ -29,92 +31,125 @@ type DBWrapper struct {
db *gorm.DB
}
var db *DBWrapper
var globalDB *DBWrapper
func GetDB() (DB, error) {
getDBLogger := dbLogger.WithPrefix("GetDB")
getDBLogger.Debug("Attempting to get database connection")
var err error
dbFile := filepath.Join("data.sqlite")
getDBLogger.Debug("Opening database file: %q", dbFile)
db, err := gorm.Open(sqlite.Open(dbFile), &gorm.Config{
// SkipDefaultTransaction: true,
PrepareStmt: true,
// Logger: gormlogger.Default.LogMode(gormlogger.Silent),
})
if err != nil {
getDBLogger.Error("Failed to open database: %v", err)
return nil, err
}
db.AutoMigrate(&FileSnapshot{})
getDBLogger.Debug("Database opened successfully, running auto migration")
if err := db.AutoMigrate(&FileSnapshot{}); err != nil {
getDBLogger.Error("Auto migration failed: %v", err)
return nil, err
}
getDBLogger.Debug("Auto migration completed")
return &DBWrapper{db: db}, nil
globalDB = &DBWrapper{db: db}
getDBLogger.Debug("Database wrapper initialized")
return globalDB, nil
}
// Just a wrapper
func (db *DBWrapper) Raw(sql string, args ...any) *gorm.DB {
rawLogger := dbLogger.WithPrefix("Raw").WithField("sql", sql)
rawLogger.Debug("Executing raw SQL query with args: %v", args)
return db.db.Raw(sql, args...)
}
func (db *DBWrapper) DB() *gorm.DB {
dbLogger.WithPrefix("DB").Debug("Returning GORM DB instance")
return db.db
}
func (db *DBWrapper) FileExists(filePath string) (bool, error) {
fileExistsLogger := dbLogger.WithPrefix("FileExists").WithField("filePath", filePath)
fileExistsLogger.Debug("Checking if file exists in database")
var count int64
err := db.db.Model(&FileSnapshot{}).Where("file_path = ?", filePath).Count(&count).Error
if err != nil {
fileExistsLogger.Error("Error checking if file exists: %v", err)
return false, err
}
fileExistsLogger.Debug("File exists: %t", count > 0)
return count > 0, err
}
func (db *DBWrapper) SaveFile(filePath string, fileData []byte) error {
log := cylogger.Default.WithPrefix(fmt.Sprintf("SaveFile: %q", filePath))
saveFileLogger := dbLogger.WithPrefix("SaveFile").WithField("filePath", filePath)
saveFileLogger.Debug("Attempting to save file to database")
saveFileLogger.Trace("File data length: %d", len(fileData))
exists, err := db.FileExists(filePath)
if err != nil {
log.Error("Error checking if file exists: %v", err)
saveFileLogger.Error("Error checking if file exists: %v", err)
return err
}
log.Debug("File exists: %t", exists)
// Nothing to do, file already exists
if exists {
log.Debug("File already exists, skipping save")
saveFileLogger.Debug("File already exists, skipping save")
return nil
}
log.Debug("Saving file to database")
return db.db.Create(&FileSnapshot{
saveFileLogger.Debug("Creating new file snapshot in database")
err = db.db.Create(&FileSnapshot{
Date: time.Now(),
FilePath: filePath,
FileData: fileData,
}).Error
if err != nil {
saveFileLogger.Error("Failed to create file snapshot: %v", err)
} else {
saveFileLogger.Debug("File saved successfully to database")
}
return err
}
func (db *DBWrapper) GetFile(filePath string) ([]byte, error) {
log := cylogger.Default.WithPrefix(fmt.Sprintf("GetFile: %q", filePath))
log.Debug("Getting file from database")
getFileLogger := dbLogger.WithPrefix("GetFile").WithField("filePath", filePath)
getFileLogger.Debug("Getting file from database")
var fileSnapshot FileSnapshot
err := db.db.Model(&FileSnapshot{}).Where("file_path = ?", filePath).First(&fileSnapshot).Error
if err != nil {
getFileLogger.Error("Failed to get file from database: %v", err)
return nil, err
}
log.Debug("File found in database")
getFileLogger.Debug("File found in database")
getFileLogger.Trace("Retrieved file data length: %d", len(fileSnapshot.FileData))
return fileSnapshot.FileData, nil
}
func (db *DBWrapper) GetAllFiles() ([]FileSnapshot, error) {
log := cylogger.Default.WithPrefix("GetAllFiles")
log.Debug("Getting all files from database")
getAllFilesLogger := dbLogger.WithPrefix("GetAllFiles")
getAllFilesLogger.Debug("Getting all files from database")
var fileSnapshots []FileSnapshot
err := db.db.Model(&FileSnapshot{}).Find(&fileSnapshots).Error
if err != nil {
getAllFilesLogger.Error("Failed to get all files from database: %v", err)
return nil, err
}
log.Debug("Found %d files in database", len(fileSnapshots))
getAllFilesLogger.Debug("Found %d files in database", len(fileSnapshots))
getAllFilesLogger.Trace("File snapshots retrieved: %v", fileSnapshots)
return fileSnapshots, nil
}
func (db *DBWrapper) RemoveAllFiles() error {
log := cylogger.Default.WithPrefix("RemoveAllFiles")
log.Debug("Removing all files from database")
removeAllFilesLogger := dbLogger.WithPrefix("RemoveAllFiles")
removeAllFilesLogger.Debug("Removing all files from database")
err := db.db.Exec("DELETE FROM file_snapshots").Error
if err != nil {
return err
removeAllFilesLogger.Error("Failed to remove all files from database: %v", err)
} else {
removeAllFilesLogger.Debug("All files removed from database")
}
log.Debug("All files removed from database")
return nil
return err
}

View File

@@ -1,96 +1,142 @@
package utils
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"git.site.quack-lab.dev/dave/cylogger"
logger "git.site.quack-lab.dev/dave/cylogger"
)
// fileLogger is a scoped logger for the utils/file package.
var fileLogger = logger.Default.WithPrefix("utils/file")
func CleanPath(path string) string {
log := cylogger.Default.WithPrefix(fmt.Sprintf("CleanPath: %q", path))
log.Trace("Start")
cleanPathLogger := fileLogger.WithPrefix("CleanPath")
cleanPathLogger.Debug("Cleaning path: %q", path)
cleanPathLogger.Trace("Original path: %q", path)
path = filepath.Clean(path)
path = strings.ReplaceAll(path, "\\", "/")
log.Trace("Done: %q", path)
cleanPathLogger.Trace("Cleaned path result: %q", path)
return path
}
func ToAbs(path string) string {
log := cylogger.Default.WithPrefix(fmt.Sprintf("ToAbs: %q", path))
log.Trace("Start")
toAbsLogger := fileLogger.WithPrefix("ToAbs")
toAbsLogger.Debug("Converting path to absolute: %q", path)
toAbsLogger.Trace("Input path: %q", path)
if filepath.IsAbs(path) {
log.Trace("Path is already absolute: %q", path)
return CleanPath(path)
toAbsLogger.Debug("Path is already absolute, cleaning it.")
cleanedPath := CleanPath(path)
toAbsLogger.Trace("Already absolute path after cleaning: %q", cleanedPath)
return cleanedPath
}
cwd, err := os.Getwd()
if err != nil {
log.Error("Error getting cwd: %v", err)
toAbsLogger.Error("Error getting current working directory: %v", err)
return CleanPath(path)
}
log.Trace("Cwd: %q", cwd)
return CleanPath(filepath.Join(cwd, path))
toAbsLogger.Trace("Current working directory: %q", cwd)
cleanedPath := CleanPath(filepath.Join(cwd, path))
toAbsLogger.Trace("Converted absolute path result: %q", cleanedPath)
return cleanedPath
}
// LimitString truncates a string to maxLen and adds "..." if truncated
func LimitString(s string, maxLen int) string {
limitStringLogger := fileLogger.WithPrefix("LimitString").WithField("originalLength", len(s)).WithField("maxLength", maxLen)
limitStringLogger.Debug("Limiting string length")
s = strings.ReplaceAll(s, "\n", "\\n")
if len(s) <= maxLen {
limitStringLogger.Trace("String length (%d) is within max length (%d), no truncation", len(s), maxLen)
return s
}
limited := s[:maxLen-3] + "..."
limitStringLogger.Trace("String truncated from %d to %d characters: %q", len(s), len(limited), limited)
return limited
}
// StrToFloat converts a string to a float64, returning 0 on error.
func StrToFloat(s string) float64 {
strToFloatLogger := fileLogger.WithPrefix("StrToFloat").WithField("inputString", s)
strToFloatLogger.Debug("Attempting to convert string to float")
f, err := strconv.ParseFloat(s, 64)
if err != nil {
strToFloatLogger.Warning("Failed to convert string %q to float, returning 0: %v", s, err)
return 0
}
strToFloatLogger.Trace("Successfully converted %q to float: %f", s, f)
return f
}
func ResetWhereNecessary(associations map[string]FileCommandAssociation, db DB) error {
log := cylogger.Default.WithPrefix("ResetWhereNecessary")
log.Debug("Start")
resetWhereNecessaryLogger := fileLogger.WithPrefix("ResetWhereNecessary")
resetWhereNecessaryLogger.Debug("Starting reset where necessary operation")
resetWhereNecessaryLogger.Trace("File-command associations input: %v", associations)
dirtyFiles := make(map[string]struct{})
for _, association := range associations {
resetWhereNecessaryLogger.Debug("Processing association for file: %q", association.File)
for _, command := range association.Commands {
log.Debug("Checking command %q for file %q", command.Name, association.File)
resetWhereNecessaryLogger.Debug("Checking command %q for reset requirement", command.Name)
resetWhereNecessaryLogger.Trace("Command details: %v", command)
if command.Reset {
log.Debug("Command %q requires reset for file %q", command.Name, association.File)
resetWhereNecessaryLogger.Debug("Command %q requires reset for file %q, marking as dirty", command.Name, association.File)
dirtyFiles[association.File] = struct{}{}
}
}
for _, command := range association.IsolateCommands {
log.Debug("Checking isolate command %q for file %q", command.Name, association.File)
resetWhereNecessaryLogger.Debug("Checking isolate command %q for reset requirement", command.Name)
resetWhereNecessaryLogger.Trace("Isolate command details: %v", command)
if command.Reset {
log.Debug("Isolate command %q requires reset for file %q", command.Name, association.File)
resetWhereNecessaryLogger.Debug("Isolate command %q requires reset for file %q, marking as dirty", command.Name, association.File)
dirtyFiles[association.File] = struct{}{}
}
}
}
log.Debug("Dirty files: %v", dirtyFiles)
resetWhereNecessaryLogger.Debug("Identified %d files that need to be reset", len(dirtyFiles))
resetWhereNecessaryLogger.Trace("Dirty files identified: %v", dirtyFiles)
for file := range dirtyFiles {
log.Debug("Resetting file %q", file)
resetWhereNecessaryLogger.Debug("Resetting file %q", file)
fileData, err := db.GetFile(file)
if err != nil {
log.Warning("Failed to get file %q: %v", file, err)
resetWhereNecessaryLogger.Warning("Failed to get original content for file %q from database: %v", file, err)
continue
}
log.Debug("Writing file %q to disk", file)
resetWhereNecessaryLogger.Trace("Retrieved original file data length for %q: %d", file, len(fileData))
resetWhereNecessaryLogger.Debug("Writing original content back to file %q", file)
err = os.WriteFile(file, fileData, 0644)
if err != nil {
log.Warning("Failed to write file %q: %v", file, err)
resetWhereNecessaryLogger.Warning("Failed to write original content back to file %q: %v", file, err)
continue
}
log.Debug("File %q written to disk", file)
resetWhereNecessaryLogger.Debug("Successfully reset file %q", file)
}
log.Debug("Done")
resetWhereNecessaryLogger.Debug("Finished reset where necessary operation")
return nil
}
func ResetAllFiles(db DB) error {
log := cylogger.Default.WithPrefix("ResetAllFiles")
log.Debug("Start")
resetAllFilesLogger := fileLogger.WithPrefix("ResetAllFiles")
resetAllFilesLogger.Debug("Starting reset all files operation")
fileSnapshots, err := db.GetAllFiles()
if err != nil {
resetAllFilesLogger.Error("Failed to get all file snapshots from database: %v", err)
return err
}
log.Debug("Found %d files in database", len(fileSnapshots))
resetAllFilesLogger.Debug("Found %d files in database to reset", len(fileSnapshots))
resetAllFilesLogger.Trace("File snapshots retrieved: %v", fileSnapshots)
for _, fileSnapshot := range fileSnapshots {
log.Debug("Resetting file %q", fileSnapshot.FilePath)
resetAllFilesLogger.Debug("Resetting file %q", fileSnapshot.FilePath)
err = os.WriteFile(fileSnapshot.FilePath, fileSnapshot.FileData, 0644)
if err != nil {
log.Warning("Failed to write file %q: %v", fileSnapshot.FilePath, err)
resetAllFilesLogger.Warning("Failed to write file %q to disk: %v", fileSnapshot.FilePath, err)
continue
}
log.Debug("File %q written to disk", fileSnapshot.FilePath)
resetAllFilesLogger.Debug("File %q written to disk successfully", fileSnapshot.FilePath)
}
log.Debug("Done")
resetAllFilesLogger.Debug("Finished reset all files operation")
return nil
}

View File

@@ -2,9 +2,19 @@ package utils
import (
"flag"
logger "git.site.quack-lab.dev/dave/cylogger"
)
// flagsLogger is a scoped logger for the utils/flags package.
var flagsLogger = logger.Default.WithPrefix("utils/flags")
var (
ParallelFiles = flag.Int("P", 100, "Number of files to process in parallel")
Filter = flag.String("f", "", "Filter commands before running them")
)
func init() {
flagsLogger.Debug("Initializing flags")
flagsLogger.Trace("ParallelFiles initial value: %d, Filter initial value: %q", *ParallelFiles, *Filter)
}

View File

@@ -11,6 +11,9 @@ import (
"gopkg.in/yaml.v3"
)
// modifyCommandLogger is a scoped logger for the utils/modifycommand package.
var modifyCommandLogger = logger.Default.WithPrefix("utils/modifycommand")
type ModifyCommand struct {
Name string `yaml:"name"`
Regex string `yaml:"regex"`
@@ -22,21 +25,29 @@ type ModifyCommand struct {
NoDedup bool `yaml:"nodedup"`
Disabled bool `yaml:"disable"`
}
type CookFile []ModifyCommand
func (c *ModifyCommand) Validate() error {
validateLogger := modifyCommandLogger.WithPrefix("Validate").WithField("commandName", c.Name)
validateLogger.Debug("Validating command")
if c.Regex == "" {
validateLogger.Error("Validation failed: Regex pattern is required")
return fmt.Errorf("pattern is required")
}
if c.Lua == "" {
validateLogger.Error("Validation failed: Lua expression is required")
return fmt.Errorf("lua expression is required")
}
if len(c.Files) == 0 {
validateLogger.Error("Validation failed: At least one file is required")
return fmt.Errorf("at least one file is required")
}
if c.LogLevel == "" {
validateLogger.Debug("LogLevel not specified, defaulting to INFO")
c.LogLevel = "INFO"
}
validateLogger.Debug("Command validated successfully")
return nil
}
@@ -44,34 +55,47 @@ func (c *ModifyCommand) Validate() error {
var matchesMemoTable map[string]bool = make(map[string]bool)
func Matches(path string, glob string) (bool, error) {
matchesLogger := modifyCommandLogger.WithPrefix("Matches").WithField("path", path).WithField("glob", glob)
matchesLogger.Debug("Checking if path matches glob")
key := fmt.Sprintf("%s:%s", path, glob)
if matches, ok := matchesMemoTable[key]; ok {
logger.Debug("Found match for file %q and glob %q in memo table", path, glob)
matchesLogger.Debug("Found match in memo table: %t", matches)
return matches, nil
}
matches, err := doublestar.Match(glob, path)
if err != nil {
matchesLogger.Error("Failed to match glob: %v", err)
return false, fmt.Errorf("failed to match glob %s with file %s: %w", glob, path, err)
}
matchesMemoTable[key] = matches
matchesLogger.Debug("Match result: %t, storing in memo table", matches)
return matches, nil
}
func SplitPattern(pattern string) (string, string) {
splitPatternLogger := modifyCommandLogger.WithPrefix("SplitPattern").WithField("pattern", pattern)
splitPatternLogger.Debug("Splitting pattern")
splitPatternLogger.Trace("Original pattern: %q", pattern)
static, pattern := doublestar.SplitPattern(pattern)
cwd, err := os.Getwd()
if err != nil {
splitPatternLogger.Error("Error getting current working directory: %v", err)
return "", ""
}
splitPatternLogger.Trace("Current working directory: %q", cwd)
if static == "" {
splitPatternLogger.Debug("Static part is empty, defaulting to current working directory")
static = cwd
}
if !filepath.IsAbs(static) {
splitPatternLogger.Debug("Static part is not absolute, joining with current working directory")
static = filepath.Join(cwd, static)
static = filepath.Clean(static)
splitPatternLogger.Trace("Static path after joining and cleaning: %q", static)
}
static = strings.ReplaceAll(static, "\\", "/")
splitPatternLogger.Trace("Final static path: %q, Remaining pattern: %q", static, pattern)
return static, pattern
}
@@ -82,180 +106,262 @@ type FileCommandAssociation struct {
}
func AssociateFilesWithCommands(files []string, commands []ModifyCommand) (map[string]FileCommandAssociation, error) {
associateFilesLogger := modifyCommandLogger.WithPrefix("AssociateFilesWithCommands")
associateFilesLogger.Debug("Associating files with commands")
associateFilesLogger.Trace("Input files: %v", files)
associateFilesLogger.Trace("Input commands: %v", commands)
associationCount := 0
fileCommands := make(map[string]FileCommandAssociation)
for _, file := range files {
file = strings.ReplaceAll(file, "\\", "/")
associateFilesLogger.Debug("Processing file: %q", file)
fileCommands[file] = FileCommandAssociation{
File: file,
IsolateCommands: []ModifyCommand{},
Commands: []ModifyCommand{},
}
for _, command := range commands {
associateFilesLogger.Debug("Checking command %q for file %q", command.Name, file)
for _, glob := range command.Files {
glob = strings.ReplaceAll(glob, "\\", "/")
static, pattern := SplitPattern(glob)
patternFile := strings.Replace(file, static+`/`, "", 1)
associateFilesLogger.Trace("Glob parts for %q → static=%q pattern=%q", glob, static, pattern)
// Build absolute path for the current file to compare with static
cwd, err := os.Getwd()
if err != nil {
associateFilesLogger.Warning("Failed to get CWD when matching %q for file %q: %v", glob, file, err)
continue
}
var absFile string
if filepath.IsAbs(file) {
absFile = filepath.Clean(file)
} else {
absFile = filepath.Clean(filepath.Join(cwd, file))
}
absFile = strings.ReplaceAll(absFile, "\\", "/")
associateFilesLogger.Trace("Absolute file path resolved for matching: %q", absFile)
// Only match if the file is under the static root
if !(strings.HasPrefix(absFile, static+"/") || absFile == static) {
associateFilesLogger.Trace("Skipping glob %q for file %q because file is outside static root %q", glob, file, static)
continue
}
patternFile := strings.TrimPrefix(absFile, static+`/`)
associateFilesLogger.Trace("Pattern-relative path used for match: %q", patternFile)
matches, err := Matches(patternFile, pattern)
if err != nil {
logger.Trace("Failed to match glob %s with file %s: %v", glob, file, err)
associateFilesLogger.Warning("Failed to match glob %q with file %q: %v", glob, file, err)
continue
}
if matches {
logger.Debug("Found match for file %q and command %q", file, command.Regex)
associateFilesLogger.Debug("File %q matches glob %q. Associating with command %q", file, glob, command.Name)
association := fileCommands[file]
if command.Isolate {
associateFilesLogger.Debug("Command %q is an isolate command, adding to isolate list", command.Name)
association.IsolateCommands = append(association.IsolateCommands, command)
} else {
associateFilesLogger.Debug("Command %q is a regular command, adding to regular list", command.Name)
association.Commands = append(association.Commands, command)
}
fileCommands[file] = association
associationCount++
} else {
associateFilesLogger.Trace("File %q did not match glob %q (pattern=%q, rel=%q)", file, glob, pattern, patternFile)
}
}
}
logger.Debug("Found %d commands for file %q", len(fileCommands[file].Commands), file)
if len(fileCommands[file].Commands) == 0 {
logger.Info("No commands found for file %q", file)
}
if len(fileCommands[file].IsolateCommands) > 0 {
logger.Info("Found %d isolate commands for file %q", len(fileCommands[file].IsolateCommands), file)
}
currentFileCommands := fileCommands[file]
associateFilesLogger.Debug("Finished processing file %q. Found %d regular commands and %d isolate commands", file, len(currentFileCommands.Commands), len(currentFileCommands.IsolateCommands))
associateFilesLogger.Trace("Commands for file %q: %v", file, currentFileCommands.Commands)
associateFilesLogger.Trace("Isolate commands for file %q: %v", file, currentFileCommands.IsolateCommands)
}
logger.Info("Found %d associations between %d files and %d commands", associationCount, len(files), len(commands))
associateFilesLogger.Info("Completed association. Found %d total associations for %d files and %d commands", associationCount, len(files), len(commands))
return fileCommands, nil
}
func AggregateGlobs(commands []ModifyCommand) map[string]struct{} {
logger.Info("Aggregating globs for %d commands", len(commands))
aggregateGlobsLogger := modifyCommandLogger.WithPrefix("AggregateGlobs")
aggregateGlobsLogger.Debug("Aggregating glob patterns from commands")
aggregateGlobsLogger.Trace("Input commands for aggregation: %v", commands)
globs := make(map[string]struct{})
for _, command := range commands {
aggregateGlobsLogger.Debug("Processing command %q for glob patterns", command.Name)
for _, glob := range command.Files {
glob = strings.Replace(glob, "~", os.Getenv("HOME"), 1)
glob = strings.ReplaceAll(glob, "\\", "/")
globs[glob] = struct{}{}
resolvedGlob := strings.Replace(glob, "~", os.Getenv("HOME"), 1)
resolvedGlob = strings.ReplaceAll(resolvedGlob, "\\", "/")
aggregateGlobsLogger.Trace("Adding glob: %q (resolved to %q)", glob, resolvedGlob)
globs[resolvedGlob] = struct{}{}
}
}
logger.Info("Found %d unique globs", len(globs))
aggregateGlobsLogger.Debug("Finished aggregating globs. Found %d unique glob patterns", len(globs))
aggregateGlobsLogger.Trace("Aggregated unique globs: %v", globs)
return globs
}
func ExpandGLobs(patterns map[string]struct{}) ([]string, error) {
expandGlobsLogger := modifyCommandLogger.WithPrefix("ExpandGLobs")
expandGlobsLogger.Debug("Expanding glob patterns to actual files")
expandGlobsLogger.Trace("Input patterns for expansion: %v", patterns)
var files []string
filesMap := make(map[string]bool)
cwd, err := os.Getwd()
if err != nil {
expandGlobsLogger.Error("Failed to get current working directory: %v", err)
return nil, fmt.Errorf("failed to get current working directory: %w", err)
}
expandGlobsLogger.Debug("Current working directory: %q", cwd)
logger.Debug("Expanding patterns from directory: %s", cwd)
for pattern := range patterns {
logger.Trace("Processing pattern: %s", pattern)
expandGlobsLogger.Debug("Processing glob pattern: %q", pattern)
static, pattern := SplitPattern(pattern)
matches, _ := doublestar.Glob(os.DirFS(static), pattern)
logger.Debug("Found %d matches for pattern %s", len(matches), pattern)
matches, err := doublestar.Glob(os.DirFS(static), pattern)
if err != nil {
expandGlobsLogger.Warning("Error expanding glob %q in %q: %v", pattern, static, err)
continue
}
expandGlobsLogger.Debug("Found %d matches for pattern %q", len(matches), pattern)
expandGlobsLogger.Trace("Raw matches for pattern %q: %v", pattern, matches)
for _, m := range matches {
m = filepath.Join(static, m)
info, err := os.Stat(m)
if err != nil {
logger.Warning("Error getting file info for %s: %v", m, err)
expandGlobsLogger.Warning("Error getting file info for %q: %v", m, err)
continue
}
if !info.IsDir() && !filesMap[m] {
logger.Trace("Adding file to process list: %s", m)
expandGlobsLogger.Trace("Adding unique file to list: %q", m)
filesMap[m], files = true, append(files, m)
}
}
}
if len(files) > 0 {
logger.Debug("Found %d files to process: %v", len(files), files)
expandGlobsLogger.Debug("Finished expanding globs. Found %d unique files to process", len(files))
expandGlobsLogger.Trace("Unique files to process: %v", files)
} else {
expandGlobsLogger.Warning("No files found after expanding glob patterns.")
}
return files, nil
}
func LoadCommands(args []string) ([]ModifyCommand, error) {
loadCommandsLogger := modifyCommandLogger.WithPrefix("LoadCommands")
loadCommandsLogger.Debug("Loading commands from arguments (cook files or direct patterns)")
loadCommandsLogger.Trace("Input arguments: %v", args)
commands := []ModifyCommand{}
logger.Info("Loading commands from cook files: %s", args)
for _, arg := range args {
newcommands, err := LoadCommandsFromCookFiles(arg)
loadCommandsLogger.Debug("Processing argument for commands: %q", arg)
newCommands, err := LoadCommandsFromCookFiles(arg)
if err != nil {
loadCommandsLogger.Error("Failed to load commands from argument %q: %v", arg, err)
return nil, fmt.Errorf("failed to load commands from cook files: %w", err)
}
logger.Info("Successfully loaded %d commands from cook files", len(newcommands))
for _, cmd := range newcommands {
loadCommandsLogger.Debug("Successfully loaded %d commands from %q", len(newCommands), arg)
for _, cmd := range newCommands {
if cmd.Disabled {
logger.Info("Skipping disabled command: %s", cmd.Name)
loadCommandsLogger.Debug("Skipping disabled command: %q", cmd.Name)
continue
}
commands = append(commands, cmd)
loadCommandsLogger.Trace("Added command %q. Current total commands: %d", cmd.Name, len(commands))
}
logger.Info("Now total commands: %d", len(commands))
}
logger.Info("Loaded %d commands from all cook file", len(commands))
loadCommandsLogger.Info("Finished loading commands. Total %d commands loaded", len(commands))
return commands, nil
}
func LoadCommandsFromCookFiles(pattern string) ([]ModifyCommand, error) {
loadCookFilesLogger := modifyCommandLogger.WithPrefix("LoadCommandsFromCookFiles").WithField("pattern", pattern)
loadCookFilesLogger.Debug("Loading commands from cook files based on pattern")
loadCookFilesLogger.Trace("Input pattern: %q", pattern)
static, pattern := SplitPattern(pattern)
commands := []ModifyCommand{}
cookFiles, err := doublestar.Glob(os.DirFS(static), pattern)
if err != nil {
loadCookFilesLogger.Error("Failed to glob cook files for pattern %q: %v", pattern, err)
return nil, fmt.Errorf("failed to glob cook files: %w", err)
}
loadCookFilesLogger.Debug("Found %d cook files for pattern %q", len(cookFiles), pattern)
loadCookFilesLogger.Trace("Cook files found: %v", cookFiles)
for _, cookFile := range cookFiles {
cookFile = filepath.Join(static, cookFile)
cookFile = filepath.Clean(cookFile)
cookFile = strings.ReplaceAll(cookFile, "\\", "/")
logger.Info("Loading commands from cook file: %s", cookFile)
loadCookFilesLogger.Debug("Loading commands from individual cook file: %q", cookFile)
cookFileData, err := os.ReadFile(cookFile)
if err != nil {
loadCookFilesLogger.Error("Failed to read cook file %q: %v", cookFile, err)
return nil, fmt.Errorf("failed to read cook file: %w", err)
}
newcommands, err := LoadCommandsFromCookFile(cookFileData)
loadCookFilesLogger.Trace("Read %d bytes from cook file %q", len(cookFileData), cookFile)
newCommands, err := LoadCommandsFromCookFile(cookFileData)
if err != nil {
loadCookFilesLogger.Error("Failed to load commands from cook file data for %q: %v", cookFile, err)
return nil, fmt.Errorf("failed to load commands from cook file: %w", err)
}
commands = append(commands, newcommands...)
commands = append(commands, newCommands...)
loadCookFilesLogger.Debug("Added %d commands from cook file %q. Total commands now: %d", len(newCommands), cookFile, len(commands))
}
loadCookFilesLogger.Debug("Finished loading commands from cook files. Total %d commands", len(commands))
return commands, nil
}
func LoadCommandsFromCookFile(cookFileData []byte) ([]ModifyCommand, error) {
loadCommandLogger := modifyCommandLogger.WithPrefix("LoadCommandsFromCookFile")
loadCommandLogger.Debug("Unmarshaling commands from cook file data")
loadCommandLogger.Trace("Cook file data length: %d", len(cookFileData))
commands := []ModifyCommand{}
err := yaml.Unmarshal(cookFileData, &commands)
if err != nil {
loadCommandLogger.Error("Failed to unmarshal cook file data: %v", err)
return nil, fmt.Errorf("failed to unmarshal cook file: %w", err)
}
loadCommandLogger.Debug("Successfully unmarshaled %d commands", len(commands))
loadCommandLogger.Trace("Unmarshaled commands: %v", commands)
return commands, nil
}
// CountGlobsBeforeDedup counts the total number of glob patterns across all commands before deduplication
func CountGlobsBeforeDedup(commands []ModifyCommand) int {
countGlobsLogger := modifyCommandLogger.WithPrefix("CountGlobsBeforeDedup")
countGlobsLogger.Debug("Counting glob patterns before deduplication")
count := 0
for _, cmd := range commands {
countGlobsLogger.Trace("Processing command %q, adding %d globs", cmd.Name, len(cmd.Files))
count += len(cmd.Files)
}
countGlobsLogger.Debug("Total glob patterns before deduplication: %d", count)
return count
}
func FilterCommands(commands []ModifyCommand, filter string) []ModifyCommand {
filterCommandsLogger := modifyCommandLogger.WithPrefix("FilterCommands").WithField("filter", filter)
filterCommandsLogger.Debug("Filtering commands")
filterCommandsLogger.Trace("Input commands: %v", commands)
filteredCommands := []ModifyCommand{}
filters := strings.Split(filter, ",")
filterCommandsLogger.Trace("Split filters: %v", filters)
for _, cmd := range commands {
for _, filter := range filters {
if strings.Contains(cmd.Name, filter) {
filterCommandsLogger.Debug("Checking command %q against filters", cmd.Name)
for _, f := range filters {
if strings.Contains(cmd.Name, f) {
filterCommandsLogger.Debug("Command %q matches filter %q, adding to filtered list", cmd.Name, f)
filteredCommands = append(filteredCommands, cmd)
break // Command matches, no need to check other filters
}
}
}
filterCommandsLogger.Debug("Finished filtering commands. Found %d filtered commands", len(filteredCommands))
filterCommandsLogger.Trace("Filtered commands: %v", filteredCommands)
return filteredCommands
}

View File

@@ -7,6 +7,9 @@ import (
logger "git.site.quack-lab.dev/dave/cylogger"
)
// replaceCommandLogger is a scoped logger for the utils/replacecommand package.
var replaceCommandLogger = logger.Default.WithPrefix("utils/replacecommand")
type ReplaceCommand struct {
From int
To int
@@ -14,45 +17,63 @@ type ReplaceCommand struct {
}
func ExecuteModifications(modifications []ReplaceCommand, fileData string) (string, int) {
executeModificationsLogger := replaceCommandLogger.WithPrefix("ExecuteModifications")
executeModificationsLogger.Debug("Executing a batch of text modifications")
executeModificationsLogger.Trace("Number of modifications: %d, Original file data length: %d", len(modifications), len(fileData))
var err error
sort.Slice(modifications, func(i, j int) bool {
return modifications[i].From > modifications[j].From
})
logger.Trace("Preparing to apply %d replacement commands in reverse order", len(modifications))
executeModificationsLogger.Debug("Modifications sorted in reverse order for safe replacement")
executeModificationsLogger.Trace("Sorted modifications: %v", modifications)
executed := 0
for _, modification := range modifications {
for idx, modification := range modifications {
executeModificationsLogger.Debug("Applying modification %d/%d", idx+1, len(modifications))
executeModificationsLogger.Trace("Current modification details: From=%d, To=%d, With=%q", modification.From, modification.To, modification.With)
fileData, err = modification.Execute(fileData)
if err != nil {
logger.Error("Failed to execute replacement: %v", err)
executeModificationsLogger.Error("Failed to execute replacement for modification %+v: %v", modification, err)
continue
}
executed++
executeModificationsLogger.Trace("File data length after modification: %d", len(fileData))
}
logger.Info("Successfully applied %d text replacements", executed)
executeModificationsLogger.Info("Successfully applied %d text replacements", executed)
return fileData, executed
}
func (m *ReplaceCommand) Execute(fileDataStr string) (string, error) {
executeLogger := replaceCommandLogger.WithPrefix("Execute").WithField("modification", fmt.Sprintf("From:%d,To:%d,With:%q", m.From, m.To, m.With))
executeLogger.Debug("Attempting to execute single replacement")
err := m.Validate(len(fileDataStr))
if err != nil {
executeLogger.Error("Failed to validate modification: %v", err)
return fileDataStr, fmt.Errorf("failed to validate modification: %v", err)
}
logger.Trace("Replace pos %d-%d with %q", m.From, m.To, m.With)
return fileDataStr[:m.From] + m.With + fileDataStr[m.To:], nil
executeLogger.Trace("Applying replacement: fileDataStr[:%d] + %q + fileDataStr[%d:]", m.From, m.With, m.To)
result := fileDataStr[:m.From] + m.With + fileDataStr[m.To:]
executeLogger.Trace("Replacement executed. Result length: %d", len(result))
return result, nil
}
func (m *ReplaceCommand) Validate(maxsize int) error {
validateLogger := replaceCommandLogger.WithPrefix("Validate").WithField("modification", fmt.Sprintf("From:%d,To:%d,With:%q", m.From, m.To, m.With)).WithField("maxSize", maxsize)
validateLogger.Debug("Validating replacement command against max size")
if m.To < m.From {
validateLogger.Error("Validation failed: 'To' (%d) is less than 'From' (%d)", m.To, m.From)
return fmt.Errorf("command to is less than from: %v", m)
}
if m.From > maxsize || m.To > maxsize {
validateLogger.Error("Validation failed: 'From' (%d) or 'To' (%d) is greater than max size (%d)", m.From, m.To, maxsize)
return fmt.Errorf("command from or to is greater than replacement length: %v", m)
}
if m.From < 0 || m.To < 0 {
validateLogger.Error("Validation failed: 'From' (%d) or 'To' (%d) is less than 0", m.From, m.To)
return fmt.Errorf("command from or to is less than 0: %v", m)
}
validateLogger.Debug("Modification command validated successfully")
return nil
}