Skip to content

Commit 354a202

Browse files
committed
GlobGitignore doesn't preprocess patterns
1 parent c22b18d commit 354a202

File tree

16 files changed

+1128
-124
lines changed

16 files changed

+1128
-124
lines changed

.spellr_wordlists/english.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,15 @@ torvolds
136136
tsx
137137
ttributes
138138
txt
139+
umc
139140
unanchorable
140141
unc
141142
unexpandable
142143
unfuck
143144
unnegated
144145
unrecursive
145146
unstaged
147+
unstub
146148
untr
147149
upcase
148150
urrent

lib/path_list/autoloader.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def autoload(klass)
1919
def class_from_path(path)
2020
name = ::File.basename(path).delete_suffix('.rb')
2121

22-
if name == 'version' || name == 'expandable_path'
22+
if name == 'version' || name == 'scanner'
2323
name.upcase
2424
else
2525
name.gsub(/(?:^|_)(\w)/, &:upcase).delete('_')

lib/path_list/canonical_path.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@ class << self
1010
class_eval <<~RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
1111
def case_insensitive?
1212
#{
13-
pwd = ::Dir.pwd
14-
pwd_swapcase = pwd.swapcase
13+
test_dir = ::Dir.pwd
14+
test_dir_swapcase = test_dir.swapcase
1515
# :nocov:
1616
# if the current directory has no casing differences
1717
# (maybe because it's at /)
1818
# then:
19-
if pwd == pwd_swapcase
19+
if test_dir == test_dir_swapcase
2020
require 'tmpdir'
21-
pwd = ::File.write(::Dir.mktmpdir + '/case_test', '')
22-
pwd_swapcase = pwd.swapcase
21+
test_dir = ::File.write(::Dir.mktmpdir + '/case_test', '')
22+
test_dir_swapcase = test_dir.swapcase
2323
end
2424
# :nocov:
2525
26-
::File.identical?(pwd, pwd_swapcase)
26+
::File.identical?(test_dir, test_dir_swapcase)
2727
}
2828
end
2929
RUBY

lib/path_list/pattern_parser/gitignore.rb

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ class PatternParser
1515
class Gitignore
1616
Autoloader.autoload(self)
1717

18+
SCANNER = RuleScanner
19+
1820
# @api private
1921
# @param pattern [String]
2022
# @param polarity [:ignore, :allow]
2123
# @param root [String]
2224
def initialize(pattern, polarity, root)
23-
@s = RuleScanner.new(pattern)
25+
@s = self.class::SCANNER.new(pattern)
2426
@default_polarity = polarity
2527
@rule_polarity = polarity
2628
@root = root
@@ -51,9 +53,7 @@ def implicit_matcher
5153
private
5254

5355
def prepare_regexp_builder
54-
@re = if @root.nil?
55-
TokenRegexp::Path.new([:start_anchor])
56-
elsif @root.end_with?('/')
56+
@re = if @root.end_with?('/')
5757
TokenRegexp::Path.new_from_path(@root, [:any_dir])
5858
else
5959
TokenRegexp::Path.new_from_path(@root, [:dir, :any_dir])
@@ -117,12 +117,13 @@ def append_string(string)
117117
end
118118

119119
def emit_end
120+
@re.remove_trailing_dir
120121
append_part :end_anchor
121122
break!
122123
end
123124

124-
def process_backslash
125-
return unless @s.backslash?
125+
def process_escape
126+
return unless @s.escape?
126127

127128
if @re.append_string(@s.next_character)
128129
emitted!
@@ -142,7 +143,7 @@ def process_character_class
142143

143144
until @s.character_class_end?
144145
next if process_character_class_range
145-
next if process_backslash
146+
next if process_escape
146147
next if append_string(@s.character_class_literal)
147148

148149
unmatchable_rule!
@@ -158,11 +159,9 @@ def process_character_class_range
158159
start = @s.character_class_range_start
159160
return unless start
160161

161-
start = start.delete_prefix('\\')
162-
163162
append_string(start)
164163

165-
finish = @s.character_class_range_end.delete_prefix('\\')
164+
finish = @s.character_class_range_end
166165

167166
return true unless start < finish
168167

