Skip to content

Commit

Permalink
GlobGitignore doesn't preprocess patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
robotdana committed Nov 23, 2023
1 parent c22b18d commit 354a202
Show file tree
Hide file tree
Showing 16 changed files with 1,128 additions and 124 deletions.
2 changes: 2 additions & 0 deletions .spellr_wordlists/english.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,15 @@ torvolds
tsx
ttributes
txt
umc
unanchorable
unc
unexpandable
unfuck
unnegated
unrecursive
unstaged
unstub
untr
upcase
urrent
Expand Down
2 changes: 1 addition & 1 deletion lib/path_list/autoloader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def autoload(klass)
def class_from_path(path)
name = ::File.basename(path).delete_suffix('.rb')

if name == 'version' || name == 'expandable_path'
if name == 'version' || name == 'scanner'
name.upcase
else
name.gsub(/(?:^|_)(\w)/, &:upcase).delete('_')
Expand Down
12 changes: 6 additions & 6 deletions lib/path_list/canonical_path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ class << self
class_eval <<~RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
def case_insensitive?
#{
pwd = ::Dir.pwd
pwd_swapcase = pwd.swapcase
test_dir = ::Dir.pwd
test_dir_swapcase = test_dir.swapcase
# :nocov:
# if the current directory has no casing differences
# (maybe because it's at /)
# then:
if pwd == pwd_swapcase
if test_dir == test_dir_swapcase
require 'tmpdir'
pwd = ::File.write(::Dir.mktmpdir + '/case_test', '')
pwd_swapcase = pwd.swapcase
test_dir = ::File.write(::Dir.mktmpdir + '/case_test', '')
test_dir_swapcase = test_dir.swapcase
end
# :nocov:
::File.identical?(pwd, pwd_swapcase)
::File.identical?(test_dir, test_dir_swapcase)
}
end
RUBY
Expand Down
61 changes: 34 additions & 27 deletions lib/path_list/pattern_parser/gitignore.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ class PatternParser
class Gitignore
Autoloader.autoload(self)

SCANNER = RuleScanner

# @api private
# @param pattern [String]
# @param polarity [:ignore, :allow]
# @param root [String]
def initialize(pattern, polarity, root)
@s = RuleScanner.new(pattern)
@s = self.class::SCANNER.new(pattern)
@default_polarity = polarity
@rule_polarity = polarity
@root = root
Expand Down Expand Up @@ -51,9 +53,7 @@ def implicit_matcher
private

def prepare_regexp_builder
@re = if @root.nil?
TokenRegexp::Path.new([:start_anchor])
elsif @root.end_with?('/')
@re = if @root.end_with?('/')
TokenRegexp::Path.new_from_path(@root, [:any_dir])
else
TokenRegexp::Path.new_from_path(@root, [:dir, :any_dir])
Expand Down Expand Up @@ -117,12 +117,13 @@ def append_string(string)
end

def emit_end
@re.remove_trailing_dir
append_part :end_anchor
break!
end

def process_backslash
return unless @s.backslash?
def process_escape
return unless @s.escape?

if @re.append_string(@s.next_character)
emitted!
Expand All @@ -142,7 +143,7 @@ def process_character_class

until @s.character_class_end?
next if process_character_class_range
next if process_backslash
next if process_escape
next if append_string(@s.character_class_literal)

unmatchable_rule!
Expand All @@ -158,11 +159,9 @@ def process_character_class_range
start = @s.character_class_range_start
return unless start

start = start.delete_prefix('\\')

append_string(start)

finish = @s.character_class_range_end.delete_prefix('\\')
finish = @s.character_class_range_end

return true unless start < finish

Expand All @@ -184,31 +183,39 @@ def process_rule
catch :abort_build do
blank! if @s.hash?
negated! if @s.exclamation_mark?
prepare_regexp_builder
anchored! if !@anchored && @s.slash?
process_first_characters

catch :break do
loop do
next if process_backslash
next unmatchable_rule! if @s.star_star_slash_slash?
next append_part(:any) && dir_only! if @s.star_star_slash_end?
next append_part(:any_dir) && anchored! if @s.star_star_slash?
next unmatchable_rule! if @s.slash_slash?
next append_part(:dir) && append_part(:any) && anchored! if @s.slash_star_star_end?
next append_part(:any_non_dir) if @s.star?
next dir_only! if @s.slash_end?
next append_part(:dir) && anchored! if @s.slash?
next append_part(:one_non_dir) if @s.question_mark?
next if process_character_class
next if append_string(@s.literal)
next if append_string(@s.significant_whitespace)

process_end
process_next_characters
end
end
end
end

def process_first_characters
prepare_regexp_builder
anchored! if !@anchored && @s.slash?
end

def process_next_characters
return if process_escape
return unmatchable_rule! if @s.star_star_slash_slash?
return append_part(:any) && dir_only! if @s.star_star_slash_end?
return append_part(:any_dir) && anchored! if @s.star_star_slash?
return unmatchable_rule! if @s.slash_slash?
return append_part(:dir) && append_part(:any) && anchored! if @s.slash_star_star_end?
return append_part(:any_non_dir) if @s.star?
return dir_only! if @s.slash_end?
return append_part(:dir) && anchored! if @s.slash?
return append_part(:one_non_dir) if @s.question_mark?
return if process_character_class
return if append_string(@s.literal)
return if append_string(@s.significant_whitespace)

