package main import ( "bufio" "flag" "io" "log" "os" "path/filepath" "sync" "sync/atomic" ) const deliminer = "," const SourceColor = Purple const TargetColor = Yellow const ErrorColor = Red const ImportantColor = BRed const DefaultColor = Reset const PathColor = Green var programName = os.Args[0] var undo = false func main() { recurse := flag.String("r", "", "recurse into directories") file := flag.String("f", "", "file to read instructions from") debug := flag.Bool("d", false, "debug") undoF := flag.Bool("u", false, "undo") flag.Parse() undo = *undoF if *debug { log.SetFlags(log.Lmicroseconds | log.Lshortfile) logFile, err := os.Create(programName + ".log") if err != nil { LogError("Error creating log file: %v", err) os.Exit(1) } logger := io.MultiWriter(os.Stdout, logFile) log.SetOutput(logger) } else { log.SetFlags(log.Lmicroseconds) } instructions := make(chan *LinkInstruction, 1000) status := make(chan error) // Check input sources in priority order switch { case *recurse != "": LogInfo("Recurse: %s", *recurse) go ReadFromFilesRecursively(*recurse, instructions, status) case *file != "": LogInfo("File: %s", *file) go ReadFromFile(*file, instructions, status, true) case len(flag.Args()) > 0: LogInfo("Reading from command line arguments") go ReadFromArgs(instructions, status) // case IsPipeInput(): // LogInfo("Reading from stdin pipe") // go ReadFromStdin(instructions, status) default: if _, err := os.Stat("sync"); err == nil { LogInfo("Using default sync file") go ReadFromFile("sync", instructions, status, true) } else if _, err := os.Stat("sync.yaml"); err == nil { LogInfo("Using default sync.yaml file") go ReadFromFile("sync.yaml", instructions, status, true) } else if _, err := os.Stat("sync.yml"); err == nil { LogInfo("Using default sync.yml file") go ReadFromFile("sync.yml", instructions, status, true) } else { 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) } } go func() { for { err, ok := <-status if !ok { break } if err != nil { LogError("%v", err) } } }() var instructionsDone int32 = 0 var wg sync.WaitGroup for { instruction, ok := <-instructions if !ok { LogInfo("No more instructions to process") break } LogInfo("Processing: %s", instruction.String()) status := make(chan error) go instruction.RunAsync(status) wg.Add(1) err := <-status if err != nil { LogError("Failed processing instruction: %v", err) } atomic.AddInt32(&instructionsDone, 1) wg.Done() } wg.Wait() if instructionsDone == 0 { LogInfo("No instructions were processed") os.Exit(1) } LogInfo("All done") } func IsPipeInput() bool { info, err := os.Stdin.Stat() if err != nil { return false } return info.Mode()&os.ModeNamedPipe != 0 } func ReadFromFilesRecursively(input string, output chan *LinkInstruction, status chan error) { defer close(output) defer close(status) workdir, _ := os.Getwd() input = NormalizePath(input, workdir) LogInfo("Reading input from files recursively starting in %s", FormatPathValue(input)) files := make(chan string, 128) fileStatus := make(chan error) go GetSyncFilesRecursively(input, files, fileStatus) // Collect all files first var syncFiles []string for { file, ok := <-files if !ok { break } syncFiles = append(syncFiles, file) } // Check for errors from file search for { err, ok := <-fileStatus if !ok { break } if err != nil { LogError("Failed to get sync files recursively: %v", err) status <- err } } // Process each file for _, file := range syncFiles { file = NormalizePath(file, workdir) LogInfo("Processing file: %s", FormatPathValue(file)) // Change to the directory containing the sync file fileDir := filepath.Dir(file) originalDir, _ := os.Getwd() err := os.Chdir(fileDir) if err != nil { LogError("Failed to change directory to %s: %v", FormatSourcePath(fileDir), err) continue } // Read and process the file ReadFromFile(file, output, status, false) // Return to original directory os.Chdir(originalDir) } } func ReadFromFile(input string, output chan *LinkInstruction, status chan error, doclose bool) { if doclose { defer close(output) defer close(status) } input = NormalizePath(input, filepath.Dir(input)) LogInfo("Reading input from file: %s", FormatPathValue(input)) // Check if this is a YAML file if IsYAMLFile(input) { LogInfo("Parsing as YAML file") instructions, err := ParseYAMLFile(input, filepath.Dir(input)) if err != nil { LogError("Failed to parse YAML file %s: %v", FormatSourcePath(input), err) status <- err return } for _, instruction := range instructions { instr := instruction // Create a copy to avoid reference issues LogInfo("Read YAML instruction: %s", instr.String()) output <- &instr } return } // Handle CSV format (legacy) file, err := os.Open(input) if err != nil { log.Fatalf("Failed to open file %s%s%s: %s%+v%s", SourceColor, input, DefaultColor, ErrorColor, err, DefaultColor) return } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() instruction, err := ParseInstruction(line, filepath.Dir(input)) if err != nil { log.Printf("Error parsing line: %s'%s'%s, error: %s%+v%s", SourceColor, line, DefaultColor, ErrorColor, err, DefaultColor) continue } log.Printf("Read instruction: %s", instruction.String()) output <- &instruction } } func ReadFromArgs(output chan *LinkInstruction, status chan error) { defer close(output) defer close(status) workdir, _ := os.Getwd() LogInfo("Reading input from args") for _, arg := range flag.Args() { instruction, err := ParseInstruction(arg, workdir) if err != nil { LogError("Error parsing arg '%s': %v", arg, err) continue } output <- &instruction } } func ReadFromStdin(output chan *LinkInstruction, status chan error) { defer close(output) defer close(status) workdir, _ := os.Getwd() LogInfo("Reading input from stdin") scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { line := scanner.Text() instruction, err := ParseInstruction(line, workdir) if err != nil { LogError("Error parsing line '%s': %v", line, err) continue } output <- &instruction } if err := scanner.Err(); err != nil { LogError("Error reading from stdin: %v", err) status <- err return } }