@@ -184,31 +183,39 @@ def process_rule
184183
catch :abort_build do
185184
blank! if @s.hash?
186185
negated! if @s.exclamation_mark?
187-
prepare_regexp_builder
188-
anchored! if !@anchored && @s.slash?
186+
process_first_characters
189187

190188
catch :break do
191189
loop do
192-
next if process_backslash
193-
next unmatchable_rule! if @s.star_star_slash_slash?
194-
next append_part(:any) && dir_only! if @s.star_star_slash_end?
195-
next append_part(:any_dir) && anchored! if @s.star_star_slash?
196-
next unmatchable_rule! if @s.slash_slash?
197-
next append_part(:dir) && append_part(:any) && anchored! if @s.slash_star_star_end?
198-
next append_part(:any_non_dir) if @s.star?
199-
next dir_only! if @s.slash_end?
200-
next append_part(:dir) && anchored! if @s.slash?
201-
next append_part(:one_non_dir) if @s.question_mark?
202-
next if process_character_class
203-
next if append_string(@s.literal)
204-
next if append_string(@s.significant_whitespace)
205-
206-
process_end
190+
process_next_characters
207191
end
208192
end
209193
end
210194
end
211195

196+
def process_first_characters
197+
prepare_regexp_builder
198+
anchored! if !@anchored && @s.slash?
199+
end
200+
201+
def process_next_characters
202+
return if process_escape
203+
return unmatchable_rule! if @s.star_star_slash_slash?
204+
return append_part(:any) && dir_only! if @s.star_star_slash_end?
205+
return append_part(:any_dir) && anchored! if @s.star_star_slash?
206+
return unmatchable_rule! if @s.slash_slash?
207+
return append_part(:dir) && append_part(:any) && anchored! if @s.slash_star_star_end?
208+
return append_part(:any_non_dir) if @s.star?
209+
return dir_only! if @s.slash_end?
210+
return append_part(:dir) && anchored! if @s.slash?
211+
return append_part(:one_non_dir) if @s.question_mark?
212+
return if process_character_class
213+
return if append_string(@s.literal)
214+
return if append_string(@s.significant_whitespace)
215+
216+
process_end
217+
end
218+
212219
def build_matcher
213220
@main_re ||= @re.dup.compress
214221

lib/path_list/pattern_parser/gitignore/rule_scanner.rb

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,53 @@ def slash?
2727
skip(%r{/})
2828
end
2929

30+
# @return [String, nil]
31+
def root_end
32+
matched if scan(%r{/\s*\z})
33+
end
34+
35+
# @return [String, nil]
36+
def root
37+
matched if scan(%r{/})
38+
end
39+
40+
# @return [String, nil]
41+
def home_slash_end
42+
self[1] if scan(%r{(~[^/]*)/\s*\z})
43+
end
44+
45+
# @return [String, nil]
46+
def home_slash_or_end
47+
self[1] if scan(%r{(~[^/]*)(?:/|\s*\z)})
48+
end
49+
50+
# @return [Boolean]
51+
def dot_slash_or_end?
52+
skip(%r{\.(?:/|\s*\z)})
53+
end
54+
55+
# @return [Boolean]
56+
def dot_slash_end?
57+
skip(%r{\./\s*\z})
58+
end
59+
60+
# @return [Boolean]
61+
def dot_dot_slash_end?
62+
skip(%r{\.\./\s*\z})
63+
end
64+
65+
# @return [Boolean]
66+
def dot_dot_slash_or_end?
67+
skip(%r{\.\.(?:/|\s*\z)})
68+
end
69+
3070
# @return [Boolean]
3171
def slash_end?
3272
skip(%r{/\s*\z})
3373
end
3474

3575
# @return [Boolean]
36-
def backslash?
76+
def escape?
3777
skip(/\\/)
3878
end
3979

@@ -84,7 +124,7 @@ def character_class_literal
84124

85125
# @return [String, nil]
86126
def character_class_range_start
87-
matched if scan(/(\\.|[^\\\]])(?=-(\\.|[^\\\]]))/)
127+
matched.delete_prefix('\\') if scan(/(\\.|[^\\\]])(?=-(\\.|[^\\\]]))/)
88128
end
89129

