Class: Clacky::GitignoreParser

Inherits:
Object
  • Object
show all
Defined in:
lib/clacky/utils/gitignore_parser.rb

Overview

Parser for .gitignore files to determine which files should be ignored

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(gitignore_path = nil) ⇒ GitignoreParser

Returns a new instance of GitignoreParser.



8
9
10
11
12
13
14
15
# File 'lib/clacky/utils/gitignore_parser.rb', line 8

def initialize(gitignore_path = nil)
  @patterns = []
  @negation_patterns = []

  if gitignore_path && File.exist?(gitignore_path)
    parse_gitignore(gitignore_path)
  end
end

Instance Attribute Details

#patternsObject (readonly)

Returns the value of attribute patterns.



6
7
8
# File 'lib/clacky/utils/gitignore_parser.rb', line 6

def patterns
  @patterns
end

Instance Method Details

#ignored?(path) ⇒ Boolean

Check if a file path should be ignored

Returns:

  • (Boolean)


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/clacky/utils/gitignore_parser.rb', line 51

def ignored?(path)
  relative_path = path.start_with?('./') ? path[2..] : path
  
  # Check negation patterns first (! prefix in .gitignore)
  @negation_patterns.each do |pattern|
    return false if match_pattern?(relative_path, pattern)
  end
  
  # Then check ignore patterns
  @patterns.each do |pattern|
    return true if match_pattern?(relative_path, pattern)
  end
  
  false
end

#match_pattern?(path, pattern_info) ⇒ Boolean

Returns:

  • (Boolean)


104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/clacky/utils/gitignore_parser.rb', line 104

def match_pattern?(path, pattern_info)
  pattern = pattern_info[:pattern]
  is_absolute = pattern_info[:is_absolute]
  
  # For absolute patterns (starting with /), remove the leading slash
  # These patterns match from the root of the repository
  if is_absolute
    pattern = pattern[1..]
    # Absolute patterns match exactly from the start of the path
    return true if path == pattern
    return true if path.start_with?("#{pattern}/")
  end
  
  # Handle directory patterns
  if pattern_info[:is_directory]
    # Directory patterns should match the directory and all its contents
    return true if path == pattern
    return true if path.start_with?("#{pattern}/")
    # Also check if any path component matches the directory pattern
    return true if path.split('/').include?(pattern)
  end
  
  # Handle different wildcard patterns
  if pattern_info[:has_double_star]
    # Convert ** to match any number of directories
    regex_pattern = Regexp.escape(pattern)
      .gsub('\*\*/', '(.*/)?')  # **/ matches zero or more directories
      .gsub('\*\*', '.*')        # ** at end matches anything
      .gsub('\*', '[^/]*')       # * matches anything except /
      .gsub('\?', '[^/]')        # ? matches single character except /
    
    regex = Regexp.new("^#{regex_pattern}$")
    return true if path.match?(regex)
    return true if path.split('/').any? { |part| part.match?(regex) }
  elsif pattern_info[:has_wildcard]
    # Convert glob pattern to regex
    regex_pattern = Regexp.escape(pattern)
      .gsub('\*', '[^/]*')
      .gsub('\?', '[^/]')
    
    regex = Regexp.new("^#{regex_pattern}$")
    return true if path.match?(regex)
    return true if File.basename(path).match?(regex)
  else
    # Exact match - pattern without wildcards
    # Match as basename or as path prefix
    return true if path == pattern
    return true if path.start_with?("#{pattern}/")
    return true if File.basename(path) == pattern
    # Also check if pattern matches any path component
    return true if path.split('/').include?(pattern)
  end
  
  false
end

#merge!(other_gitignore_path, prefix: nil) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/clacky/utils/gitignore_parser.rb', line 23

def merge!(other_gitignore_path, prefix: nil)
  return unless other_gitignore_path && File.exist?(other_gitignore_path)

  File.readlines(other_gitignore_path, chomp: true).each do |line|
    next if line.strip.empty? || line.start_with?('#')

    negation = line.start_with?('!')
    raw = negation ? line[1..] : line
    info = normalize_pattern(raw)

    if prefix
      original = info[:pattern]
      original = original[1..] if info[:is_absolute]
      info[:pattern] = "#{prefix}/#{original}"
      info[:is_absolute] = false
    end

    if negation
      @negation_patterns << info
    else
      @patterns << info
    end
  end
rescue StandardError => e
  warn "Warning: Failed to merge .gitignore: #{e.message}"
end

#normalize_pattern(pattern) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/clacky/utils/gitignore_parser.rb', line 85

def normalize_pattern(pattern)
  pattern = pattern.strip
  
  # Remove trailing whitespace
  pattern = pattern.rstrip
  
  # Store original for directory detection
  is_directory = pattern.end_with?('/')
  pattern = pattern.chomp('/')
  
  {
    pattern: pattern,
    is_directory: is_directory,
    is_absolute: pattern.start_with?('/'),
    has_wildcard: pattern.include?('*') || pattern.include?('?'),
    has_double_star: pattern.include?('**')
  }
end

#parse_gitignore(path) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/clacky/utils/gitignore_parser.rb', line 68

def parse_gitignore(path)
  File.readlines(path, chomp: true).each do |line|
    # Skip comments and empty lines
    next if line.strip.empty? || line.start_with?('#')
    
    # Handle negation patterns (lines starting with !)
    if line.start_with?('!')
      @negation_patterns << normalize_pattern(line[1..])
    else
      @patterns << normalize_pattern(line)
    end
  end
rescue StandardError => e
  # If we can't parse .gitignore, just continue with empty patterns
  warn "Warning: Failed to parse .gitignore: #{e.message}"
end