Skip to content

Commit

Permalink
Regex match for given patterns (#3)
Browse files Browse the repository at this point in the history
* Add matchRegex option to cleaner_config.json

* Enhance file removal tests: Add support for regex matching and update test cases

* Add MatchRegex option to Config and update file removal logic to support regex patterns

* Add matchRegex option to README configuration example

* Update setup_test_env.sh to include additional files for regex removal testing

* Add test script to set up and run the test environment
  • Loading branch information
itSubeDibesh authored Nov 12, 2024
1 parent 3eb427a commit 8402c1e
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 98 deletions.
3 changes: 2 additions & 1 deletion ReadMe.MD
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ Cleaner uses a JSON configuration file to specify which directories and files to
],
"file_extensions_to_remove": [".DS_Store", "__debug_bin"],
"exclude_directories": [".git", ".svn"],
"exclude_files": []
"exclude_files": [],
"matchRegex": true
}
```

Expand Down
3 changes: 2 additions & 1 deletion cleaner_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
],
"file_extensions_to_remove": [".DS_Store", "__debug_bin"],
"exclude_directories": [".git", ".svn"],
"exclude_files": []
"exclude_files": [],
"matchRegex": true
}
26 changes: 16 additions & 10 deletions cleaner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,28 @@ func TestShouldRemoveDir(t *testing.T) {
}

func TestShouldRemoveFile(t *testing.T) {
extensionsToRemove := []string{".exe", ".dll", ".tmp"}

tests := []struct {
fileName string
want bool
fileName string
extensions []string
matchRegex bool
want bool
}{
{"program.exe", true},
{"tempfile.tmp", true},
{"document.txt", false},
// Non-regex cases
{"program.exe", []string{".exe", ".dll", ".tmp"}, false, true},
{"tempfile.tmp", []string{".exe", ".dll", ".tmp"}, false, true},
{"document.txt", []string{".exe", ".dll", ".tmp"}, false, false},

// Regex cases
{"tempfile.log", []string{`temp.*\.log`}, true, true},
{"logfile.log", []string{`temp.*\.log`}, true, false},
{"debug_info.txt", []string{`debug_.*\.txt`}, true, true},
{"info.txt", []string{`debug_.*\.txt`}, true, false},
}

for _, tt := range tests {
got := shouldRemoveFile(tt.fileName, extensionsToRemove)
got := shouldRemoveFile(tt.fileName, tt.extensions, tt.matchRegex)
if got != tt.want {
t.Errorf("shouldRemoveFile(%q) = %v; want %v", tt.fileName, got, tt.want)
t.Errorf("shouldRemoveFile(%q, %v, %v) = %v; want %v", tt.fileName, tt.extensions, tt.matchRegex, got, tt.want)
}
}
}
Expand All @@ -65,7 +72,6 @@ func TestIsExcluded(t *testing.T) {
}

func TestLoadConfig(t *testing.T) {
// Since we're not using files, we'll test getDefaultConfig instead
config := getDefaultConfig()

if len(config.DirectoriesToRemove) == 0 {
Expand Down
113 changes: 30 additions & 83 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"sync"
Expand All @@ -18,10 +19,10 @@ type Config struct {
FileExtensionsToRemove []string `json:"file_extensions_to_remove"`
ExcludeDirectories []string `json:"exclude_directories"`
ExcludeFiles []string `json:"exclude_files"`
MatchRegex bool `json:"matchRegex"`
}

func main() {
// Parse command-line arguments
rootDir := flag.String("root", ".", "Root directory to scan")
configFile := flag.String("config", "cleaner_config.json", "Path to the JSON configuration file")
verbose := flag.Bool("verbose", true, "Enable verbose console output")
Expand All @@ -30,20 +31,17 @@ func main() {
logFormat := flag.String("log-format", "text", "Log format: 'text' or 'json'")
flag.Parse()

// Check if the root directory exists
if _, err := os.Stat(*rootDir); os.IsNotExist(err) {
log.Fatalf("Root directory does not exist: %s\n", *rootDir)
}

// Load configuration
config, err := loadConfig(*configFile)
if err != nil {
fmt.Printf("Warning: %v\n", err)
fmt.Println("Using default configuration.")
config = getDefaultConfig()
}

// Initialize logger
var logger *log.Logger
if *saveLog {
logFile := "cleaned_source.txt"
Expand All @@ -52,21 +50,18 @@ func main() {
log.Fatalf("Failed to open log file: %v", err)
}
defer f.Close()
logger = log.New(f, "", 0) // Use custom log format
logger = log.New(f, "", 0)
}

// Create a WaitGroup and a semaphore for concurrency control
var wg sync.WaitGroup
maxGoroutines := runtime.NumCPU()
semaphore := make(chan struct{}, maxGoroutines)
var logMutex sync.Mutex
var consoleMutex sync.Mutex

// Start processing the root directory
wg.Add(1)
go walkDir(*rootDir, config, *verbose, *dryRun, *logFormat, &wg, semaphore, logger, &logMutex, &consoleMutex)

// Wait for all goroutines to finish
wg.Wait()
fmt.Println("Cleaning process completed.")
}
Expand All @@ -86,6 +81,7 @@ func getDefaultConfig() *Config {
FileExtensionsToRemove: []string{".DS_Store", "__debug_bin"},
ExcludeDirectories: []string{".git", ".svn"},
ExcludeFiles: []string{},
MatchRegex: true,
}
}

Expand All @@ -109,11 +105,9 @@ func loadConfig(configPath string) (*Config, error) {
func walkDir(dir string, config *Config, verbose, dryRun bool, logFormat string, wg *sync.WaitGroup, semaphore chan struct{}, logger *log.Logger, logMutex, consoleMutex *sync.Mutex) {
defer wg.Done()

// Acquire a semaphore slot
semaphore <- struct{}{}
defer func() { <-semaphore }()

// Read directory entries
entries, err := os.ReadDir(dir)
if err != nil {
consoleMutex.Lock()
Expand All @@ -127,115 +121,56 @@ func walkDir(dir string, config *Config, verbose, dryRun bool, logFormat string,
entryPath := filepath.Join(dir, entryName)

if entry.IsDir() {
// Check for exclusion
if isExcluded(entryName, config.ExcludeDirectories) {
continue
}

// Check if the directory should be removed
if shouldRemoveDir(entryName, config.DirectoriesToRemove) {
// Log detection
if verbose {
consoleMutex.Lock()
fmt.Printf("Detected directory to remove: %s\n", entryPath)
consoleMutex.Unlock()
}

// Log starting cleaning
if verbose {
consoleMutex.Lock()
fmt.Printf("Cleaning directory: %s\n", entryPath)
consoleMutex.Unlock()
}

if !dryRun {
// Remove the directory
err := os.RemoveAll(entryPath)
if err != nil {
consoleMutex.Lock()
fmt.Fprintf(os.Stderr, "Failed to remove directory %s: %v\n", entryPath, err)
consoleMutex.Unlock()
} else {
// Log completion
if verbose {
consoleMutex.Lock()
fmt.Printf("Completed cleaning directory: %s\n", entryPath)
consoleMutex.Unlock()
}

// Log the path of the removed directory
if logger != nil {
logEntry(logger, logMutex, logFormat, "Removed directory", entryPath)
}
}
} else {
// Dry-run mode: simulate removal
if verbose {
consoleMutex.Lock()
fmt.Printf("[Dry Run] Would remove directory: %s\n", entryPath)
consoleMutex.Unlock()
}
if logger != nil {
logEntry(logger, logMutex, logFormat, "[Dry Run] Would remove directory", entryPath)
} else if logger != nil {
logEntry(logger, logMutex, logFormat, "Removed directory", entryPath)
}
} else if logger != nil {
logEntry(logger, logMutex, logFormat, "[Dry Run] Would remove directory", entryPath)
}
} else {
// Recursively process subdirectories
wg.Add(1)
go walkDir(entryPath, config, verbose, dryRun, logFormat, wg, semaphore, logger, logMutex, consoleMutex)
}
} else {
// Check for exclusion
if isExcluded(entryName, config.ExcludeFiles) {
continue
}

// Check if the file should be removed
if shouldRemoveFile(entryName, config.FileExtensionsToRemove) {
// Log detection
if shouldRemoveFile(entryName, config.FileExtensionsToRemove, config.MatchRegex) {
if verbose {
consoleMutex.Lock()
fmt.Printf("Detected file to remove: %s\n", entryPath)
consoleMutex.Unlock()
}

// Log starting cleaning
if verbose {
consoleMutex.Lock()
fmt.Printf("Cleaning file: %s\n", entryPath)
consoleMutex.Unlock()
}

if !dryRun {
// Remove the file
err := os.Remove(entryPath)
if err != nil {
consoleMutex.Lock()
fmt.Fprintf(os.Stderr, "Failed to remove file %s: %v\n", entryPath, err)
consoleMutex.Unlock()
} else {
// Log completion
if verbose {
consoleMutex.Lock()
fmt.Printf("Completed cleaning file: %s\n", entryPath)
consoleMutex.Unlock()
}

// Log the path of the removed file
if logger != nil {
logEntry(logger, logMutex, logFormat, "Removed file", entryPath)
}
}
} else {
// Dry-run mode: simulate removal
if verbose {
consoleMutex.Lock()
fmt.Printf("[Dry Run] Would remove file: %s\n", entryPath)
consoleMutex.Unlock()
}
if logger != nil {
logEntry(logger, logMutex, logFormat, "[Dry Run] Would remove file", entryPath)
} else if logger != nil {
logEntry(logger, logMutex, logFormat, "Removed file", entryPath)
}
} else if logger != nil {
logEntry(logger, logMutex, logFormat, "[Dry Run] Would remove file", entryPath)
}
}
}
Expand All @@ -251,10 +186,23 @@ func shouldRemoveDir(dirName string, dirsToRemove []string) bool {
return false
}

func shouldRemoveFile(fileName string, extensions []string) bool {
for _, ext := range extensions {
if strings.HasSuffix(fileName, ext) {
return true
func shouldRemoveFile(fileName string, extensions []string, matchRegex bool) bool {
if matchRegex {
for _, pattern := range extensions {
matched, err := regexp.MatchString(pattern, fileName)
if err != nil {
fmt.Printf("Error in regex pattern %s: %v\n", pattern, err)
continue
}
if matched {
return true
}
}
} else {
for _, ext := range extensions {
if strings.HasSuffix(fileName, ext) {
return true
}
}
}
return false
Expand All @@ -278,7 +226,6 @@ func logEntry(logger *log.Logger, logMutex *sync.Mutex, format, action, path str
logMessage := fmt.Sprintf(`{"timestamp":"%s","action":"%s","path":"%s"}`, timestamp, action, path)
logger.Println(logMessage)
} else {
// Default to text format
logMessage := fmt.Sprintf("%s - %s: %s", timestamp, action, path)
logger.Println(logMessage)
}
Expand Down
12 changes: 9 additions & 3 deletions setup_test_env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ mkdir -p .git bin build dist node_modules/module src
# Create files in .git (excluded directory)
echo "git config" > .git/config

# Create files in bin (should be removed)
# Create files in bin (should be removed with regex and non-regex patterns)
echo "binary data" > bin/program.exe
echo "library data" > bin/helper.dll
echo "temporary data" > bin/temp_file.log # Should be removed if matched by regex `temp.*\.log`
echo "debug info" > bin/debug_info.txt # Should be removed if matched by regex `debug_.*\.txt`

# Create files in build (should be removed)
echo "object file data" > build/output.o
Expand All @@ -33,8 +35,12 @@ touch src/.DS_Store # Should be removed
# Create root-level files
echo "ENV variables" > .env # Should not be removed
echo "Project documentation" > README.md # Should not be removed
touch .DS_Store # Should be removed
touch __debug_bin # Should be removed
touch .DS_Store # Should be removed
touch __debug_bin # Should be removed
touch ___debug_bin_1100 # Should be removed
touch app__debug_bin # Should be removed
touch tempfile.log # Should be removed if matched by regex `temp.*\.log`
touch debug_output.txt # Should be removed if matched by regex `debug_.*\.txt`

# Navigate back to the parent directory
cd ..
17 changes: 17 additions & 0 deletions test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

# Create Setup Test Environment
echo "Setting up Test Environment"
./setup_test_env.sh

# Run the main script
echo "Running Test Script"
go run main.go test_project

# Clean up the test environment
echo "Cleaning up Test Environment"
rm -rf test_project
rm cleaned_source.txt

# Done
echo "Test Completed"

0 comments on commit 8402c1e

Please sign in to comment.