4 Commits

4 changed files with 343 additions and 16 deletions

View File

@@ -55,7 +55,10 @@ Features:
- Parallel file processing
- Command filtering and organization`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
logger.InitFlag()
logLevelStr, _ := cmd.Flags().GetString("loglevel")
logLevel := logger.ParseLevel(logLevelStr)
logger.SetLevel(logLevel)
mainLogger.SetLevel(logLevel)
mainLogger.Info("Initializing with log level: %s", logger.GetLevel().String())
mainLogger.Trace("Full argv: %v", os.Args)
},

View File

@@ -223,7 +223,7 @@ func ProcessRegex(content string, command utils.ModifyCommand, filename string)
modifiedGroupsCount++
}
}
matchLogger.Info("%d of %d capture groups identified for modification", modifiedGroupsCount, len(updatedCaptureGroups))
matchLogger.Debug("%d of %d capture groups identified for modification", modifiedGroupsCount, len(updatedCaptureGroups))
for _, capture := range updatedCaptureGroups {
if capture.Value == capture.Updated {

View File

@@ -91,10 +91,10 @@ func SplitPattern(pattern string) (string, string) {
static, remainingPattern := doublestar.SplitPattern(pattern)
splitPatternLogger.Trace("After split: static=%q, pattern=%q", static, remainingPattern)
// Resolve the static part to handle ~ expansion and make it absolute
// ResolvePath already normalizes to forward slashes
static = ResolvePath(static)
splitPatternLogger.Trace("Resolved static part: %q", static)
// Normalize to forward slashes but DON'T resolve relative to CWD
// Paths should already be resolved by the caller (AggregateGlobs, etc.)
static = filepath.ToSlash(static)
splitPatternLogger.Trace("Normalized static part: %q", static)
splitPatternLogger.Trace("Final static path: %q, Remaining pattern: %q", static, remainingPattern)
return static, remainingPattern
@@ -126,9 +126,22 @@ func AssociateFilesWithCommands(files []string, commands []ModifyCommand) (map[s
for _, command := range commands {
associateFilesLogger.Debug("Checking command %q for file %q", command.Name, file)
for _, glob := range command.Files {
// SplitPattern now handles tilde expansion and path resolution
static, pattern := SplitPattern(glob)
associateFilesLogger.Trace("Glob parts for %q → static=%q pattern=%q", glob, static, pattern)
// Resolve glob relative to SourceDir if it's a relative path
var resolvedGlob string
if !filepath.IsAbs(glob) && command.SourceDir != "" {
resolvedGlob = filepath.Join(command.SourceDir, glob)
associateFilesLogger.Trace("Joined relative glob %q to %q using SourceDir %q", glob, resolvedGlob, command.SourceDir)
} else {
resolvedGlob = glob
}
// Make absolute and normalize
resolvedGlob = ResolvePath(resolvedGlob)
associateFilesLogger.Trace("Final resolved glob: %q", resolvedGlob)
// SplitPattern just splits, doesn't resolve
static, pattern := SplitPattern(resolvedGlob)
associateFilesLogger.Trace("Glob parts for %q → static=%q pattern=%q", resolvedGlob, static, pattern)
// Use resolved file for matching (already normalized to forward slashes by ResolvePath)
absFile := resolvedFile
@@ -181,15 +194,23 @@ func AggregateGlobs(commands []ModifyCommand) map[string]struct{} {
globs := make(map[string]struct{})
for _, command := range commands {
aggregateGlobsLogger.Debug("Processing command %q for glob patterns", command.Name)
aggregateGlobsLogger.Trace("Command SourceDir: %q", command.SourceDir)
for _, glob := range command.Files {
// Split the glob into static and pattern parts, then resolve ONLY the static part
static, pattern := SplitPattern(glob)
// Reconstruct the glob with resolved static part
resolvedGlob := static
if pattern != "" {
resolvedGlob += "/" + pattern
// If the glob is relative and we have a SourceDir, resolve relative to SourceDir
var resolvedGlob string
if !filepath.IsAbs(glob) && command.SourceDir != "" {
// Relative path - resolve relative to the TOML file's directory
resolvedGlob = filepath.Join(command.SourceDir, glob)
aggregateGlobsLogger.Trace("Joined relative glob %q to %q using SourceDir %q", glob, resolvedGlob, command.SourceDir)
} else {
// Absolute path or no SourceDir - use as-is
resolvedGlob = glob
}
aggregateGlobsLogger.Trace("Adding glob: %q (resolved to %q) [static=%s, pattern=%s]", glob, resolvedGlob, static, pattern)
// Make absolute and normalize (ResolvePath handles both)
resolvedGlob = ResolvePath(resolvedGlob)
aggregateGlobsLogger.Trace("Final resolved glob: %q", resolvedGlob)
globs[resolvedGlob] = struct{}{}
}
}

View File

@@ -0,0 +1,303 @@
package utils
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAggregateGlobsWithSourceDir(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "aggregate-globs-test-*")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
// Create a subdirectory structure
subDir := filepath.Join(tmpDir, "subdir")
err = os.MkdirAll(subDir, 0755)
assert.NoError(t, err)
// Create test files
testFile := filepath.Join(subDir, "test.xml")
err = os.WriteFile(testFile, []byte("<test/>"), 0644)
assert.NoError(t, err)
commands := []ModifyCommand{
{
Name: "test1",
Files: []string{"subdir/*.xml"},
SourceDir: tmpDir,
},
{
Name: "test2",
Files: []string{"*.txt"},
SourceDir: tmpDir,
},
}
globs := AggregateGlobs(commands)
// Both should be resolved relative to tmpDir
expectedSubdir := ResolvePath(filepath.Join(tmpDir, "subdir/*.xml"))
expectedTxt := ResolvePath(filepath.Join(tmpDir, "*.txt"))
assert.Contains(t, globs, expectedSubdir, "Should contain resolved subdir glob")
assert.Contains(t, globs, expectedTxt, "Should contain resolved txt glob")
assert.Len(t, globs, 2, "Should have 2 unique globs")
}
func TestAggregateGlobsWithAbsolutePaths(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "aggregate-globs-abs-test-*")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
absPath := ResolvePath(tmpDir)
commands := []ModifyCommand{
{
Name: "test1",
Files: []string{absPath + "/*.xml"},
SourceDir: tmpDir, // SourceDir should be ignored for absolute paths
},
}
globs := AggregateGlobs(commands)
// Absolute path should be used as-is (after ResolvePath normalization)
expected := ResolvePath(absPath + "/*.xml")
assert.Contains(t, globs, expected, "Should contain absolute path glob")
assert.Len(t, globs, 1, "Should have 1 glob")
}
func TestAggregateGlobsWithoutSourceDir(t *testing.T) {
cwd, err := os.Getwd()
assert.NoError(t, err)
commands := []ModifyCommand{
{
Name: "test1",
Files: []string{"*.xml"},
SourceDir: "", // No SourceDir
},
}
globs := AggregateGlobs(commands)
// Without SourceDir, should resolve relative to CWD
expected := ResolvePath(filepath.Join(cwd, "*.xml"))
assert.Contains(t, globs, expected, "Should resolve relative to CWD when SourceDir is empty")
}
func TestAggregateGlobsConsistentRegardlessOfCWD(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "aggregate-globs-cwd-test-*")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
// Create test structure
testFile := filepath.Join(tmpDir, "test.xml")
err = os.WriteFile(testFile, []byte("<test/>"), 0644)
assert.NoError(t, err)
commands := []ModifyCommand{
{
Name: "test",
Files: []string{"test.xml"},
SourceDir: tmpDir,
},
}
// Get original CWD
originalCwd, err := os.Getwd()
assert.NoError(t, err)
defer os.Chdir(originalCwd)
// Test from original directory
globs1 := AggregateGlobs(commands)
expected1 := ResolvePath(filepath.Join(tmpDir, "test.xml"))
// Change to tmpDir
err = os.Chdir(tmpDir)
assert.NoError(t, err)
// Test from tmpDir - should produce same result
globs2 := AggregateGlobs(commands)
expected2 := ResolvePath(filepath.Join(tmpDir, "test.xml"))
// Both should resolve to the same absolute path
assert.Equal(t, expected1, expected2, "Paths should be identical regardless of CWD")
assert.Contains(t, globs1, expected1, "First run should contain expected path")
assert.Contains(t, globs2, expected2, "Second run should contain expected path")
assert.Equal(t, globs1, globs2, "Globs should be identical regardless of CWD")
}
func TestAssociateFilesWithCommandsSourceDir(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "associate-files-test-*")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
// Create test structure
subDir := filepath.Join(tmpDir, "data")
err = os.MkdirAll(subDir, 0755)
assert.NoError(t, err)
testFile := filepath.Join(subDir, "test.xml")
err = os.WriteFile(testFile, []byte("<test/>"), 0644)
assert.NoError(t, err)
commands := []ModifyCommand{
{
Name: "test",
Regex: "pattern",
Lua: "expr",
Files: []string{"data/test.xml"},
SourceDir: tmpDir,
},
}
files := []string{testFile}
associations, err := AssociateFilesWithCommands(files, commands)
assert.NoError(t, err)
// File should be associated with command
assert.Contains(t, associations, testFile, "File should be in associations")
association := associations[testFile]
assert.Len(t, association.Commands, 1, "Should have 1 command")
assert.Equal(t, "test", association.Commands[0].Name, "Command name should match")
}
func TestAssociateFilesWithCommandsAbsolutePath(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "associate-files-abs-test-*")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
testFile := filepath.Join(tmpDir, "test.xml")
err = os.WriteFile(testFile, []byte("<test/>"), 0644)
assert.NoError(t, err)
absPath := ResolvePath(testFile)
commands := []ModifyCommand{
{
Name: "test",
Regex: "pattern",
Lua: "expr",
Files: []string{absPath},
SourceDir: tmpDir, // Should be ignored for absolute paths
},
}
files := []string{testFile}
associations, err := AssociateFilesWithCommands(files, commands)
assert.NoError(t, err)
// File should be associated
assert.Contains(t, associations, testFile, "File should be in associations")
association := associations[testFile]
assert.Len(t, association.Commands, 1, "Should have 1 command")
}
func TestAssociateFilesWithCommandsNoSourceDir(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "associate-files-no-sourcedir-test-*")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
testFile := filepath.Join(tmpDir, "test.xml")
err = os.WriteFile(testFile, []byte("<test/>"), 0644)
assert.NoError(t, err)
// Use absolute path since we have no SourceDir
absPath := ResolvePath(testFile)
commands := []ModifyCommand{
{
Name: "test",
Regex: "pattern",
Lua: "expr",
Files: []string{absPath},
SourceDir: "", // No SourceDir
},
}
files := []string{testFile}
associations, err := AssociateFilesWithCommands(files, commands)
assert.NoError(t, err)
// File should be associated
assert.Contains(t, associations, testFile, "File should be in associations")
association := associations[testFile]
assert.Len(t, association.Commands, 1, "Should have 1 command")
}
func TestSourceDirConditionCoverage(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "sourcedir-condition-test-*")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
tests := []struct {
name string
glob string
sourceDir string
shouldResolve bool
}{
{
name: "Relative path with SourceDir",
glob: "test.xml",
sourceDir: tmpDir,
shouldResolve: true,
},
{
name: "Absolute path with SourceDir",
glob: ResolvePath(filepath.Join(tmpDir, "test.xml")),
sourceDir: tmpDir,
shouldResolve: false, // Should use absolute path as-is
},
{
name: "Relative path without SourceDir",
glob: "test.xml",
sourceDir: "",
shouldResolve: false, // Should resolve relative to CWD
},
{
name: "Absolute path without SourceDir",
glob: ResolvePath(filepath.Join(tmpDir, "test.xml")),
sourceDir: "",
shouldResolve: false, // Should use absolute path as-is
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
commands := []ModifyCommand{
{
Name: "test",
Files: []string{tt.glob},
SourceDir: tt.sourceDir,
},
}
globs := AggregateGlobs(commands)
assert.Len(t, globs, 1, "Should have 1 glob")
var resolvedGlob string
for g := range globs {
resolvedGlob = g
}
if tt.shouldResolve {
// Should be resolved relative to SourceDir
expected := ResolvePath(filepath.Join(tt.sourceDir, tt.glob))
assert.Equal(t, expected, resolvedGlob, "Should resolve relative to SourceDir")
} else if filepath.IsAbs(tt.glob) {
// Absolute path should be normalized but not changed
expected := ResolvePath(tt.glob)
assert.Equal(t, expected, resolvedGlob, "Absolute path should be normalized")
} else {
// Relative path without SourceDir should resolve to CWD
cwd, _ := os.Getwd()
expected := ResolvePath(filepath.Join(cwd, tt.glob))
assert.Equal(t, expected, resolvedGlob, "Should resolve relative to CWD")
}
})
}
}