1
0
Fork 0
collect/main.go
Andrew Tomaka eb88ef97c0 Implement collect CLI tool
- Add Go implementation with modular architecture
- Support --name flag for exact filename matching
- Support --match flag for directory glob pattern matching
- Create tar.gz and zip archives preserving directory structure
- Handle errors with appropriate exit codes
- Skip files with permission errors gracefully
- Add comprehensive test suite with 11 test cases
2025-06-12 21:38:00 -04:00

103 lines
No EOL
2.7 KiB
Go

package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"github.com/atomaka/collect/archiver"
"github.com/atomaka/collect/collector"
)
const (
exitSuccess = 0
exitNoFiles = 1
exitArchiveError = 2
exitInvalidArgs = 3
)
func main() {
// Define flags
nameFlag := flag.String("name", "", "Match exact filename")
matchFlag := flag.String("match", "", "Match directory pattern")
// Custom usage message
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [--name <filename> | --match <pattern>] <source-dir> <output-archive>\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])
}
flag.Parse()
// Validate flags
if (*nameFlag == "" && *matchFlag == "") || (*nameFlag != "" && *matchFlag != "") {
fmt.Fprintf(os.Stderr, "Error: Exactly one of --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 matcher
var matcher collector.Matcher
if *nameFlag != "" {
matcher = collector.NewNameMatcher(*nameFlag)
} else {
matcher = collector.NewPatternMatcher(*matchFlag)
}
// 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)
}