1
0
Fork 0
collect/main.go

182 lines
5.5 KiB
Go

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
var includeDirFlags stringSlice
var excludeDirFlags stringSlice
var verbose bool
var dryRun bool
flag.Var(&nameFlags, "name", "Match exact filename (can be specified multiple times)")
flag.Var(&matchFlags, "match", "Match directory pattern (can be specified multiple times)")
flag.Var(&includeDirFlags, "include-dir", "Only traverse directories matching pattern (can be specified multiple times)")
flag.Var(&excludeDirFlags, "exclude-dir", "Skip directories matching pattern (can be specified multiple times)")
flag.BoolVar(&verbose, "verbose", false, "Enable verbose output for debugging")
flag.BoolVar(&dryRun, "dry-run", false, "Show what files would be collected without creating archive")
// Custom usage message
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [--name <filename>]... [--match <pattern>]... [--include-dir <pattern>]... [--exclude-dir <pattern>]... [--dry-run] [--verbose] <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])
fmt.Fprintf(os.Stderr, " %s --name .mise.toml --name README.md --match 'test-*' ./ backup.tgz\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s --include-dir src --exclude-dir 'temp-*' --name '*.go' ./ backup.tgz\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %s --dry-run --name .mise.toml ./ 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 (skip validation in dry-run mode)
var format string
if !dryRun {
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 directory filters
var dirFilter collector.DirectoryFilter
if len(includeDirFlags) > 0 || len(excludeDirFlags) > 0 {
var includeFilters []collector.DirectoryFilter
var excludeFilters []collector.DirectoryFilter
// Create include filters
if len(includeDirFlags) > 0 {
includeFilters = append(includeFilters, collector.NewIncludeDirectoryFilter(includeDirFlags))
}
// Create exclude filters
if len(excludeDirFlags) > 0 {
excludeFilters = append(excludeFilters, collector.NewExcludeDirectoryFilter(excludeDirFlags))
}
dirFilter = collector.NewCompositeDirectoryFilter(includeFilters, excludeFilters)
}
// Create collector and collect files
var c *collector.Collector
if dirFilter != nil {
c = collector.NewWithDirectoryFilter(matcher, dirFilter, verbose)
} else {
c = collector.New(matcher, verbose)
}
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)
}
// Handle dry-run mode
if dryRun {
fmt.Printf("DRY RUN: Found %d files that would be collected:\n", len(files))
for _, file := range files {
fmt.Printf(" %s\n", file.Path)
}
fmt.Printf("\nDRY RUN: Would create archive: %s\n", outputPath)
fmt.Printf("DRY RUN: No archive created (use without --dry-run to create archive)\n")
os.Exit(exitSuccess)
}
// 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)
}