Support multiple --name and --match flags with OR logic
Allow users to specify multiple --name and --match flags in any combination. Files matching ANY of the specified criteria are collected, following standard Linux command conventions like grep -e and rsync --include. Changes: - Add CompositeMatcher for combining multiple matchers with OR logic - Update CLI to accept multiple flag values using custom stringSlice type - Add comprehensive tests for multiple flag combinations - Update usage message with examples of multiple flag usage
This commit is contained in:
parent
eb88ef97c0
commit
1265f9fb07
3 changed files with 85 additions and 13 deletions
|
@ -116,4 +116,24 @@ func (m *PatternMatcher) dirMatchesPattern(dirPath string) (bool, error) {
|
|||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CompositeMatcher combines multiple matchers with OR logic
|
||||
type CompositeMatcher struct {
|
||||
matchers []Matcher
|
||||
}
|
||||
|
||||
// NewCompositeMatcher creates a matcher that combines multiple matchers
|
||||
func NewCompositeMatcher(matchers []Matcher) *CompositeMatcher {
|
||||
return &CompositeMatcher{matchers: matchers}
|
||||
}
|
||||
|
||||
// ShouldInclude returns true if ANY of the matchers match the file
|
||||
func (m *CompositeMatcher) ShouldInclude(path string, info os.FileInfo) bool {
|
||||
for _, matcher := range m.matchers {
|
||||
if matcher.ShouldInclude(path, info) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
50
main.go
50
main.go
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/atomaka/collect/archiver"
|
||||
"github.com/atomaka/collect/collector"
|
||||
|
@ -17,27 +18,43 @@ const (
|
|||
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
|
||||
nameFlag := flag.String("name", "", "Match exact filename")
|
||||
matchFlag := flag.String("match", "", "Match directory pattern")
|
||||
// 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 <filename> | --match <pattern>] <source-dir> <output-archive>\n\n", os.Args[0])
|
||||
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])
|
||||
fmt.Fprintf(os.Stderr, " %s --name .mise.toml --name README.md --match 'test-*' ./ backup.tgz\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")
|
||||
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)
|
||||
}
|
||||
|
@ -60,12 +77,25 @@ func main() {
|
|||
os.Exit(exitInvalidArgs)
|
||||
}
|
||||
|
||||
// Create matcher
|
||||
// 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 *nameFlag != "" {
|
||||
matcher = collector.NewNameMatcher(*nameFlag)
|
||||
if len(matchers) == 1 {
|
||||
matcher = matchers[0]
|
||||
} else {
|
||||
matcher = collector.NewPatternMatcher(*matchFlag)
|
||||
matcher = collector.NewCompositeMatcher(matchers)
|
||||
}
|
||||
|
||||
// Create collector and collect files
|
||||
|
|
28
test.sh
28
test.sh
|
@ -118,9 +118,9 @@ run_test "no files found error" \
|
|||
run_test "invalid arguments - no flags" \
|
||||
"./collect test/ output.zip" 3
|
||||
|
||||
# Test 6: Invalid arguments - both flags
|
||||
run_test "invalid arguments - both flags" \
|
||||
"./collect --name .mise.toml --match 'aet-*' test/ output.zip" 3
|
||||
# Test 6: Invalid arguments - no positional args
|
||||
run_test "invalid arguments - no positional args" \
|
||||
"./collect --name .mise.toml" 3
|
||||
|
||||
# Test 7: Invalid arguments - missing output file
|
||||
run_test "invalid arguments - missing output file" \
|
||||
|
@ -144,6 +144,28 @@ run_test "name matching finds files in pattern dirs too" \
|
|||
"./collect --name .mise.toml test/ test-name-all.tgz"
|
||||
verify_archive_contents "test-name-all.tgz" ".mise.toml subdir/.mise.toml subdir/aet-bin/.mise.toml"
|
||||
|
||||
# Test 12: Multiple name flags
|
||||
echo "readme" > test/README.md
|
||||
echo "makefile" > test/Makefile
|
||||
echo "another makefile" > test/subdir/Makefile
|
||||
run_test "multiple name flags" \
|
||||
"./collect --name .mise.toml --name README.md --name Makefile test/ test-multi-name.tgz"
|
||||
verify_archive_contents "test-multi-name.tgz" ".mise.toml Makefile README.md subdir/.mise.toml subdir/Makefile subdir/aet-bin/.mise.toml"
|
||||
|
||||
# Test 13: Multiple match flags
|
||||
mkdir -p test/prefix-one test/prefix-two test/other-prefix
|
||||
echo "file1" > test/prefix-one/data.txt
|
||||
echo "file2" > test/prefix-two/config.yml
|
||||
echo "file3" > test/other-prefix/info.log
|
||||
run_test "multiple match flags" \
|
||||
"./collect --match 'prefix-*' --match 'aet-*' test/ test-multi-match.tgz"
|
||||
verify_archive_contents "test-multi-match.tgz" "deep/nested/aet-tools/deep.sh prefix-one/data.txt prefix-two/config.yml subdir/aet-bin/.mise.toml subdir/aet-bin/tool subdir/aet-config/settings.conf"
|
||||
|
||||
# Test 14: Combined name and match flags
|
||||
run_test "combined name and match flags" \
|
||||
"./collect --name .mise.toml --match 'aet-*' --name README.md test/ test-combined.tgz"
|
||||
verify_archive_contents "test-combined.tgz" ".mise.toml README.md deep/nested/aet-tools/deep.sh subdir/.mise.toml subdir/aet-bin/.mise.toml subdir/aet-bin/tool subdir/aet-config/settings.conf"
|
||||
|
||||
# Clean up
|
||||
echo -e "\nCleaning up..."
|
||||
rm -rf test test-*.tgz test-*.zip collect /tmp/test_output.txt
|
||||
|
|
Loading…
Add table
Reference in a new issue