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 b7ea8ed
Show file tree
Hide file tree
Showing 17 changed files with 1,274 additions and 182 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
38 changes: 21 additions & 17 deletions lib/path_list/candidate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@ class PathList
# @api private
# The object that gets passed to all {PathList::Matcher} subclasses #match
class Candidate
# :nocov:
# https://github.com/jruby/jruby/issues/8018
# ftype follows symlinks on jruby on windows.
if ::RUBY_PLATFORM == 'java' && ::RbConfig::CONFIG['host_os'].match?(/mswin|mingw/)
# :nodoc:
module FileFtypeFix
refine ::File do
# :nodoc:
def ftype(path)
if ::File.symlink?(path)
'link'
else
super
end
end
end
end
using FileFtypeFix
end
# :nocov:

attr_reader :full_path

# @param full_path [String] resolved absolute path
Expand Down Expand Up @@ -105,23 +126,6 @@ def shebang

private

# :nocov:
# https://github.com/jruby/jruby/issues/8018
# ftype follows symlinks on jruby on windows.
if ::RUBY_PLATFORM == 'java' && ::RbConfig::CONFIG['host_os'].match?(/mswin|mingw/)
refine ::File do
# :nodoc:
def ftype(path)
if ::File.symlink?(path)
'link'
else
super
end
end
end
end
# :nocov:

def ftype
return @ftype if @ftype

Expand Down
27 changes: 15 additions & 12 deletions lib/path_list/canonical_path.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,23 @@ class << self
class_eval <<~RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
def case_insensitive?
#{
pwd = ::Dir.pwd
pwd_swapcase = pwd.swapcase
# :nocov:
# if the current directory has no casing differences
# (maybe because it's at /)
# then:
if pwd == pwd_swapcase
test_dir = ::Dir.pwd
test_dir_swapcase = test_dir.swapcase
if test_dir == test_dir_swapcase
# :nocov:
# this is copied into the canonical_path_spec to be tested there.
# if the current directory has no casing differences
# (maybe because it's at /)
# then:
require 'tmpdir'
pwd = ::File.write(::Dir.mktmpdir + '/case_test', '')
pwd_swapcase = pwd.swapcase
test_file = ::Dir.mktmpdir + '/case_test'
::File.write(test_file, '')
::File.exist?(test_file.swapcase)
# :nocov:
else
::File.identical?(test_dir, test_dir_swapcase)
end
# :nocov:
::File.identical?(pwd, pwd_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
Loading

0 comments on commit b7ea8ed

Please sign in to comment.