182 lines
5.5 KiB
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)
|
|
}
|