Add directory filtering with --include-dir and --exclude-dir flags
Implement comprehensive directory filtering functionality that allows users to control which directories are traversed during file collection. Features: - --include-dir: Only traverse directories matching specified patterns - --exclude-dir: Skip directories matching specified patterns - Support for glob patterns (e.g., 'temp-*', 'project-*') - Multiple filters with OR logic (like existing --name/--match flags) - Include filters take precedence over exclude filters when both specified - Seamless integration with existing file matching functionality Implementation: - Add DirectoryFilter interface with Include/Exclude/Composite implementations - Update Collector to accept optional DirectoryFilter and use filepath.SkipDir - Add CLI flags and argument parsing for new directory filtering options - Comprehensive test suite with 7 new test cases covering all scenarios
This commit is contained in:
parent
bde7aeed90
commit
e63426f7c7
4 changed files with 259 additions and 5 deletions
|
@ -137,3 +137,164 @@ func (m *CompositeMatcher) ShouldInclude(path string, info os.FileInfo) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DirectoryFilter determines if a directory should be traversed
|
||||
type DirectoryFilter interface {
|
||||
ShouldTraverse(path string, sourceDir string) bool
|
||||
}
|
||||
|
||||
// IncludeDirectoryFilter only traverses directories matching the patterns
|
||||
type IncludeDirectoryFilter struct {
|
||||
patterns []string
|
||||
}
|
||||
|
||||
// NewIncludeDirectoryFilter creates a filter that only includes matching directories
|
||||
func NewIncludeDirectoryFilter(patterns []string) *IncludeDirectoryFilter {
|
||||
return &IncludeDirectoryFilter{patterns: patterns}
|
||||
}
|
||||
|
||||
// ShouldTraverse returns true if the directory path matches any include pattern
|
||||
func (f *IncludeDirectoryFilter) ShouldTraverse(path string, sourceDir string) bool {
|
||||
// Always traverse the source directory itself
|
||||
if path == sourceDir {
|
||||
return true
|
||||
}
|
||||
|
||||
// Get relative path from source directory
|
||||
relPath, err := filepath.Rel(sourceDir, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
relPath = filepath.ToSlash(relPath)
|
||||
|
||||
// Check if this directory or any parent directory matches any pattern
|
||||
for _, pattern := range f.patterns {
|
||||
if f.pathMatchesPattern(relPath, pattern) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// pathMatchesPattern checks if a path matches a glob pattern
|
||||
func (f *IncludeDirectoryFilter) pathMatchesPattern(path, pattern string) bool {
|
||||
// Direct match
|
||||
if matched, _ := filepath.Match(pattern, path); matched {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if any parent directory matches the pattern
|
||||
pathSegments := strings.Split(path, "/")
|
||||
for i := 0; i < len(pathSegments); i++ {
|
||||
segment := pathSegments[i]
|
||||
if matched, _ := filepath.Match(pattern, segment); matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're a subdirectory of a matching directory
|
||||
dir := filepath.Dir(path)
|
||||
for dir != "." && dir != "/" {
|
||||
if matched, _ := filepath.Match(pattern, filepath.Base(dir)); matched {
|
||||
return true
|
||||
}
|
||||
dir = filepath.Dir(dir)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ExcludeDirectoryFilter skips directories matching the patterns
|
||||
type ExcludeDirectoryFilter struct {
|
||||
patterns []string
|
||||
}
|
||||
|
||||
// NewExcludeDirectoryFilter creates a filter that excludes matching directories
|
||||
func NewExcludeDirectoryFilter(patterns []string) *ExcludeDirectoryFilter {
|
||||
return &ExcludeDirectoryFilter{patterns: patterns}
|
||||
}
|
||||
|
||||
// ShouldTraverse returns false if the directory path matches any exclude pattern
|
||||
func (f *ExcludeDirectoryFilter) ShouldTraverse(path string, sourceDir string) bool {
|
||||
// Always traverse the source directory itself
|
||||
if path == sourceDir {
|
||||
return true
|
||||
}
|
||||
|
||||
// Get relative path from source directory
|
||||
relPath, err := filepath.Rel(sourceDir, path)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
relPath = filepath.ToSlash(relPath)
|
||||
|
||||
// Check if this directory or any parent directory matches any exclude pattern
|
||||
for _, pattern := range f.patterns {
|
||||
if f.pathMatchesPattern(relPath, pattern) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// pathMatchesPattern checks if a path matches a glob pattern
|
||||
func (f *ExcludeDirectoryFilter) pathMatchesPattern(path, pattern string) bool {
|
||||
// Direct match
|
||||
if matched, _ := filepath.Match(pattern, path); matched {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if any parent directory matches the pattern
|
||||
pathSegments := strings.Split(path, "/")
|
||||
for i := 0; i < len(pathSegments); i++ {
|
||||
segment := pathSegments[i]
|
||||
if matched, _ := filepath.Match(pattern, segment); matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// CompositeDirectoryFilter combines multiple directory filters
|
||||
type CompositeDirectoryFilter struct {
|
||||
includeFilters []DirectoryFilter
|
||||
excludeFilters []DirectoryFilter
|
||||
}
|
||||
|
||||
// NewCompositeDirectoryFilter creates a filter combining include and exclude filters
|
||||
func NewCompositeDirectoryFilter(includeFilters, excludeFilters []DirectoryFilter) *CompositeDirectoryFilter {
|
||||
return &CompositeDirectoryFilter{
|
||||
includeFilters: includeFilters,
|
||||
excludeFilters: excludeFilters,
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldTraverse applies include filters first, then exclude filters
|
||||
// Include filters take precedence when both are present
|
||||
func (f *CompositeDirectoryFilter) ShouldTraverse(path string, sourceDir string) bool {
|
||||
// If we have include filters, the directory must match at least one
|
||||
if len(f.includeFilters) > 0 {
|
||||
shouldInclude := false
|
||||
for _, filter := range f.includeFilters {
|
||||
if filter.ShouldTraverse(path, sourceDir) {
|
||||
shouldInclude = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !shouldInclude {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Apply exclude filters
|
||||
for _, filter := range f.excludeFilters {
|
||||
if !filter.ShouldTraverse(path, sourceDir) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue