Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GlobGitignore doesn't preprocess patterns
Browse files Browse the repository at this point in the history
robotdana committed Nov 23, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent c22b18d commit e1c9c61
Showing 11 changed files with 588 additions and 99 deletions.
2 changes: 2 additions & 0 deletions .spellr_wordlists/english.txt
Original file line number Diff line number Diff line change
@@ -136,13 +136,15 @@ torvolds
tsx
ttributes
txt
umc
unanchorable
unc
unexpandable
unfuck
unnegated
unrecursive
unstaged
unstub
untr
upcase
urrent
2 changes: 1 addition & 1 deletion lib/path_list/autoloader.rb
Original file line number Diff line number Diff line change
@@ -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 == 'expandable_path' || name == 'scanner'
name.upcase
else
name.gsub(/(?:^|_)(\w)/, &:upcase).delete('_')
59 changes: 34 additions & 25 deletions lib/path_list/pattern_parser/gitignore.rb
Original file line number Diff line number Diff line change
@@ -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
@@ -60,6 +62,7 @@ def prepare_regexp_builder
end

@start_any_dir_position = @re.length - 1
@re.delete_at(@start_any_dir_position) if @root && @anchored
end

def break!
@@ -94,7 +97,7 @@ def dir_only?

def anchored!
@anchored ||= begin
@re.delete_at(@start_any_dir_position)
@re.delete_at(@start_any_dir_position) if defined?(@re)
true
end
end
@@ -121,8 +124,8 @@ def emit_end
break!
end

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

if @re.append_string(@s.next_character)
emitted!
@@ -142,7 +145,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!
@@ -158,11 +161,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

@@ -184,31 +185,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

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
@@ -27,13 +27,53 @@ def slash?
skip(%r{/})
end

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

# @return [String, nil]
def home
scan(%r{~[^/]*})
end

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

# @return [Boolean]
def dot_end?
skip(/\.\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?
skip(%r{\.\./})
end

# @return [Boolean]
def dot_dot_end?
skip(/\.\.\s*\z/)
end

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

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

@@ -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]
@@ -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]
108 changes: 108 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,108 @@
# 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 home
# not sure this makes sense on windows, but just for similarity
scan(%r{~[^/\\]*})
end

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

# @return [Boolean]
def dot_slash?
skip(%r{\.[\\/]})
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?
skip(%r{\.\.[\\/]})
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 e1c9c61

Please sign in to comment.