diff --git a/instruction.go b/instruction.go index 43cb5c0..cb5efaa 100644 --- a/instruction.go +++ b/instruction.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "log" "os" "path/filepath" "slices" @@ -157,7 +156,7 @@ func (instruction *LinkInstruction) RunAsync(status chan (error)) { return } if info.Mode().IsRegular() && info.Name() == filepath.Base(instruction.Source) { - log.Printf("Overwriting existing file %s%s%s", TargetColor, instruction.Target, DefaultColor) + LogTarget("Overwriting existing file %s", instruction.Target) err := os.Remove(instruction.Target) if err != nil { status <- fmt.Errorf("could not remove existing file %s%s%s; err: %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor) @@ -167,7 +166,7 @@ func (instruction *LinkInstruction) RunAsync(status chan (error)) { } if isSymlink { - log.Printf("Removing symlink at %s%s%s", TargetColor, instruction.Target, DefaultColor) + LogTarget("Removing symlink at %s", instruction.Target) err = os.Remove(instruction.Target) if err != nil { status <- fmt.Errorf("failed deleting %s%s%s due to %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor) @@ -178,7 +177,7 @@ func (instruction *LinkInstruction) RunAsync(status chan (error)) { status <- fmt.Errorf("refusing to delte actual (non symlink) file %s%s%s", TargetColor, instruction.Target, DefaultColor) return } - log.Printf("%sDeleting (!!!)%s %s%s%s", ImportantColor, DefaultColor, TargetColor, instruction.Target, DefaultColor) + LogImportant("Deleting (!!!) %s", instruction.Target) err = os.RemoveAll(instruction.Target) if err != nil { status <- fmt.Errorf("failed deleting %s%s%s due to %s%+v%s", TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor) @@ -210,7 +209,9 @@ func (instruction *LinkInstruction) RunAsync(status chan (error)) { status <- fmt.Errorf("failed creating symlink between %s%s%s and %s%s%s with error %s%+v%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor, ErrorColor, err, DefaultColor) return } - log.Printf("Created symlink between %s%s%s and %s%s%s", SourceColor, instruction.Source, DefaultColor, TargetColor, instruction.Target, DefaultColor) + LogInfo("Created symlink between %s and %s", + FormatSourcePath(instruction.Source), + FormatTargetPath(instruction.Target)) status <- nil } @@ -237,12 +238,13 @@ func ParseYAMLFile(filename, workdir string) ([]LinkInstruction, error) { toRemove := []int{} for i, link := range config.Links { if strings.Contains(link.Source, "*") { - log.Printf("Expanding wildcard source %s%s%s in YAML file %s%s%s", SourceColor, link.Source, DefaultColor, SourceColor, filename, DefaultColor) + LogSource("Expanding wildcard source %s in YAML file %s", link.Source, filename) newlinks, err := ExpandWildcard(link.Source, workdir, link.Target) if err != nil { return nil, fmt.Errorf("error expanding wildcard: %w", err) } - log.Printf("Expanded wildcard source %s%s%s in YAML file %s%s%s to %d links", SourceColor, link.Source, DefaultColor, SourceColor, filename, DefaultColor, len(newlinks)) + LogInfo("Expanded wildcard source %s in YAML file %s to %d links", + FormatSourcePath(link.Source), FormatSourcePath(filename), len(newlinks)) config.Links = append(config.Links, newlinks...) toRemove = append(toRemove, i) } @@ -287,6 +289,7 @@ func ExpandWildcard(source, workdir, target string) (links []LinkInstruction, er links = append(links, link) } + LogInfo("Expanded wildcard source %s to %d links", FormatSourcePath(source), len(links)) return } diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..e7e0877 --- /dev/null +++ b/logger.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + "log" +) + +// LogInfo logs an informational message +func LogInfo(format string, args ...interface{}) { + log.Printf(format, args...) +} + +// LogSource logs a message about a source file/path with proper coloring +func LogSource(format string, args ...interface{}) { + message := fmt.Sprintf(format, args...) + log.Printf("%s%s%s", SourceColor, message, DefaultColor) +} + +// LogTarget logs a message about a target file/path with proper coloring +func LogTarget(format string, args ...interface{}) { + message := fmt.Sprintf(format, args...) + log.Printf("%s%s%s", TargetColor, message, DefaultColor) +} + +// LogPath logs a message about a path with proper coloring +func LogPath(format string, args ...interface{}) { + message := fmt.Sprintf(format, args...) + log.Printf("%s%s%s", PathColor, message, DefaultColor) +} + +// LogImportant logs a message that needs attention with proper coloring +func LogImportant(format string, args ...interface{}) { + message := fmt.Sprintf(format, args...) + log.Printf("%s%s%s", ImportantColor, message, DefaultColor) +} + +// LogError logs an error message with proper coloring that won't be cut off +func LogError(format string, args ...interface{}) { + message := fmt.Sprintf(format, args...) + log.Printf("%s%s%s", ErrorColor, message, DefaultColor) + // Make sure we're properly resetting before applying error color to avoid cutoffs + log.Printf("%s%s%s", ErrorColor, fmt.Sprintf(format, args...), DefaultColor) +} + +// FormatSourcePath formats a source path with proper coloring +func FormatSourcePath(path string) string { + return fmt.Sprintf("%s%s%s", SourceColor, path, DefaultColor) +} + +// FormatTargetPath formats a target path with proper coloring +func FormatTargetPath(path string) string { + return fmt.Sprintf("%s%s%s", TargetColor, path, DefaultColor) +} + +// FormatPathValue formats a path value with proper coloring +func FormatPathValue(path string) string { + return fmt.Sprintf("%s%s%s", PathColor, path, DefaultColor) +} + +// FormatErrorValue formats an error value with proper coloring +func FormatErrorValue(err error) string { + if err == nil { + return "" + } + return fmt.Sprintf("%s%v%s", ErrorColor, err, DefaultColor) +} diff --git a/main.go b/main.go index 599adf2..85eb833 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ func main() { log.SetFlags(log.Lmicroseconds | log.Lshortfile) logFile, err := os.Create("cln.log") if err != nil { - log.Printf("Error creating log file: %v", err) + LogError("Error creating log file: %v", err) os.Exit(1) } logger := io.MultiWriter(os.Stdout, logFile) @@ -48,36 +48,36 @@ func main() { // Check input sources in priority order switch { case *recurse != "": - log.Printf("Recurse: %s", *recurse) + LogInfo("Recurse: %s", *recurse) go ReadFromFilesRecursively(*recurse, instructions, status) case *file != "": - log.Printf("File: %s", *file) + LogInfo("File: %s", *file) go ReadFromFile(*file, instructions, status, true) case len(flag.Args()) > 0: - log.Printf("Reading from command line arguments") + LogInfo("Reading from command line arguments") go ReadFromArgs(instructions, status) case IsPipeInput(): - log.Printf("Reading from stdin pipe") + LogInfo("Reading from stdin pipe") go ReadFromStdin(instructions, status) default: if _, err := os.Stat("sync.yaml"); err == nil { - log.Printf("Using default sync.yaml file") + LogInfo("Using default sync.yaml file") go ReadFromFile("sync.yaml", instructions, status, true) } else if _, err := os.Stat("sync.yml"); err == nil { - log.Printf("Using default sync.yml file") + LogInfo("Using default sync.yml file") go ReadFromFile("sync.yml", instructions, status, true) } else { - log.Printf("No input provided") - log.Printf("Provide input as: ") - log.Printf("Arguments - %s ,,", programName) - log.Printf("File - %s -f ", programName) - log.Printf("YAML File - %s -f ", programName) - log.Printf("Folder (finding sync files in folder recursively) - %s -r ", programName) - log.Printf("stdin - (cat | %s)", programName) + LogInfo("No input provided") + LogInfo("Provide input as: ") + LogInfo("Arguments - %s ,,", programName) + LogInfo("File - %s -f ", programName) + LogInfo("YAML File - %s -f ", programName) + LogInfo("Folder (finding sync files in folder recursively) - %s -r ", programName) + LogInfo("stdin - (cat | %s)", programName) os.Exit(1) } } @@ -89,7 +89,7 @@ func main() { break } if err != nil { - log.Println(err) + LogError("%v", err) } } }() @@ -99,26 +99,27 @@ func main() { for { instruction, ok := <-instructions if !ok { - log.Printf("No more instructions to process") + LogInfo("No more instructions to process") break } - log.Printf("Processing: %s", instruction.String()) + LogInfo("Processing: %s", instruction.String()) status := make(chan error) go instruction.RunAsync(status) wg.Add(1) err := <-status if err != nil { - log.Printf("Failed parsing instruction %s%s%s due to %s%+v%s", SourceColor, instruction.String(), DefaultColor, ErrorColor, err, DefaultColor) + LogError("Failed parsing instruction %s due to %v", + FormatSourcePath(instruction.String()), err) } atomic.AddInt32(&instructionsDone, 1) wg.Done() } wg.Wait() - log.Println("All done") if instructionsDone == 0 { - log.Printf("No instructions were processed") + LogInfo("No instructions were processed") os.Exit(1) } + LogInfo("All done") } func IsPipeInput() bool { @@ -135,55 +136,61 @@ func ReadFromFilesRecursively(input string, output chan *LinkInstruction, status workdir, _ := os.Getwd() input = NormalizePath(input, workdir) - log.Printf("Reading input from files recursively starting in %s%s%s", PathColor, input, DefaultColor) + LogInfo("Reading input from files recursively starting in %s%s%s", PathColor, input, DefaultColor) files := make(chan string, 128) - recurseStatus := make(chan error) - go GetSyncFilesRecursively(input, files, recurseStatus) + fileStatus := make(chan error) + var wg sync.WaitGroup + go GetSyncFilesRecursively(input, files, fileStatus) + go func() { + wg.Wait() + close(files) + }() + go func() { for { - err, ok := <-recurseStatus + err, ok := <-fileStatus if !ok { break } if err != nil { - log.Printf("Failed to get sync files recursively: %s%+v%s", ErrorColor, err, DefaultColor) + LogError("Failed to get sync files recursively: %v", err) status <- err } } }() - var wg sync.WaitGroup for { file, ok := <-files if !ok { - log.Printf("No more files to process") + LogInfo("No more files to process") break } wg.Add(1) go func() { defer wg.Done() - log.Println(file) + LogInfo(file) file = NormalizePath(file, workdir) - log.Printf("Processing file: %s%s%s", PathColor, file, DefaultColor) + LogInfo("Processing file: %s%s%s", PathColor, file, DefaultColor) // This "has" to be done because instructions are resolved in relation to cwd fileDir := FileRegex.FindStringSubmatch(file) if fileDir == nil { - log.Printf("Failed to extract directory from %s%s%s", SourceColor, file, DefaultColor) + LogError("Failed to extract directory from %s%s%s", SourceColor, file, DefaultColor) return } - log.Printf("Changing directory to %s%s%s (for %s%s%s)", PathColor, fileDir[1], DefaultColor, PathColor, file, DefaultColor) + LogInfo("Changing directory to %s%s%s (for %s%s%s)", PathColor, fileDir[1], DefaultColor, PathColor, file, DefaultColor) err := os.Chdir(fileDir[1]) if err != nil { - log.Printf("Failed to change directory to %s%s%s: %s%+v%s", SourceColor, fileDir[1], DefaultColor, ErrorColor, err, DefaultColor) + LogError("Failed to change directory to %s%s%s: %v", + SourceColor, fileDir[1], DefaultColor, err) return } - ReadFromFile(file, output, status, false) + // Don't return directory, stay where we are + os.Chdir(workdir) }() } - wg.Wait() } func ReadFromFile(input string, output chan *LinkInstruction, status chan error, doclose bool) { @@ -193,14 +200,14 @@ func ReadFromFile(input string, output chan *LinkInstruction, status chan error, } input = NormalizePath(input, filepath.Dir(input)) - log.Printf("Reading input from file: %s%s%s", PathColor, input, DefaultColor) + LogInfo("Reading input from file: %s%s%s", PathColor, input, DefaultColor) // Check if this is a YAML file if IsYAMLFile(input) { - log.Printf("Parsing as YAML file") + LogInfo("Parsing as YAML file") instructions, err := ParseYAMLFile(input, filepath.Dir(input)) if err != nil { - log.Printf("Failed to parse YAML file %s%s%s: %s%+v%s", + LogError("Failed to parse YAML file %s%s%s: %s%+v%s", SourceColor, input, DefaultColor, ErrorColor, err, DefaultColor) status <- err return @@ -208,7 +215,7 @@ func ReadFromFile(input string, output chan *LinkInstruction, status chan error, for _, instruction := range instructions { instr := instruction // Create a copy to avoid reference issues - log.Printf("Read YAML instruction: %s", instr.String()) + LogInfo("Read YAML instruction: %s", instr.String()) output <- &instr } return @@ -220,11 +227,12 @@ func ReadFromArgs(output chan *LinkInstruction, status chan error) { defer close(status) workdir, _ := os.Getwd() - log.Printf("Reading input from args") + LogInfo("Reading input from args") for _, arg := range flag.Args() { instruction, err := ParseInstruction(arg, workdir) if err != nil { - log.Printf("Error parsing arg: %s'%s'%s, error: %s%+v%s", SourceColor, arg, DefaultColor, ErrorColor, err, DefaultColor) + LogError("Error parsing arg: %s'%s'%s, error: %v", + SourceColor, arg, DefaultColor, err) continue } output <- &instruction @@ -236,20 +244,21 @@ func ReadFromStdin(output chan *LinkInstruction, status chan error) { defer close(status) workdir, _ := os.Getwd() - log.Printf("Reading input from stdin") + LogInfo("Reading input from stdin") scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { line := scanner.Text() instruction, err := ParseInstruction(line, workdir) if err != nil { - log.Printf("Error parsing line: %s'%s'%s, error: %s%+v%s", SourceColor, line, DefaultColor, ErrorColor, err, DefaultColor) + LogError("Error parsing line: %s'%s'%s, error: %v", + SourceColor, line, DefaultColor, err) continue } output <- &instruction } if err := scanner.Err(); err != nil { - log.Fatalf("Error reading from stdin: %s%+v%s", ErrorColor, err, DefaultColor) + LogError("Error reading from stdin: %v", err) status <- err return } diff --git a/synclib.log b/synclib.log new file mode 100644 index 0000000..a77a6fa --- /dev/null +++ b/synclib.log @@ -0,0 +1,26 @@ +18:24:37.967093 Using default sync.yaml file +18:24:37.967093 Input 'sync.yaml' is not absolute, prepending work dir '.' +18:24:37.967593 Reading input from file: C:/Users/Administrator/Seafile/Projects-Go/GoProjects/synclib/sync.yaml +18:24:37.967593 Parsing as YAML file +18:24:37.967593 Failed to parse YAML file C:/Users/Administrator/Seafile/Projects-Go/GoProjects/synclib/sync.yaml: error unmarshaling YAML: yaml: unmarshal errors: + line 1: cannot unmarshal !!seq into main.YAMLConfig +18:24:37.967593 No more instructions to process +18:24:37.968092 No instructions were processed +18:27:59.691333 Using default sync.yaml file +18:27:59.691333 Input 'sync.yaml' is not absolute, prepending work dir '.' +18:27:59.691834 Reading input from file: C:/Users/Administrator/Seafile/Projects-Go/GoProjects/synclib/sync.yaml +18:27:59.691834 Parsing as YAML file +18:27:59.692335 Expanding wildcard source \* in YAML file C:/Users/Administrator/Seafile/Projects-Go/GoProjects/synclib/sync.yaml +18:27:59.692335 Expanded wildcard source \* to 0 links +18:27:59.692836 Expanded wildcard source \* in YAML file C:/Users/Administrator/Seafile/Projects-Go/GoProjects/synclib/sync.yaml to 0 links +18:27:59.692836 No more instructions to process +18:27:59.692836 No instructions were processed +18:28:04.075821 Using default sync.yaml file +18:28:04.076320 Input 'sync.yaml' is not absolute, prepending work dir '.' +18:28:04.076320 Reading input from file: C:/Users/Administrator/Seafile/Projects-Go/GoProjects/synclib/sync.yaml +18:28:04.076320 Parsing as YAML file +18:28:04.076320 Expanding wildcard source \* in YAML file C:/Users/Administrator/Seafile/Projects-Go/GoProjects/synclib/sync.yaml +18:28:04.076821 Expanded wildcard source \* to 0 links +18:28:04.076821 Expanded wildcard source \* in YAML file C:/Users/Administrator/Seafile/Projects-Go/GoProjects/synclib/sync.yaml to 0 links +18:28:04.076821 No more instructions to process +18:28:04.076821 No instructions were processed diff --git a/util.go b/util.go index 065d9d3..1162632 100644 --- a/util.go +++ b/util.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "log" "os" "path/filepath" "strings" @@ -33,12 +32,12 @@ func NormalizePath(input, workdir string) string { input = strings.ReplaceAll(input, "\"", "") if !filepath.IsAbs(input) { - log.Printf("Input '%s' is not absolute, prepending work dir '%s'", input, workdir) + LogInfo("Input '%s' is not absolute, prepending work dir '%s'", input, workdir) var err error input = filepath.Join(workdir, input) input, err = filepath.Abs(input) if err != nil { - log.Printf("Failed to get absolute path for %s%s%s: %s%+v%s", SourceColor, input, DefaultColor, ErrorColor, err, DefaultColor) + LogError("Failed to get absolute path for %s: %v", FormatSourcePath(input), err) return input } } @@ -157,15 +156,17 @@ func GetSyncFilesRecursively(input string, output chan string, status chan error <-allDone - log.Printf("Files processed: %d; Folders processed: %d", - atomic.LoadInt32(&filesProcessed), - atomic.LoadInt32(&foldersProcessed)) + if atomic.LoadInt32(&filesProcessed) > 0 { + LogInfo("Files processed: %d; Folders processed: %d", + atomic.LoadInt32(&filesProcessed), + atomic.LoadInt32(&foldersProcessed)) + } } func processDirectory(directory string, directories chan<- string, output chan<- string, filesProcessed *int32) { files, err := os.ReadDir(directory) if err != nil { - log.Printf("Error reading directory %s: %+v", directory, err) + LogError("Error reading directory %s: %v", directory, err) return }