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)


45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/clacky/utils/gitignore_parser.rb', line 45

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)


98
99
100
101
102
103
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
# File 'lib/clacky/utils/gitignore_parser.rb', line 98

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



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/clacky/utils/gitignore_parser.rb', line 17

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



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/clacky/utils/gitignore_parser.rb', line 79

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



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/clacky/utils/gitignore_parser.rb', line 62

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