Module: Ace::Review::Molecules::SubjectFilter
- Defined in:
- lib/ace/review/molecules/subject_filter.rb
Overview
Pure module for filtering review subjects based on file patterns.
Provides file pattern matching logic for filtering diff content, file lists, and bundle sections. Uses glob patterns with include/exclude semantics following the standard: include patterns whitelist files, exclude patterns blacklist them.
Constant Summary collapse
- FNMATCH_FLAGS =
File.fnmatch flags for glob pattern matching
File::FNM_PATHNAME | File::FNM_EXTGLOB
Class Method Summary collapse
-
.filter(subject, file_patterns) ⇒ String, Hash
Filter subject content based on file patterns.
-
.filter_bundle_sections(sections, file_patterns) ⇒ Hash
Filter bundle sections based on file patterns.
-
.filter_diff(diff_content, file_patterns) ⇒ String
Filter diff content based on file patterns.
-
.filter_hash(subject, file_patterns) ⇒ Hash
Filter subject hash based on file patterns.
-
.glob_match?(pattern, file_path, flags = FNMATCH_FLAGS) ⇒ Boolean
File.fnmatch treats a trailing /** as a single path segment.
-
.has_patterns?(file_patterns) ⇒ Boolean
Check if file patterns are configured and non-empty.
-
.matches_file?(file_path, file_patterns) ⇒ Boolean
Check if a file path matches the given patterns.
Class Method Details
.filter(subject, file_patterns) ⇒ String, Hash
Filter subject content based on file patterns
Dispatches to appropriate filter method based on subject type. Returns subject unchanged if no patterns configured.
40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/ace/review/molecules/subject_filter.rb', line 40 def self.filter(subject, file_patterns) return subject unless has_patterns?(file_patterns) case subject when String filter_diff(subject, file_patterns) when Hash filter_hash(subject, file_patterns) else subject end end |
.filter_bundle_sections(sections, file_patterns) ⇒ Hash
Filter bundle sections based on file patterns
Recursively filters files arrays within each section.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/ace/review/molecules/subject_filter.rb', line 109 def self.filter_bundle_sections(sections, file_patterns) filtered = {} sections.each do |name, section| section = normalize_keys(section) if section.is_a?(Hash) if section.is_a?(Hash) && section["files"].is_a?(Array) filtered_files = section["files"].select { |f| matches_file?(f.to_s, file_patterns) } next if filtered_files.empty? filtered[name] = section.merge("files" => filtered_files) else filtered[name] = section end end filtered end |
.filter_diff(diff_content, file_patterns) ⇒ String
Filter diff content based on file patterns
Splits diff into per-file chunks, filters by patterns, and rejoins. Uses the destination (b/) path for consistency with renamed files.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/ace/review/molecules/subject_filter.rb', line 61 def self.filter_diff(diff_content, file_patterns) return diff_content unless has_patterns?(file_patterns) # Split diff into file chunks chunks = split_diff_into_chunks(diff_content) # Filter chunks based on file patterns filtered_chunks = chunks.select do |chunk| file_path = extract_file_path_from_chunk(chunk) file_path ? matches_file?(file_path, file_patterns) : true end filtered_chunks.join end |
.filter_hash(subject, file_patterns) ⇒ Hash
Filter subject hash based on file patterns
Filters files arrays and bundle sections within the hash.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/ace/review/molecules/subject_filter.rb', line 83 def self.filter_hash(subject, file_patterns) return subject unless has_patterns?(file_patterns) result = normalize_keys(subject.dup) # Filter files array if present if result["files"].is_a?(Array) result["files"] = result["files"].select { |f| matches_file?(f.to_s, file_patterns) } end # Filter bundle sections if present if result["bundle"].is_a?(Hash) && result["bundle"]["sections"].is_a?(Hash) result["bundle"] = normalize_keys(result["bundle"].dup) result["bundle"]["sections"] = filter_bundle_sections(result["bundle"]["sections"], file_patterns) end result end |
.glob_match?(pattern, file_path, flags = FNMATCH_FLAGS) ⇒ Boolean
File.fnmatch treats a trailing /** as a single path segment. Append /* so the recursive glob covers files at every depth.
27 28 29 30 |
# File 'lib/ace/review/molecules/subject_filter.rb', line 27 def self.glob_match?(pattern, file_path, flags = FNMATCH_FLAGS) File.fnmatch?(pattern, file_path, flags) || (pattern.end_with?("/**") && File.fnmatch?("#{pattern}/*", file_path, flags)) end |
.has_patterns?(file_patterns) ⇒ Boolean
Check if file patterns are configured and non-empty
157 158 159 160 161 162 |
# File 'lib/ace/review/molecules/subject_filter.rb', line 157 def self.has_patterns?(file_patterns) return false unless file_patterns.is_a?(Hash) (file_patterns["include"].is_a?(Array) && file_patterns["include"].any?) || (file_patterns["exclude"].is_a?(Array) && file_patterns["exclude"].any?) end |
.matches_file?(file_path, file_patterns) ⇒ Boolean
Check if a file path matches the given patterns
Include patterns: file must match at least one (if any exist) Exclude patterns: file must not match any
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/ace/review/molecules/subject_filter.rb', line 136 def self.matches_file?(file_path, file_patterns) return true unless has_patterns?(file_patterns) includes = file_patterns["include"] || [] excludes = file_patterns["exclude"] || [] # If include patterns exist, file must match at least one if includes.any? return false unless includes.any? { |pattern| glob_match?(pattern, file_path) } end # File must not match any exclude pattern return false if excludes.any? { |pattern| glob_match?(pattern, file_path) } true end |