package main import ( "flag" "fmt" "os" "path/filepath" "strings" "github.com/atomaka/collect/archiver" "github.com/atomaka/collect/collector" ) const ( exitSuccess = 0 exitNoFiles = 1 exitArchiveError = 2 exitInvalidArgs = 3 ) // stringSlice is a custom flag type that accumulates string values type stringSlice []string func (s *stringSlice) String() string { return strings.Join(*s, ", ") } func (s *stringSlice) Set(value string) error { *s = append(*s, value) return nil } func main() { // Define flags using custom type for multiple values var nameFlags stringSlice var matchFlags stringSlice flag.Var(&nameFlags, "name", "Match exact filename (can be specified multiple times)") flag.Var(&matchFlags, "match", "Match directory pattern (can be specified multiple times)") // Custom usage message flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s [--name ]... [--match ]... \n\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Collects files recursively matching specific criteria and archives them.\n\n") fmt.Fprintf(os.Stderr, "Options:\n") flag.PrintDefaults() fmt.Fprintf(os.Stderr, "\nExamples:\n") fmt.Fprintf(os.Stderr, " %s --name .mise.toml ./ backup.tgz\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s --match 'aet-*/' ./ backup.zip\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s --name .mise.toml --name README.md --match 'test-*' ./ backup.tgz\n", os.Args[0]) } flag.Parse() // Validate flags if len(nameFlags) == 0 && len(matchFlags) == 0 { fmt.Fprintf(os.Stderr, "Error: At least one --name or --match must be specified\n\n") flag.Usage() os.Exit(exitInvalidArgs) } // Check positional arguments args := flag.Args() if len(args) != 2 { fmt.Fprintf(os.Stderr, "Error: Expected 2 arguments (source directory and output archive), got %d\n\n", len(args)) flag.Usage() os.Exit(exitInvalidArgs) } sourceDir := args[0] outputPath := args[1] // Determine archive format format := collector.GetArchiveFormat(outputPath) if format == "" { fmt.Fprintf(os.Stderr, "Error: Unsupported archive format. Use .tar.gz, .tgz, or .zip\n") os.Exit(exitInvalidArgs) } // Create matchers var matchers []collector.Matcher // Add name matchers for _, name := range nameFlags { matchers = append(matchers, collector.NewNameMatcher(name)) } // Add pattern matchers for _, pattern := range matchFlags { matchers = append(matchers, collector.NewPatternMatcher(pattern)) } // Create a composite matcher if we have multiple matchers, otherwise use the single one var matcher collector.Matcher if len(matchers) == 1 { matcher = matchers[0] } else { matcher = collector.NewCompositeMatcher(matchers) } // Create collector and collect files c := collector.New(matcher) files, err := c.Collect(sourceDir) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) if err.Error() == "no files found matching criteria" { os.Exit(exitNoFiles) } os.Exit(exitArchiveError) } // Report number of files found fmt.Printf("Found %d files to archive\n", len(files)) // Create appropriate archiver var arch archiver.Archiver switch format { case "tar.gz": arch = archiver.NewTarArchiver() case "zip": arch = archiver.NewZipArchiver() } // Create archive if err := arch.Create(outputPath, files); err != nil { fmt.Fprintf(os.Stderr, "Error creating archive: %v\n", err) os.Exit(exitArchiveError) } // Get absolute path for cleaner output absOutput, _ := filepath.Abs(outputPath) fmt.Printf("Archive created successfully: %s\n", absOutput) }