1
0
Fork 0
collect/collector/matcher.go
Andrew Tomaka bde7aeed90
Apply standard Go formatting and update project documentation
- Run go fmt on all Go files to ensure consistent formatting
- Add official Go tooling commands to CLAUDE.md for code quality
- Update project status to reflect current implementation state
2025-06-12 22:02:26 -04:00

139 lines
3.7 KiB
Go

package collector
import (
"os"
"path/filepath"
"strings"
)
// Matcher determines if a file should be included in the collection
type Matcher interface {
ShouldInclude(path string, info os.FileInfo) bool
}
// NameMatcher matches files by exact name
type NameMatcher struct {
name string
}
// NewNameMatcher creates a matcher for exact filename matching
func NewNameMatcher(name string) *NameMatcher {
return &NameMatcher{name: name}
}
// ShouldInclude returns true if the file matches the exact name
func (m *NameMatcher) ShouldInclude(path string, info os.FileInfo) bool {
if info.IsDir() {
return false
}
return info.Name() == m.name
}
// PatternMatcher matches files within directories matching a glob pattern
type PatternMatcher struct {
pattern string
matchedDirs map[string]bool
patternSegments []string
}
// NewPatternMatcher creates a matcher for directory pattern matching
func NewPatternMatcher(pattern string) *PatternMatcher {
// Remove trailing slash if present
pattern = strings.TrimSuffix(pattern, "/")
return &PatternMatcher{
pattern: pattern,
matchedDirs: make(map[string]bool),
patternSegments: strings.Split(pattern, string(os.PathSeparator)),
}
}
// ShouldInclude returns true if the file is within a directory matching the pattern
func (m *PatternMatcher) ShouldInclude(path string, info os.FileInfo) bool {
// For directories, check if they match the pattern and cache the result
if info.IsDir() {
matched, err := m.dirMatchesPattern(path)
if err == nil && matched {
m.matchedDirs[path] = true
}
return false // Don't include the directory itself, only files within
}
// For files, check if any parent directory is in the matched set
dir := filepath.Dir(path)
for {
if m.matchedDirs[dir] {
return true
}
// Also check if this directory matches the pattern (in case we haven't seen it yet)
if matched, err := m.dirMatchesPattern(dir); err == nil && matched {
m.matchedDirs[dir] = true
return true
}
parent := filepath.Dir(dir)
if parent == dir || parent == "." {
break
}
dir = parent
}
return false
}
// dirMatchesPattern checks if a directory path matches the glob pattern
func (m *PatternMatcher) dirMatchesPattern(dirPath string) (bool, error) {
// Get the directory name
dirName := filepath.Base(dirPath)
// For simple patterns (no path separators), just match the directory name
if len(m.patternSegments) == 1 {
return filepath.Match(m.pattern, dirName)
}
// For complex patterns, we need to match the full path segments
pathSegments := strings.Split(dirPath, string(os.PathSeparator))
// Try to match the pattern segments against the path segments
if len(pathSegments) < len(m.patternSegments) {
return false, nil
}
// Check each pattern segment against the corresponding path segment
for i := 0; i < len(m.patternSegments); i++ {
// Start from the end of both slices
patternIdx := len(m.patternSegments) - 1 - i
pathIdx := len(pathSegments) - 1 - i
matched, err := filepath.Match(m.patternSegments[patternIdx], pathSegments[pathIdx])
if err != nil {
return false, err
}
if !matched {
return false, 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
}