process_end
end

def build_matcher
@main_re ||= @re.dup.compress

Expand Down
46 changes: 43 additions & 3 deletions lib/path_list/pattern_parser/gitignore/rule_scanner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,53 @@ def slash?
skip(%r{/})
end

# @return [String, nil]
def root_end
matched if scan(%r{/\s*\z})
end

# @return [String, nil]
def root
matched if scan(%r{/})
end

# @return [String, nil]
def home_slash_end
self[1] if scan(%r{(~[^/]*)/\s*\z})
end

# @return [String, nil]
def home_slash_or_end
self[1] if scan(%r{(~[^/]*)(?:/|\s*\z)})
end

# @return [Boolean]
def dot_slash_or_end?
skip(%r{\.(?:/|\s*\z)})
end

# @return [Boolean]
def dot_slash_end?
skip(%r{\./\s*\z})
end

# @return [Boolean]
def dot_dot_slash_end?
skip(%r{\.\./\s*\z})
end

# @return [Boolean]
def dot_dot_slash_or_end?
skip(%r{\.\.(?:/|\s*\z)})
end

# @return [Boolean]
def slash_end?
skip(%r{/\s*\z})
end

# @return [Boolean]
def backslash?
def escape?
skip(/\\/)
end

Expand Down Expand Up @@ -84,7 +124,7 @@ def character_class_literal

# @return [String, nil]
def character_class_range_start
matched if scan(/(\\.|[^\\\]])(?=-(\\.|[^\\\]]))/)
matched.delete_prefix('\\') if scan(/(\\.|[^\\\]])(?=-(\\.|[^\\\]]))/)
end

# @return [String, nil]
Expand All @@ -93,7 +133,7 @@ def character_class_range_end
# with the lookahead in character_class_range_start
skip(/-/)
scan(/(\\.|[^\\\]])/)
matched
matched.delete_prefix('\\')
end

# @return [String, nil]
Expand Down
120 changes: 120 additions & 0 deletions lib/path_list/pattern_parser/gitignore/windows_rule_scanner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# frozen_string_literal: true

require 'strscan'

class PathList
class PatternParser
class Gitignore
# @api private
class WindowsRuleScanner < RuleScanner
# @return [Boolean]
def slash?
skip(%r{[\\/]})
end

# @return [String, nil]
def root_end
# / or \ or UMC path or driver letter
matched if scan(%r{(?:[\\/]{1,2}|[a-zA-Z]:[\\/])\s*\z})
end

# @return [String, nil]
def root
# / or \ or UMC path or driver letter
matched if scan(%r{(?:[\\/]{1,2}|[a-zA-Z]:[\\/])})
end

# @return [String, nil]
def home_slash_end
# not sure this makes sense on windows, but just for similarity
self[1] if scan(%r{(~[^/\\]*)[\\/]\s*\z})
end

# @return [String, nil]
def home_slash_or_end
# not sure this makes sense on windows, but just for similarity
self[1] if scan(%r{(~[^/\\]*)(?:[\\/]|\s*\z)})
end

# @return [Boolean]
def dot_slash_or_end?
skip(%r{\.(?:[\\/]|\s*\z)})
end

# @return [Boolean]
def dot_slash_end?
skip(%r{\.[\\/]\s*\z})
end

# @return [Boolean]
def dot_dot_slash_end?
skip(%r{\.\.[\\/]\s*\z})
end

# @return [Boolean]
def dot_dot_slash_or_end?
skip(%r{\.\.(?:[\\/]|\s*\z)})
end

# @return [Boolean]
def slash_end?
skip(%r{[\\/]\s*\z})
end

# @return [Boolean]
def escape?
skip(/`/)
end

# @return [Boolean]
def star_star_slash_end?
skip(%r{\*{2,}[\\/]\s*\z})
end

# @return [Boolean]
def star_star_slash_slash?
skip(%r{\*{2,}[\\/]{2}})
end

# @return [Boolean]
def slash_slash?
skip(%r{[\\/]{2}})
end

# @return [Boolean]
def star_star_slash?
skip(%r{\*{2,}[\\/]})
end

# @return [Boolean]
def slash_star_star_end?
skip(%r{[\\/]\*{2,}\s*\z})
end

# @return [String, nil]
def character_class_literal
matched if scan(/[^\]`][^\]`-]*(?!-)/)
end

# @return [String, nil]
def character_class_range_start
matched.delete_prefix('`') if scan(/(`.|[^`\]])(?=-(`.|[^`\]]))/)
end

# @return [String, nil]
def character_class_range_end
# we already confirmed this was going to match
# with the lookahead in character_class_range_start
skip(/-/)
scan(/(`.|[^`\]])/)
matched.delete_prefix('`')
end

# @return [String, nil]
def literal
matched if scan(%r{[^*\\/?\[`\s]+})
end
end
end
end
end
Loading

0 comments on commit 354a202

Please sign in to comment.