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
|
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"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/atomaka/collect/archiver"
|
"github.com/atomaka/collect/archiver"
|
||||||
"github.com/atomaka/collect/collector"
|
"github.com/atomaka/collect/collector"
|
||||||
|
@ -17,27 +18,43 @@ const (
|
||||||
exitInvalidArgs = 3
|
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() {
|
func main() {
|
||||||
// Define flags
|
// Define flags using custom type for multiple values
|
||||||
nameFlag := flag.String("name", "", "Match exact filename")
|
var nameFlags stringSlice
|
||||||
matchFlag := flag.String("match", "", "Match directory pattern")
|
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
|
// Custom usage message
|
||||||
flag.Usage = func() {
|
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, "Collects files recursively matching specific criteria and archives them.\n\n")
|
||||||
fmt.Fprintf(os.Stderr, "Options:\n")
|
fmt.Fprintf(os.Stderr, "Options:\n")
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
fmt.Fprintf(os.Stderr, "\nExamples:\n")
|
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 --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 --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()
|
flag.Parse()
|
||||||
|
|
||||||
// Validate flags
|
// Validate flags
|
||||||
if (*nameFlag == "" && *matchFlag == "") || (*nameFlag != "" && *matchFlag != "") {
|
if len(nameFlags) == 0 && len(matchFlags) == 0 {
|
||||||
fmt.Fprintf(os.Stderr, "Error: Exactly one of --name or --match must be specified\n\n")
|
fmt.Fprintf(os.Stderr, "Error: At least one --name or --match must be specified\n\n")
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
os.Exit(exitInvalidArgs)
|
os.Exit(exitInvalidArgs)
|
||||||
}
|
}
|
||||||
|
@ -60,12 +77,25 @@ func main() {
|
||||||
os.Exit(exitInvalidArgs)
|
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
|
var matcher collector.Matcher
|
||||||
if *nameFlag != "" {
|
if len(matchers) == 1 {
|
||||||
matcher = collector.NewNameMatcher(*nameFlag)
|
matcher = matchers[0]
|
||||||
} else {
|
} else {
|
||||||
matcher = collector.NewPatternMatcher(*matchFlag)
|
matcher = collector.NewCompositeMatcher(matchers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create collector and collect files
|
// 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" \
|
run_test "invalid arguments - no flags" \
|
||||||
"./collect test/ output.zip" 3
|
"./collect test/ output.zip" 3
|
||||||
|
|
||||||
# Test 6: Invalid arguments - both flags
|
# Test 6: Invalid arguments - no positional args
|
||||||
run_test "invalid arguments - both flags" \
|
run_test "invalid arguments - no positional args" \
|
||||||
"./collect --name .mise.toml --match 'aet-*' test/ output.zip" 3
|
"./collect --name .mise.toml" 3
|
||||||
|
|
||||||
# Test 7: Invalid arguments - missing output file
|
# Test 7: Invalid arguments - missing output file
|
||||||
run_test "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"
|
"./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"
|
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
|
# Clean up
|
||||||
echo -e "\nCleaning up..."
|
echo -e "\nCleaning up..."
|
||||||
rm -rf test test-*.tgz test-*.zip collect /tmp/test_output.txt
|
rm -rf test test-*.tgz test-*.zip collect /tmp/test_output.txt
|
||||||
|
|
Loading…
Add table
Reference in a new issue