- 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
103 lines
No EOL
2.7 KiB
Go
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)
|
|
} |