90130
# @return [String, nil]
@@ -93,7 +133,7 @@ def character_class_range_end
93133
# with the lookahead in character_class_range_start
94134
skip(/-/)
95135
scan(/(\\.|[^\\\]])/)
96-
matched
136+
matched.delete_prefix('\\')
97137
end
98138

99139
# @return [String, nil]
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# frozen_string_literal: true
2+
3+
require 'strscan'
4+
5+
class PathList
6+
class PatternParser
7+
class Gitignore
8+
# @api private
9+
class WindowsRuleScanner < RuleScanner
10+
# @return [Boolean]
11+
def slash?
12+
skip(%r{[\\/]})
13+
end
14+
15+
# @return [String, nil]
16+
def root_end
17+
# / or \ or UMC path or driver letter
18+
matched if scan(%r{(?:[\\/]{1,2}|[a-zA-Z]:[\\/])\s*\z})
19+
end
20+
21+
# @return [String, nil]
22+
def root
23+
# / or \ or UMC path or driver letter
24+
matched if scan(%r{(?:[\\/]{1,2}|[a-zA-Z]:[\\/])})
25+
end
26+
27+
# @return [String, nil]
28+
def home_slash_end
29+
# not sure this makes sense on windows, but just for similarity
30+
self[1] if scan(%r{(~[^/\\]*)[\\/]\s*\z})
31+
end
32+
33+
# @return [String, nil]
34+
def home_slash_or_end
35+
# not sure this makes sense on windows, but just for similarity
36+
self[1] if scan(%r{(~[^/\\]*)(?:[\\/]|\s*\z)})
37+
end
38+
39+
# @return [Boolean]
40+
def dot_slash_or_end?
41+
skip(%r{\.(?:[\\/]|\s*\z)})
42+
end
43+
44+
# @return [Boolean]
45+
def dot_slash_end?
46+
skip(%r{\.[\\/]\s*\z})
47+
end
48+
49+
# @return [Boolean]
50+
def dot_dot_slash_end?
51+
skip(%r{\.\.[\\/]\s*\z})
52+
end
53+
54+
# @return [Boolean]
55+
def dot_dot_slash_or_end?
56+
skip(%r{\.\.(?:[\\/]|\s*\z)})
57+
end
58+
59+
# @return [Boolean]
60+
def slash_end?
61+
skip(%r{[\\/]\s*\z})
62+
end
63+
64+
# @return [Boolean]
65+
def escape?
66+
skip(/`/)
67+
end
68+
69+
# @return [Boolean]
70+
def star_star_slash_end?
71+
skip(%r{\*{2,}[\\/]\s*\z})
72+
end
73+
74+
# @return [Boolean]
75+
def star_star_slash_slash?
76+
skip(%r{\*{2,}[\\/]{2}})
77+
end
78+
79+
# @return [Boolean]
80+
def slash_slash?
81+
skip(%r{[\\/]{2}})
82+
end
83+
84+
# @return [Boolean]
85+
def star_star_slash?
86+
skip(%r{\*{2,}[\\/]})
87+
end
88+
89+
# @return [Boolean]
90+
def slash_star_star_end?
91+
skip(%r{[\\/]\*{2,}\s*\z})
92+
end
93+
94+
# @return [String, nil]
95+
def character_class_literal
96+
matched if scan(/[^\]`][^\]`-]*(?!-)/)
97+
end
98+
99+
# @return [String, nil]
100+
def character_class_range_start
101+
matched.delete_prefix('`') if scan(/(`.|[^`\]])(?=-(`.|[^`\]]))/)
102+
end
103+
104+
# @return [String, nil]
105+
def character_class_range_end
106+
# we already confirmed this was going to match
107+
# with the lookahead in character_class_range_start
108+
skip(/-/)
109+
scan(/(`.|[^`\]])/)
110+
matched.delete_prefix('`')
111+
end
112+
113+
# @return [String, nil]
114+
def literal
115+
matched if scan(%r{[^*\\/?\[`\s]+})
116+
end
117+
end
118+
end
119+
end
120+
end

0 commit comments

Comments
 (0)