diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5d8bb8..3539cac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,10 +19,8 @@ jobs: bundler-cache: true - run: gem install syntax_suggest timeout-minutes: 5 - - run: bundle exec rspec spec --dry-run - timeout-minutes: 1 - run: bundle exec rspec spec --tag=~gitls - timeout-minutes: 5 + timeout-minutes: 10 compare: strategy: @@ -46,12 +44,16 @@ jobs: run: | Set-Location target ruby ..\bin\compare + ruby ..\bin\time # id: compare timeout-minutes: 1 - if: matrix.platform != 'windows' - run: cd target && ../bin/compare + run: | + cd target + ../bin/compare + ../bin/time id: compare - timeout-minutes: 1 + timeout-minutes: 2 - run: cd target && ../bin/parse if: failure() && steps.compare.outcome == 'failure' - run: cd target && ../bin/ls diff --git a/.spellr_wordlists/english.txt b/.spellr_wordlists/english.txt index b2707e7..74a6b2a 100644 --- a/.spellr_wordlists/english.txt +++ b/.spellr_wordlists/english.txt @@ -2,6 +2,7 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaazzz allowlist appendables argv +ath attributesfile backport barfoo @@ -44,6 +45,7 @@ filetree flamegraph fnmatch frotz +fsroot gemfile gitattributes gitconfig @@ -61,6 +63,7 @@ i'm i've idx ignorecase +ime includefile includeif initializable @@ -141,6 +144,7 @@ unrecursive unstaged untr upcase +urrent usr utf warmup diff --git a/Gemfile.lock b/Gemfile.lock index 596aa18..3a6029c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,9 +17,6 @@ GEM diff-lcs (1.5.0) docile (1.4.0) fast_ignore (0.17.4) - ffi (1.15.5) - get_process_mem (0.2.7) - ffi (~> 1.0) io-console (0.6.0) io-console (0.6.0-java) irb (1.7.1) @@ -27,7 +24,6 @@ GEM jar-dependencies (0.4.1) jaro_winkler (1.5.6) jaro_winkler (1.5.6-java) - jruby-win32ole (0.8.5) json (2.6.3) json (2.6.3-java) language_server-protocol (3.17.0.3) @@ -115,8 +111,6 @@ GEM parallel (~> 1.0) stringio (3.0.7) syntax_suggest (1.1.0) - sys-proctable (1.3.0) - ffi (~> 1.1) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) unicode-display_width (2.4.2) @@ -136,8 +130,6 @@ DEPENDENCIES benchmark-ips commonmarker debug - get_process_mem - jruby-win32ole leftovers (>= 0.4.0) path_list! pry @@ -153,7 +145,6 @@ DEPENDENCIES simplecov-console spellr (>= 0.8.3) syntax_suggest - sys-proctable webrick yard diff --git a/bin/benchmark b/bin/benchmark index ef7b679..8765dcd 100755 --- a/bin/benchmark +++ b/bin/benchmark @@ -99,6 +99,46 @@ benchmark('or-or-any') do end end + +benchmark('bang-bang') do + Benchmark.ips do |x| + x.config(config) + x.report(:nil_bang_nil, '!nil.nil?') + x.report(:nil_bang_bang, '!!nil') + x.report(:nil_ternary, 'nil ? true : false') + x.report(:truthy_ref_bang_nil, '![].nil?') + x.report(:truthy_ref_bang_bang, '!![]') + x.report(:truthy_ref_ternary, '[] ? true : false') + x.report(:truthy_bang_nil, '!1.nil?') + x.report(:truthy_bang_bang, '!!1') + x.report(:truthy_ternary, '1 ? true : false') + x.report(:true_bang_nil, '!true.nil?') + x.report(:true_bang_bang, '!!true') + x.report(:true_ternary, 'true ? true : false') + x.report(:false_bang_nil, '!false.nil?') + x.report(:false_bang_bang, '!!false') + x.report(:false_ternary, 'false ? true : false') + x.compare! + end +end + +benchmark('nil-not-false') do + Benchmark.ips do |x| + x.config(config) + x.report(:nil_nil, 'nil.nil?') + x.report(:nil_eq_false, 'nil || false == nil') + x.report(:true_nil, 'true.nil?') + x.report(:true_eq_false, 'true || false == true') + x.report(:false_nil, 'false.nil?') + x.report(:false_eq_false, 'false || false == false') + x.report(:truthy_nil, '1.nil?') + x.report(:truthy_eq_false, '1 || false == 1') + x.report(:truthy_ref_nil, '[].nil?') + x.report(:truthy_ref_eq_false, '[] || false == []') + x.compare! + end +end + benchmark('hash-merge') do Benchmark.ips do |x| x.config(config) diff --git a/lib/path_list/autoloader.rb b/lib/path_list/autoloader.rb index 82881de..9f6e39e 100644 --- a/lib/path_list/autoloader.rb +++ b/lib/path_list/autoloader.rb @@ -19,7 +19,7 @@ def autoload(klass) def class_from_path(path) name = ::File.basename(path).delete_suffix('.rb') - if name == 'version' + if name == 'version' || name == 'expandable_path' name.upcase else name.gsub(/(?:^|_)(\w)/, &:upcase).delete('_') diff --git a/lib/path_list/candidate.rb b/lib/path_list/candidate.rb index 64333ad..6f14374 100644 --- a/lib/path_list/candidate.rb +++ b/lib/path_list/candidate.rb @@ -13,7 +13,7 @@ def initialize(full_path, directory = nil, shebang = nil) @full_path = full_path @full_path_downcase = nil @directory = directory - @exists = nil + @shebang = shebang @child_candidates = nil @@ -29,10 +29,8 @@ def full_path_downcase # the containing directory as a Candidate, # or nil if this is already the root def parent - puts "#{__FILE__}:#{__LINE__}, @full_path: #{@full_path}" return if @full_path.end_with?('/') # '/' on unix X:/ on win - puts "#{__FILE__}:#{__LINE__}, ::File.dirname(@full_path): #{::File.dirname(@full_path)}" self.class.new(::File.dirname(@full_path), true) end @@ -56,23 +54,32 @@ def children end end - # @return [Boolean] whether this path is a directory (false for symlinks to directories) - def directory? - return @directory unless @directory.nil? + # :nocov: + if ::RUBY_PLATFORM == 'jruby' && ::RbConfig::CONFIG['host_os'].match?(/mswin|mingw/) + # @return [Boolean] whether this path is a directory (false for symlinks to directories) + puts 'WE ARE WINDOWS JRUBY' + def directory? + return @directory unless @directory.nil? - @directory = ::File.lstat(@full_path).directory? - rescue ::SystemCallError - @exists ||= false - @directory = false + @directory = if ::File.symlink?(@full_path) + false + else + lstat&.directory? || false + end + end + # :nocov: + else + # @return [Boolean] whether this path is a directory (false for symlinks to directories) + def directory? + return @directory unless @directory.nil? + + @directory = lstat&.directory? || false + end end # @return [Boolean] whether this path exists def exists? - return @exists unless @exists.nil? - - @exists = ::File.exist?(@full_path) - rescue ::SystemCallError - @exists = false + lstat ? true : false end alias_method :original_inspect, :inspect # leftovers:keep @@ -104,11 +111,21 @@ def shebang '' end rescue ::IOError, ::SystemCallError - @exists ||= false + @lstat ||= nil '' ensure file&.close end end + + private + + def lstat + return @lstat if defined?(@lstat) + + @lstat = ::File.lstat(@full_path) + rescue ::SystemCallError + @lstat = nil + end end end diff --git a/lib/path_list/gitconfig/file_parser.rb b/lib/path_list/gitconfig/file_parser.rb index 2d75ec8..62fc7bc 100644 --- a/lib/path_list/gitconfig/file_parser.rb +++ b/lib/path_list/gitconfig/file_parser.rb @@ -51,7 +51,7 @@ def read_file(path) file = ::StringScanner.new(::File.read(path)) until file.eos? - if file.skip(/(\s+|[#;].*\n)/) + if file.skip(/(\s+|[#;].*\r?\n)/) # skip elsif file.skip(/\[core\]/i) self.section = :core @@ -61,9 +61,9 @@ def read_file(path) self.section = include_if(file) ? :include : :not_include elsif file.skip(/\[[\w.]+( "([^\0\\"]|\\(\\{2})*"|\\{2}*)+")?\]/) self.section = :other - elsif section == :core && file.skip(/excludesfile\s*=(\s|\\\n)*/i) + elsif section == :core && file.skip(/excludesfile\s*=(\s|\\\r?\n)*/i) self.value = scan_value(file) - elsif section == :include && file.skip(/path\s*=(\s|\\\n)*/) + elsif section == :include && file.skip(/path\s*=(\s|\\\r?\n)*/) include_path = scan_value(file) value = self.class.parse( @@ -73,9 +73,9 @@ def read_file(path) ) self.value = value if value self.section = :include - elsif file.skip(/[a-zA-Z0-9]\w*\s*([#;].*)?\n/) + elsif file.skip(/[a-zA-Z0-9]\w*\s*([#;].*)?\r?\n/) nil - elsif file.skip(/[a-zA-Z0-9]\w*\s*=(\s|\\\n)*/) + elsif file.skip(/[a-zA-Z0-9]\w*\s*=(\s|\\\r?\n)*/) skip_value(file) else raise ParseError.new('Unexpected character', scanner: file, path: path) @@ -84,7 +84,7 @@ def read_file(path) end def scan_condition_value(file) - if file.scan(/([^\0\\\n"]|\\(\\{2})*"|\\{2}*)+(?="\])/) + if file.scan(/([^\0\\\r\n"]|\\(\\{2})*"|\\{2}*)+(?="\])/) value = file.matched file.skip(/"\]/) value @@ -94,7 +94,7 @@ def scan_condition_value(file) end def skip_condition_value(file) - unless file.skip(/([^\0\\\n"]|\\(\\{2})*"|\\{2}*)+"\]/) + unless file.skip(/([^\0\\\r\n"]|\\(\\{2})*"|\\{2}*)+"\]/) raise ParseError.new('Unexpected character in condition', scanner: file, path: path) end end @@ -135,7 +135,7 @@ def gitdir?(gitdir, path:, case_insensitive: false) def scan_value(file) value = +'' until file.eos? - if file.skip(/\\\n/) + if file.skip(/\\\r?\n/) # continue elsif file.skip(/\\\\/) value << '\\' @@ -152,7 +152,7 @@ def scan_value(file) elsif within_quotes if file.skip(/"/) self.within_quotes = false - elsif file.scan(/[^"\\\n]+/) + elsif file.scan(/[^"\\\n\r]+/) value << file.matched else raise ParseError.new('Unexpected character in quoted value', scanner: file, path: path) @@ -161,7 +161,7 @@ def scan_value(file) self.within_quotes = true elsif file.scan(/[^;#"\s\\]+/) value << file.matched - elsif file.skip(/\s*[;#\n]/) + elsif file.skip(/\s*[;#\n\r]/) break elsif file.scan(/\s+/) # rubocop:disable Lint/DuplicateBranch value << file.matched @@ -179,14 +179,14 @@ def scan_value(file) def skip_value(file) until file.eos? - if file.skip(/\\(?:\n|\\|n|t|b|")/) + if file.skip(/\\(?:\r?\n|\\|n|t|b|")/) nil elsif file.skip(/\\/) raise ParseError.new('Unrecognized escape sequence in value', scanner: file, path: path) elsif within_quotes if file.skip(/"/) self.within_quotes = false - elsif file.skip(/[^"\\\n]+/) + elsif file.skip(/[^"\\\n\r]+/) nil else raise ParseError.new('Unexpected character in quoted value', scanner: file, path: path) @@ -195,7 +195,7 @@ def skip_value(file) self.within_quotes = true elsif file.skip(/[^;#"\s\\]+/) # rubocop:disable Lint/DuplicateBranch nil - elsif file.skip(/\s*[;#\n]/) + elsif file.skip(/\s*[;#\n\r]/) break elsif file.skip(/\s+/) # rubocop:disable Lint/DuplicateBranch nil diff --git a/lib/path_list/gitconfig/parse_error.rb b/lib/path_list/gitconfig/parse_error.rb index 9e36d7d..7312bf3 100644 --- a/lib/path_list/gitconfig/parse_error.rb +++ b/lib/path_list/gitconfig/parse_error.rb @@ -22,7 +22,7 @@ def message chars_before_our_line = @scanner.string.match(/\A(?:.*\n){#{lineno - 1}}/)[0].length col = @scanner.pos - chars_before_our_line @scanner.pos = chars_before_our_line - line = @scanner.scan(/^[^\n]*/) + line = @scanner.scan(/^[^\r\n]*/) @scanner.pos = chars_before_our_line + col <<~MESSAGE diff --git a/lib/path_list/gitignore.rb b/lib/path_list/gitignore.rb index e095901..5962e33 100644 --- a/lib/path_list/gitignore.rb +++ b/lib/path_list/gitignore.rb @@ -31,7 +31,7 @@ def find_root loop do return dir if ::File.exist?("#{dir}/.git") - return pwd if dir.casecmp(home).zero? || dir == '/' + return pwd if dir.casecmp(home).zero? || dir.end_with?('/') dir = ::File.dirname(dir) end diff --git a/lib/path_list/matcher/exact_string/case_insensitive.rb b/lib/path_list/matcher/exact_string/case_insensitive.rb index f186cce..15ec0a9 100644 --- a/lib/path_list/matcher/exact_string/case_insensitive.rb +++ b/lib/path_list/matcher/exact_string/case_insensitive.rb @@ -22,8 +22,6 @@ def initialize(item, polarity) # @param (see Matcher#match) # @return (see Matcher#match) def match(candidate) - puts "#{__FILE__}:#{__LINE__}, @item: #{@item}" - puts "#{__FILE__}:#{__LINE__}, candidate.full_path_downcase: #{candidate.full_path_downcase}" return @polarity if @item == candidate.full_path_downcase end end diff --git a/lib/path_list/matcher/exact_string/set.rb b/lib/path_list/matcher/exact_string/set.rb index a0288e9..806d255 100644 --- a/lib/path_list/matcher/exact_string/set.rb +++ b/lib/path_list/matcher/exact_string/set.rb @@ -33,7 +33,7 @@ def match(candidate) # @return (see Matcher#inspect) def inspect - "#{self.class}.new([#{@set.map(&:inspect).join(', ')}], #{@polarity.inspect})" + "#{self.class}.new([#{@set.to_a.sort.map(&:inspect).join(', ')}], #{@polarity.inspect})" end # @return set [Set] diff --git a/lib/path_list/matcher/exact_string/set/case_insensitive.rb b/lib/path_list/matcher/exact_string/set/case_insensitive.rb index 3560081..fd89630 100644 --- a/lib/path_list/matcher/exact_string/set/case_insensitive.rb +++ b/lib/path_list/matcher/exact_string/set/case_insensitive.rb @@ -17,8 +17,6 @@ def initialize(set, polarity) # @param (see Matcher#match) # @return (see Matcher#match) def match(candidate) - puts "#{__FILE__}:#{__LINE__}, @set: #{@set}" - puts "#{__FILE__}:#{__LINE__}, candidate.full_path_downcase: #{candidate.full_path_downcase}" @polarity if @set.include?(candidate.full_path_downcase) end end diff --git a/lib/path_list/matcher/path_regexp.rb b/lib/path_list/matcher/path_regexp.rb index 7073d24..fb43c58 100644 --- a/lib/path_list/matcher/path_regexp.rb +++ b/lib/path_list/matcher/path_regexp.rb @@ -21,8 +21,6 @@ def self.build(regexp_tokens, polarity) # @param (see Matcher#match) # @return (see Matcher#match) def match(candidate) - puts "#{__FILE__}:#{__LINE__}, @regexp: #{@regexp}" - puts "#{__FILE__}:#{__LINE__}, candidate.full_path: #{candidate.full_path}" @polarity if @regexp.match?(candidate.full_path) end diff --git a/lib/path_list/matcher/path_regexp/case_insensitive.rb b/lib/path_list/matcher/path_regexp/case_insensitive.rb index 87839e9..b6b6773 100644 --- a/lib/path_list/matcher/path_regexp/case_insensitive.rb +++ b/lib/path_list/matcher/path_regexp/case_insensitive.rb @@ -8,8 +8,6 @@ class CaseInsensitive < PathRegexp # @param (see Matcher#match) # @return (see Matcher#match) def match(candidate) - puts "#{__FILE__}:#{__LINE__}, @regexp: #{@regexp}" - puts "#{__FILE__}:#{__LINE__}, candidate.full_path: #{candidate.full_path}" @polarity if @regexp.match?(candidate.full_path_downcase) end diff --git a/lib/path_list/pattern_parser/gitignore.rb b/lib/path_list/pattern_parser/gitignore.rb index 6a22efd..9e21a50 100644 --- a/lib/path_list/pattern_parser/gitignore.rb +++ b/lib/path_list/pattern_parser/gitignore.rb @@ -25,9 +25,10 @@ def initialize(pattern, polarity, root) @rule_polarity = polarity @root = root - @dir_only = false + @dir_only ||= false @emitted = false @return = nil + @anchored ||= false end # @api private @@ -50,10 +51,12 @@ def implicit_matcher private def prepare_regexp_builder - @re = if @root && @root != '/' - TokenRegexp::Path.new_from_path(@root, [:dir, :any_dir]) + @re = if @root.nil? + TokenRegexp::Path.new([:start_anchor]) + elsif @root.end_with?('/') + TokenRegexp::Path.new_from_path(@root, [:any_dir]) else - TokenRegexp::Path.new([:start_anchor, :dir, :any_dir]) + TokenRegexp::Path.new_from_path(@root, [:dir, :any_dir]) end @start_any_dir_position = @re.length - 1 @@ -182,7 +185,7 @@ def process_rule blank! if @s.hash? negated! if @s.exclamation_mark? prepare_regexp_builder - anchored! if @s.slash? + anchored! if !@anchored && @s.slash? catch :break do loop do diff --git a/lib/path_list/pattern_parser/glob_gitignore.rb b/lib/path_list/pattern_parser/glob_gitignore.rb index 5b5dc81..a7a5097 100644 --- a/lib/path_list/pattern_parser/glob_gitignore.rb +++ b/lib/path_list/pattern_parser/glob_gitignore.rb @@ -20,6 +20,10 @@ class PatternParser # - Patterns containing with `/../` are resolved relative to the `root:` directory # - Patterns beginning with `*` (or `!*`) will match any descendant of the `root:` directory # - Other patterns match children (not descendants) of the `root:` directory + # - Additionally, on windows + # - \ is treated as a path separator, not an escape character. + # There is no cross-platform escape character when using :glob_gitignore format. + # - Patterns beginning with `c:/`, `d:\`, or `!c:/`, or etc are absolute. # @example # PathList.only(ARGV, format: :glob_gitignore) # PathList.only( @@ -33,9 +37,13 @@ class PatternParser # format: :glob_gitignore # ).to_a # PathList.only('./relative_to_root_dir', format: :glob_gitignore, root: './subdir') + # # on windows + # PathList.only('c:\root\path', 'relative\to\current\dir, format: :glob_gitignore) # @see https://git-scm.com/docs/gitignore#_pattern_format # @see ::PathList::PatternParser::Gitignore class GlobGitignore < Gitignore + Autoloader.autoload(self) + # @api private # @param pattern [String] # @param polarity [:ignore, :allow] @@ -44,13 +52,15 @@ def initialize(pattern, polarity, root) pattern = +'' if pattern.start_with?('#') negated_sigil = '!' if pattern.delete_prefix!('!') if pattern.start_with?('*') - pattern = "#{negated_sigil}#{pattern}" - elsif pattern.match?(%r{(?:\A[~/]|\A\.{1,2}/|(?:[^\\]|\A)(?:\\{2})*/\.\./)}) - dir_only! if pattern.match?(%r{/\s*\z}) # expand_path will remove it + pattern = "#{negated_sigil}#{pattern.tr(::File::ALT_SEPARATOR.to_s, ::File::SEPARATOR)}" + elsif pattern.match?(EXPANDABLE_PATH) + dir_only! if pattern.match?(%r{[/\\]\s*\z}) # expand_path will remove it + pattern = "#{negated_sigil}#{CanonicalPath.full_path_from(pattern, root)}" - root = '/' + root = nil + @anchored = true else - pattern = "#{negated_sigil}/#{pattern}" + pattern = "#{negated_sigil}/#{pattern.tr(::File::ALT_SEPARATOR.to_s, ::File::SEPARATOR)}" end super(pattern, polarity, root) diff --git a/lib/path_list/pattern_parser/glob_gitignore/expandable_path.rb b/lib/path_list/pattern_parser/glob_gitignore/expandable_path.rb new file mode 100644 index 0000000..1d374b7 --- /dev/null +++ b/lib/path_list/pattern_parser/glob_gitignore/expandable_path.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class PathList + class PatternParser + class GlobGitignore + EXPANDABLE_PATH = if File.expand_path('/') == '/' + %r{(?:\A(?:[~/]|\.{1,2}(?:/|\z))|(?:[^\\]|\A)(?:\\{2})*/\.\./)} + # :nocov: + else + # this isn't actually nocov, but it's cov is because i reload the file + %r{(?:\A(?:[~/\\]|[a-zA-Z]:[/\\]|[/\\]{2}|\.{1,2}(?:[/\\]|\z))|[\\/]\.\.[/\\])} + # :nocov: + end + end + end +end diff --git a/lib/path_list/token_regexp.rb b/lib/path_list/token_regexp.rb index ec7dd59..77591ec 100644 --- a/lib/path_list/token_regexp.rb +++ b/lib/path_list/token_regexp.rb @@ -9,7 +9,7 @@ class EscapedString < ::String; end Autoloader.autoload(self) # @return [Array] - attr_reader :parts + attr_accessor :parts # @param parts [Array] def initialize(parts = []) @@ -72,9 +72,6 @@ def append_string(value) append_part(value) end - protected - # @param value [Array] - attr_writer :parts end end diff --git a/lib/path_list/token_regexp/compress.rb b/lib/path_list/token_regexp/compress.rb index 884fa9d..7bda457 100644 --- a/lib/path_list/token_regexp/compress.rb +++ b/lib/path_list/token_regexp/compress.rb @@ -8,7 +8,6 @@ class << self # @param parts [Array] # @return [Array] def compress!(parts) - puts "#{__FILE__}:#{__LINE__}, parts (before): #{parts}" loop do next if compress_any!(parts) next if compress_any_dir!(parts) @@ -18,7 +17,6 @@ def compress!(parts) break end - puts "#{__FILE__}:#{__LINE__}, parts (after): #{parts}" end private diff --git a/lib/path_list/token_regexp/path.rb b/lib/path_list/token_regexp/path.rb index aa83ab6..3e3020d 100644 --- a/lib/path_list/token_regexp/path.rb +++ b/lib/path_list/token_regexp/path.rb @@ -8,13 +8,17 @@ class Path < TokenRegexp # @param tail [Array] # @return [TokenRegexp::Path] def self.new_from_path(path, tail = [:end_anchor]) - new( - [:start_anchor] + - path.delete_prefix('/').split('/').flat_map do |part| - [:dir, CanonicalPath.case_insensitive? ? part.downcase : part] - end + - tail - ) + parts = [:start_anchor] + split = path.split('/') + (parts << (CanonicalPath.case_insensitive? ? split[0].downcase : split[0])) if split[0] && !split[0].empty? + (parts << :dir) if split.length == 1 || (split.empty? && path == '/') + split.drop(1).each do |part| + parts << :dir + parts << (CanonicalPath.case_insensitive? ? part.downcase : part) + end + parts.concat(tail) + + new(parts) end # @return [Boolean] @@ -34,12 +38,15 @@ def compress # @return [Array] def ancestors - prev_rule = [:start_anchor] - rules = [self.class.new([:start_anchor, :dir, :end_anchor])] + prev_rule = [] + rules = [] parts = @parts any_dir_index = parts.index(:any) || parts.index(:any_dir) parts = parts[0, any_dir_index] + [:any, :dir] if any_dir_index - parts.slice_before(:dir).to_a[1...-1].each do |chunk| + parts = parts.slice_before(:dir) + prev_rule.concat(parts.first) + rules << self.class.new(prev_rule + [:dir, :end_anchor]) + parts.to_a[1...-1].each do |chunk| prev_rule.concat(chunk) rules << self.class.new(prev_rule + [:end_anchor]) end diff --git a/spec/candidate_spec.rb b/spec/candidate_spec.rb index 345cfea..48027ff 100644 --- a/spec/candidate_spec.rb +++ b/spec/candidate_spec.rb @@ -54,12 +54,12 @@ before { create_file_list 'foo' } it 'is memoized when true' do - allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:lstat).and_call_original expect(candidate.exists?).to be true - expect(File).to have_received(:exist?).once + expect(File).to have_received(:lstat).once expect(candidate.exists?).to be true - expect(File).to have_received(:exist?).once + expect(File).to have_received(:lstat).once end end @@ -67,22 +67,22 @@ let(:full_path) { './foo' } it 'is memoized when false' do - allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:lstat).and_call_original expect(candidate.exists?).to be false - expect(File).to have_received(:exist?).with('./foo').once + expect(File).to have_received(:lstat).with('./foo').once expect(candidate.exists?).to be false - expect(File).to have_received(:exist?).with('./foo').once + expect(File).to have_received(:lstat).with('./foo').once end it 'is false when there is an error' do - allow(File).to receive(:exist?).and_call_original - allow(File).to receive(:exist?).with(full_path).and_raise(Errno::EACCES) + allow(File).to receive(:lstat).and_call_original + allow(File).to receive(:lstat).with(full_path).and_raise(Errno::EACCES) expect(candidate.exists?).to be false - expect(File).to have_received(:exist?).once + expect(File).to have_received(:lstat).with('./foo').once expect(candidate.exists?).to be false - expect(File).to have_received(:exist?).once + expect(File).to have_received(:lstat).with('./foo').once end end end @@ -131,6 +131,20 @@ end end + describe '#directory?', :aggregate_failures do + within_temp_dir + + it 'treats soft links to directories as files rather than the directories they point to' do + create_file_list 'foo_target/foo_child' + create_symlink('foo' => 'foo_target') + + candidate = described_class.new(File.expand_path('foo')) + expect(File.symlink?('./foo')).to be true + # expect(candidate.send(:lstat)).to have_attributes(directory?: false, symlink?: true) + expect(candidate).not_to be_directory + end + end + describe '#shebang' do context 'when reading from the file system' do within_temp_dir @@ -144,7 +158,9 @@ puts('it saves the first 64 characters by default, not that many') RUBY - expect(candidate.shebang).to eq "#!/usr/bin/env ruby\n\nputs('it saves the first 64 characters by d" + expect(candidate.shebang).to eq("#!/usr/bin/env ruby\n\nputs('it saves the first 64 characters by d") + .or(eq("#!/usr/bin/env ruby\r\n\r\nputs('it saves the first 64 characters by")) + .or(eq("#!/usr/bin/env ruby\n\nputs('it saves the first 64 characters by")) end it 'returns the first line of a long shebang' do @@ -154,8 +170,8 @@ puts('yes') RUBY - expect(candidate.shebang) - .to eq "#!/usr/bin/env ruby -w --disable-gems --verbose --enable-frozen-string-literal\n" + expect(candidate.shebang.chomp) + .to eq '#!/usr/bin/env ruby -w --disable-gems --verbose --enable-frozen-string-literal' end it 'returns the first line of one line if it has a shebang' do @@ -163,7 +179,7 @@ #!/usr/bin/env ruby RUBY - expect(candidate.shebang).to eq "#!/usr/bin/env ruby\n" + expect(candidate.shebang.chomp).to eq '#!/usr/bin/env ruby' end it 'returns the first line of one line if it has a shebang and no trailing newline' do diff --git a/spec/gitconfig/core_excludesfile_spec.rb b/spec/gitconfig/core_excludesfile_spec.rb index 8a1c824..f2df7e9 100644 --- a/spec/gitconfig/core_excludesfile_spec.rb +++ b/spec/gitconfig/core_excludesfile_spec.rb @@ -5,7 +5,7 @@ let(:default_ignore_path) { "#{home}/.config/git/ignore" } - let(:home) { Dir.home } + let(:home) { File.expand_path(Dir.home) } let(:root) { Dir.pwd } let(:config_content) { "[core]\n\texcludesfile = #{excludesfile_value}\n" } @@ -66,7 +66,15 @@ let(:excludesfile_value) { '"~/.global_gitignore_in_quotes' } # no closing quote it 'returns nil instead of default' do + allow(Warning).to receive(:warn) expect(subject).to be_nil + expect(Warning).to have_received(:warn).with(<<~MESSAGE) + PathList gitconfig parser failed + Unexpected character in quoted value + #{Dir.pwd}/.git/config:2:46 + \texcludesfile = "~/.global_gitignore_in_quotes + ^ + MESSAGE end context 'when there is a valid global config file defined' do @@ -78,7 +86,15 @@ end it 'still returns nil because any config is invalid' do + allow(Warning).to receive(:warn) expect(subject).to be_nil + expect(Warning).to have_received(:warn).with(<<~MESSAGE) + PathList gitconfig parser failed + Unexpected character in quoted value + #{Dir.pwd}/.git/config:2:46 + \texcludesfile = "~/.global_gitignore_in_quotes + ^ + MESSAGE end end end @@ -117,7 +133,7 @@ end it 'returns a literal unquoted value for the path' do - expect(subject).to eq '/system/gitignore' + expect(subject).to eq "#{FSROOT}system/gitignore" end context 'with GIT_CONFIG_NOSYSTEM set' do @@ -167,7 +183,12 @@ end it 'returns nil, because git considers that a fatal error' do + allow(Warning).to receive(:warn) expect(subject).to be_nil + expect(Warning).to have_received(:warn).with(<<~MESSAGE.chomp) + PathList gitconfig parser failed + Invalid value "nonsense" for $GIT_CONFIG_NOSYSTEM + MESSAGE end end end diff --git a/spec/gitconfig/file_parser_spec.rb b/spec/gitconfig/file_parser_spec.rb index 00a91ff..50b0cc1 100644 --- a/spec/gitconfig/file_parser_spec.rb +++ b/spec/gitconfig/file_parser_spec.rb @@ -12,23 +12,27 @@ it 'raises for invalid file' do create_file('[', path: '.gitconfig') - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unexpected character - .gitconfig:1:0 - [ - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unexpected character + .gitconfig:1:0 + [ + ^ + MESSAGE + end end it 'raises for another invalid file' do create_file('x[', path: '.gitconfig') - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unexpected character - .gitconfig:1:0 - x[ - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unexpected character + .gitconfig:1:0 + x[ + ^ + MESSAGE + end end it 'returns nil for nonexistent file' do @@ -424,12 +428,14 @@ excludesfile = "~/gitignore GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unexpected character in quoted value - .gitconfig:2:29 - excludesfile = "~/gitignore - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unexpected character in quoted value + .gitconfig:2:29 + excludesfile = "~/gitignore + ^ + MESSAGE + end end it 'raises for file with unclosed quote and no trailing newline' do @@ -438,12 +444,14 @@ excludesfile = "~/gitignore GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unclosed quoted value - .gitconfig:2:29 - excludesfile = "~/gitignore - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unclosed quoted value + .gitconfig:2:29 + excludesfile = "~/gitignore + ^ + MESSAGE + end end it 'raises for file with excludesfile after attributesfile with unclosed quote' do @@ -453,12 +461,14 @@ excludesfile = ~/gitignore GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unexpected character in quoted value - .gitconfig:2:35 - attributesfile = "~/gitattributes - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unexpected character in quoted value + .gitconfig:2:35 + attributesfile = "~/gitattributes + ^ + MESSAGE + end end it 'raises for file with excludesfile before attributesfile with unclosed quote and no trailing newline' do @@ -468,12 +478,14 @@ attributesfile = "~/gitattributes GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unclosed quoted value - .gitconfig:3:35 - attributesfile = "~/gitattributes - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unclosed quoted value + .gitconfig:3:35 + attributesfile = "~/gitattributes + ^ + MESSAGE + end end it 'raises for file with unclosed quote followed by more stuff' do @@ -483,12 +495,14 @@ mergeoptions = --no-edit GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unexpected character in quoted value - .gitconfig:2:29 - excludesfile = "~/gitignore - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unexpected character in quoted value + .gitconfig:2:29 + excludesfile = "~/gitignore + ^ + MESSAGE + end end it 'raises for file with quote containing a newline' do @@ -498,12 +512,14 @@ ignore" GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unexpected character in quoted value - .gitconfig:2:23 - excludesfile = "~/git - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unexpected character in quoted value + .gitconfig:2:23 + excludesfile = "~/git + ^ + MESSAGE + end end it 'raises for file with excludesfile after attributesfile with quoted newline' do @@ -514,12 +530,14 @@ excludesfile = ~/gitignore GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unexpected character in quoted value - .gitconfig:2:25 - attributesfile = "~/git - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unexpected character in quoted value + .gitconfig:2:25 + attributesfile = "~/git + ^ + MESSAGE + end end it 'raises for file with invalid \ escape' do @@ -528,12 +546,14 @@ excludesfile = "~/gitignore\\x" GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unrecognized escape sequence in value - .gitconfig:2:30 - excludesfile = "~/gitignore\\x" - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unrecognized escape sequence in value + .gitconfig:2:30 + excludesfile = "~/gitignore\\x" + ^ + MESSAGE + end end it 'raises for file with excludesfile after attributesfile with invalid escape' do @@ -543,12 +563,14 @@ excludesfile = ~/gitignore GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unrecognized escape sequence in value - .gitconfig:2:26 - attributesfile = "~/git\\xattributes - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unrecognized escape sequence in value + .gitconfig:2:26 + attributesfile = "~/git\\xattributes + ^ + MESSAGE + end end it 'returns value for file when included' do @@ -671,12 +693,14 @@ excludesfile = ~/.gitignore GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unexpected character in condition - .gitconfig:1:21 - [includeif "onbranch:ma - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unexpected character in condition + .gitconfig:1:21 + [includeif "onbranch:ma + ^ + MESSAGE + end end it 'raises for file when includeif nonsense with newline' do @@ -691,12 +715,14 @@ excludesfile = ~/.gitignore GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unexpected character in condition - .gitconfig:1:12 - [includeif "nonsense - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unexpected character in condition + .gitconfig:1:12 + [includeif "nonsense + ^ + MESSAGE + end end it 'raises for file when includeif onbranch with null' do @@ -710,12 +736,14 @@ excludesfile = ~/.gitignore GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE - Unexpected character in condition - .gitconfig:1:21 - [includeif "onbranch:ma\0in"] - ^ - MESSAGE + expect { described_class.parse('.gitconfig') }.to raise_error(PathList::Gitconfig::ParseError) do |e| + expect(e.message).to eq <<~MESSAGE + Unexpected character in condition + .gitconfig:1:21 + [includeif "onbranch:ma\0in"] + ^ + MESSAGE + end end it 'returns value for file when includeif gitdir matches leading **/' do @@ -816,9 +844,8 @@ path = .gitconfig GITCONFIG - expect { described_class.parse('.gitconfig') }.to raise_error PathList::Gitconfig::ParseError, <<~MESSAGE.chomp - Include level too deep #{Dir.pwd}/.gitconfig - MESSAGE + expect { described_class.parse('.gitconfig') } + .to raise_error(PathList::Gitconfig::ParseError, "Include level too deep #{Dir.pwd}/.gitconfig") end it 'returns value for file when included nestedly' do diff --git a/spec/ignore_or_include_spec.rb b/spec/ignore_or_include_spec.rb index 3945d08..c1d1b10 100644 --- a/spec/ignore_or_include_spec.rb +++ b/spec/ignore_or_include_spec.rb @@ -53,7 +53,8 @@ end end - describe 'literal backslashes in filenames' do + # can't have literal backslashes in filenames in windows + describe 'literal backslashes in filenames', skip: windows? do it "never matches backslashes when they're not in the pattern" do gitignore 'foo' @@ -89,11 +90,12 @@ end end - describe 'Trailing spaces are ignored unless they are quoted with backslash ("\")' do + # can't end with literal backslashes in filenames in windows + describe 'Trailing spaces are ignored unless they are quoted with backslash ("\")', skip: windows? do it 'ignores trailing spaces in the gitignore file' do gitignore 'foo ' - expect(subject).not_to match_files('foo ', 'foo ') + expect(subject).not_to match_files('foo ', 'foo ', 'foo\\') expect(subject).to match_files('foo') end @@ -142,13 +144,6 @@ expect(subject).not_to match_files('bar/foo', 'baz/foo', 'bar/baz', create: false) expect(subject).to match_files('foo/bar', create: false) end - - it 'handles this specific edge case i stumbled across' do - gitignore "Ȋ/\nfoo/" - - expect(subject).not_to match_files('bar/foo', 'baz/foo', 'bar/baz', create: false) - expect(subject).to match_files('foo/bar', create: false) - end end end @@ -945,6 +940,13 @@ end end end + + it 'handles this specific edge case i stumbled across' do + gitignore "Ȋ/\nfoo/" + + expect(subject).not_to match_files('bar/foo', 'baz/foo', 'bar/baz') + expect(subject).to match_files('foo/bar') + end end describe '.gitignore' do diff --git a/spec/matcher/all/allow_spec.rb b/spec/matcher/all/allow_spec.rb index e950de1..dfef78b 100644 --- a/spec/matcher/all/allow_spec.rb +++ b/spec/matcher/all/allow_spec.rb @@ -3,7 +3,7 @@ RSpec.describe PathList::Matcher::All::Allow do subject { described_class.new(matchers) } - let(:matcher_allow_a) { instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1, polarity: :allow) } + let(:matcher_allow_a) { instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1.1, polarity: :allow) } let(:matcher_allow_b) { instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2, polarity: :allow) } let(:matcher_allow_c) { instance_double(PathList::Matcher, 'matcher_allow_c', weight: 3, polarity: :allow) } let(:matcher_allow_d) { instance_double(PathList::Matcher, 'matcher_allow_d', weight: 4, polarity: :allow) } @@ -45,7 +45,7 @@ describe '#weight' do it 'is the matchers plus 1' do - expect(subject.weight).to eq 7 + expect(subject.weight).to eq 7.1 end end diff --git a/spec/matcher/all/ignore_spec.rb b/spec/matcher/all/ignore_spec.rb index 3bb0474..f7f7044 100644 --- a/spec/matcher/all/ignore_spec.rb +++ b/spec/matcher/all/ignore_spec.rb @@ -3,7 +3,7 @@ RSpec.describe PathList::Matcher::All::Ignore do subject { described_class.new(matchers) } - let(:matcher_ignore_a) { instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1, polarity: :ignore) } + let(:matcher_ignore_a) { instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1.1, polarity: :ignore) } let(:matcher_ignore_b) { instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2, polarity: :ignore) } let(:matcher_ignore_c) { instance_double(PathList::Matcher, 'matcher_ignore_c', weight: 3, polarity: :ignore) } let(:matcher_ignore_d) { instance_double(PathList::Matcher, 'matcher_ignore_d', weight: 4, polarity: :ignore) } @@ -45,7 +45,7 @@ describe '#weight' do it 'is the matchers plus 1' do - expect(subject.weight).to eq 7 + expect(subject.weight).to eq 7.1 end end diff --git a/spec/matcher/all/two_spec.rb b/spec/matcher/all/two_spec.rb index cb58ed9..74cea45 100644 --- a/spec/matcher/all/two_spec.rb +++ b/spec/matcher/all/two_spec.rb @@ -3,14 +3,14 @@ RSpec.describe PathList::Matcher::All::Two do subject { described_class.new(matchers) } - let(:matcher_allow_a) { instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1, polarity: :allow) } - let(:matcher_allow_b) { instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2, polarity: :allow) } + let(:matcher_allow_a) { instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1.1, polarity: :allow) } + let(:matcher_allow_b) { instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2.1, polarity: :allow) } - let(:matcher_ignore_a) { instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1, polarity: :ignore) } - let(:matcher_ignore_b) { instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2, polarity: :ignore) } + let(:matcher_ignore_a) { instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1.2, polarity: :ignore) } + let(:matcher_ignore_b) { instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2.2, polarity: :ignore) } - let(:matcher_mixed_a) { instance_double(PathList::Matcher, 'matcher_mixed_a', weight: 1, polarity: :mixed) } - let(:matcher_mixed_b) { instance_double(PathList::Matcher, 'matcher_mixed_b', weight: 2, polarity: :mixed) } + let(:matcher_mixed_a) { instance_double(PathList::Matcher, 'matcher_mixed_a', weight: 1.3, polarity: :mixed) } + let(:matcher_mixed_b) { instance_double(PathList::Matcher, 'matcher_mixed_b', weight: 2.3, polarity: :mixed) } let(:matchers) { [matcher_allow_a, matcher_ignore_b] } @@ -47,7 +47,7 @@ describe '#weight' do it 'is the matchers plus 1' do - expect(subject.weight).to eq 4 + expect(subject.weight).to be_within(0.001).of(4.3) end end diff --git a/spec/matcher/all_spec.rb b/spec/matcher/all_spec.rb index 186f860..821ac34 100644 --- a/spec/matcher/all_spec.rb +++ b/spec/matcher/all_spec.rb @@ -3,17 +3,17 @@ RSpec.describe PathList::Matcher::All do subject { described_class.new(matchers) } - let(:matcher_allow_a) { instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1, polarity: :allow) } - let(:matcher_allow_b) { instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2, polarity: :allow) } - let(:matcher_allow_c) { instance_double(PathList::Matcher, 'matcher_allow_c', weight: 3, polarity: :allow) } + let(:matcher_allow_a) { instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1.1, polarity: :allow) } + let(:matcher_allow_b) { instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2.1, polarity: :allow) } + let(:matcher_allow_c) { instance_double(PathList::Matcher, 'matcher_allow_c', weight: 3.1, polarity: :allow) } - let(:matcher_ignore_a) { instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1, polarity: :ignore) } - let(:matcher_ignore_b) { instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2, polarity: :ignore) } - let(:matcher_ignore_c) { instance_double(PathList::Matcher, 'matcher_ignore_c', weight: 3, polarity: :ignore) } + let(:matcher_ignore_a) { instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1.2, polarity: :ignore) } + let(:matcher_ignore_b) { instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2.2, polarity: :ignore) } + let(:matcher_ignore_c) { instance_double(PathList::Matcher, 'matcher_ignore_c', weight: 3.2, polarity: :ignore) } - let(:matcher_mixed_a) { instance_double(PathList::Matcher, 'matcher_mixed_a', weight: 1, polarity: :mixed) } - let(:matcher_mixed_b) { instance_double(PathList::Matcher, 'matcher_mixed_b', weight: 2, polarity: :mixed) } - let(:matcher_mixed_c) { instance_double(PathList::Matcher, 'matcher_mixed_c', weight: 3, polarity: :mixed) } + let(:matcher_mixed_a) { instance_double(PathList::Matcher, 'matcher_mixed_a', weight: 1.3, polarity: :mixed) } + let(:matcher_mixed_b) { instance_double(PathList::Matcher, 'matcher_mixed_b', weight: 2.3, polarity: :mixed) } + let(:matcher_mixed_c) { instance_double(PathList::Matcher, 'matcher_mixed_c', weight: 3.3, polarity: :mixed) } let(:matchers) { [matcher_allow_a, matcher_ignore_b, matcher_mixed_c] } @@ -113,7 +113,7 @@ PathList::Matcher::Invalid ])).to be_like( described_class.new([ - matcher_allow_a, matcher_ignore_a, PathList::Matcher::Invalid, matcher_mixed_a + PathList::Matcher::Invalid, matcher_allow_a, matcher_ignore_a, matcher_mixed_a ]) ) end @@ -204,7 +204,7 @@ describe '#weight' do it 'is the matchers plus 1' do - expect(subject.weight).to eq 7 + expect(subject.weight).to eq 7.6 end end diff --git a/spec/matcher/any/allow_spec.rb b/spec/matcher/any/allow_spec.rb index 8fde513..dfb55e3 100644 --- a/spec/matcher/any/allow_spec.rb +++ b/spec/matcher/any/allow_spec.rb @@ -4,7 +4,7 @@ subject { described_class.new(matchers) } let(:matcher_allow_a) do - instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1, polarity: :allow, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1.1, polarity: :allow, squashable_with?: false) end let(:matcher_allow_b) do instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2, polarity: :allow, squashable_with?: false) @@ -53,7 +53,7 @@ describe '#weight' do it 'is the matchers halved' do - expect(subject.weight).to eq 3 + expect(subject.weight).to eq 3.05 end end diff --git a/spec/matcher/any/ignore_spec.rb b/spec/matcher/any/ignore_spec.rb index 7f83783..019e1c8 100644 --- a/spec/matcher/any/ignore_spec.rb +++ b/spec/matcher/any/ignore_spec.rb @@ -4,7 +4,7 @@ subject { described_class.new(matchers) } let(:matcher_ignore_a) do - instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1, polarity: :ignore, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1.1, polarity: :ignore, squashable_with?: false) end let(:matcher_ignore_b) do instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2, polarity: :ignore, squashable_with?: false) @@ -53,7 +53,7 @@ describe '#weight' do it 'is the matchers halved' do - expect(subject.weight).to eq 3 + expect(subject.weight).to eq 3.05 end end diff --git a/spec/matcher/any/two_spec.rb b/spec/matcher/any/two_spec.rb index 73e883b..91a5d6d 100644 --- a/spec/matcher/any/two_spec.rb +++ b/spec/matcher/any/two_spec.rb @@ -4,27 +4,27 @@ subject { described_class.new(matchers) } let(:matcher_allow_a) do - instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1, polarity: :allow, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1.1, polarity: :allow, squashable_with?: false) end let(:matcher_allow_b) do - instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2, polarity: :allow, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2.1, polarity: :allow, squashable_with?: false) end let(:matcher_ignore_a) do - instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1, polarity: :ignore, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1.2, polarity: :ignore, squashable_with?: false) end let(:matcher_ignore_b) do - instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2, polarity: :ignore, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2.2, polarity: :ignore, squashable_with?: false) end let(:matcher_mixed_a) do - instance_double(PathList::Matcher, 'matcher_mixed_a', weight: 1, polarity: :mixed, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_mixed_a', weight: 1.3, polarity: :mixed, squashable_with?: false) end let(:matcher_mixed_b) do - instance_double(PathList::Matcher, 'matcher_mixed_b', weight: 2, polarity: :mixed, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_mixed_b', weight: 2.3, polarity: :mixed, squashable_with?: false) end let(:matchers) { [matcher_allow_a, matcher_ignore_b] } @@ -62,7 +62,7 @@ describe '#weight' do it 'is the matchers halved' do - expect(subject.weight).to eq 1.5 + expect(subject.weight).to be_within(0.001).of(1.65) end end diff --git a/spec/matcher/any_spec.rb b/spec/matcher/any_spec.rb index 315ad0a..9fbcde9 100644 --- a/spec/matcher/any_spec.rb +++ b/spec/matcher/any_spec.rb @@ -4,33 +4,33 @@ subject { described_class.new(matchers) } let(:matcher_allow_a) do - instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1, polarity: :allow, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1.1, polarity: :allow, squashable_with?: false) end let(:matcher_allow_b) do - instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2, polarity: :allow, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2.1, polarity: :allow, squashable_with?: false) end let(:matcher_allow_c) do - instance_double(PathList::Matcher, 'matcher_allow_c', weight: 3, polarity: :allow, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_allow_c', weight: 3.1, polarity: :allow, squashable_with?: false) end let(:matcher_ignore_a) do - instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1, polarity: :ignore, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1.2, polarity: :ignore, squashable_with?: false) end let(:matcher_ignore_b) do - instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2, polarity: :ignore, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2.2, polarity: :ignore, squashable_with?: false) end let(:matcher_ignore_c) do - instance_double(PathList::Matcher, 'matcher_ignore_c', weight: 3, polarity: :ignore, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_ignore_c', weight: 3.2, polarity: :ignore, squashable_with?: false) end let(:matcher_mixed_a) do - instance_double(PathList::Matcher, 'matcher_mixed_a', weight: 1, polarity: :mixed, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_mixed_a', weight: 1.3, polarity: :mixed, squashable_with?: false) end let(:matcher_mixed_b) do - instance_double(PathList::Matcher, 'matcher_mixed_b', weight: 2, polarity: :mixed, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_mixed_b', weight: 2.3, polarity: :mixed, squashable_with?: false) end let(:matcher_mixed_c) do - instance_double(PathList::Matcher, 'matcher_mixed_c', weight: 3, polarity: :mixed, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_mixed_c', weight: 3.3, polarity: :mixed, squashable_with?: false) end let(:matchers) { [matcher_allow_a, matcher_ignore_b, matcher_mixed_c] } @@ -244,7 +244,7 @@ describe '#weight' do it 'is the matchers halved' do - expect(subject.weight).to eq 3 + expect(subject.weight).to eq 3.3 end end diff --git a/spec/matcher/exact_string/set_spec.rb b/spec/matcher/exact_string/set_spec.rb index 3355a40..afee606 100644 --- a/spec/matcher/exact_string/set_spec.rb +++ b/spec/matcher/exact_string/set_spec.rb @@ -45,7 +45,7 @@ describe '#inspect' do it do expect(subject) - .to have_inspect_value 'PathList::Matcher::ExactString::Set.new(["/one/path", "/Two/Path"], :allow)' + .to have_inspect_value 'PathList::Matcher::ExactString::Set.new(["/Two/Path", "/one/path"], :allow)' end end diff --git a/spec/matcher/last_match/allow_spec.rb b/spec/matcher/last_match/allow_spec.rb index 0870e67..089df85 100644 --- a/spec/matcher/last_match/allow_spec.rb +++ b/spec/matcher/last_match/allow_spec.rb @@ -4,7 +4,7 @@ subject { described_class.new(matchers) } let(:matcher_allow_a) do - instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1, polarity: :allow, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1.1, polarity: :allow, squashable_with?: false) end let(:matcher_allow_b) do instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2, polarity: :allow, squashable_with?: false) @@ -53,7 +53,7 @@ describe '#weight' do it 'is the matchers halved' do - expect(subject.weight).to eq 3 + expect(subject.weight).to eq 3.05 end end diff --git a/spec/matcher/last_match/ignore_spec.rb b/spec/matcher/last_match/ignore_spec.rb index b4175e1..c958a53 100644 --- a/spec/matcher/last_match/ignore_spec.rb +++ b/spec/matcher/last_match/ignore_spec.rb @@ -4,7 +4,7 @@ subject { described_class.new(matchers) } let(:matcher_ignore_a) do - instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1, polarity: :ignore, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1.1, polarity: :ignore, squashable_with?: false) end let(:matcher_ignore_b) do instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2, polarity: :ignore, squashable_with?: false) @@ -53,7 +53,7 @@ describe '#weight' do it 'is the matchers halved' do - expect(subject.weight).to eq 3 + expect(subject.weight).to eq 3.05 end end diff --git a/spec/matcher/last_match/two_spec.rb b/spec/matcher/last_match/two_spec.rb index 3529980..055562b 100644 --- a/spec/matcher/last_match/two_spec.rb +++ b/spec/matcher/last_match/two_spec.rb @@ -4,27 +4,27 @@ subject { described_class.new(matchers) } let(:matcher_allow_a) do - instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1, polarity: :allow, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1.1, polarity: :allow, squashable_with?: false) end let(:matcher_allow_b) do - instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2, polarity: :allow, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2.1, polarity: :allow, squashable_with?: false) end let(:matcher_ignore_a) do - instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1, polarity: :ignore, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1.2, polarity: :ignore, squashable_with?: false) end let(:matcher_ignore_b) do - instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2, polarity: :ignore, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2.2, polarity: :ignore, squashable_with?: false) end let(:matcher_mixed_a) do - instance_double(PathList::Matcher, 'matcher_mixed_a', weight: 1, polarity: :mixed, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_mixed_a', weight: 1.3, polarity: :mixed, squashable_with?: false) end let(:matcher_mixed_b) do - instance_double(PathList::Matcher, 'matcher_mixed_b', weight: 2, polarity: :mixed, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_mixed_b', weight: 2.3, polarity: :mixed, squashable_with?: false) end let(:matchers) { [matcher_allow_a, matcher_ignore_b] } @@ -62,7 +62,7 @@ describe '#weight' do it 'is the matchers halved' do - expect(subject.weight).to eq 1.5 + expect(subject.weight).to be_within(0.001).of(1.65) end end diff --git a/spec/matcher/last_match_spec.rb b/spec/matcher/last_match_spec.rb index 9e86792..bb339ed 100644 --- a/spec/matcher/last_match_spec.rb +++ b/spec/matcher/last_match_spec.rb @@ -4,33 +4,33 @@ subject { described_class.new(matchers) } let(:matcher_allow_a) do - instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1, polarity: :allow, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_allow_a', weight: 1.1, polarity: :allow, squashable_with?: false) end let(:matcher_allow_b) do - instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2, polarity: :allow, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_allow_b', weight: 2.1, polarity: :allow, squashable_with?: false) end let(:matcher_allow_c) do - instance_double(PathList::Matcher, 'matcher_allow_c', weight: 3, polarity: :allow, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_allow_c', weight: 3.1, polarity: :allow, squashable_with?: false) end let(:matcher_ignore_a) do - instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1, polarity: :ignore, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_ignore_a', weight: 1.2, polarity: :ignore, squashable_with?: false) end let(:matcher_ignore_b) do - instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2, polarity: :ignore, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_ignore_b', weight: 2.2, polarity: :ignore, squashable_with?: false) end let(:matcher_ignore_c) do - instance_double(PathList::Matcher, 'matcher_ignore_c', weight: 3, polarity: :ignore, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_ignore_c', weight: 3.2, polarity: :ignore, squashable_with?: false) end let(:matcher_mixed_a) do - instance_double(PathList::Matcher, 'matcher_mixed_a', weight: 1, polarity: :mixed, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_mixed_a', weight: 1.3, polarity: :mixed, squashable_with?: false) end let(:matcher_mixed_b) do - instance_double(PathList::Matcher, 'matcher_mixed_b', weight: 2, polarity: :mixed, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_mixed_b', weight: 2.3, polarity: :mixed, squashable_with?: false) end let(:matcher_mixed_c) do - instance_double(PathList::Matcher, 'matcher_mixed_c', weight: 3, polarity: :mixed, squashable_with?: false) + instance_double(PathList::Matcher, 'matcher_mixed_c', weight: 3.3, polarity: :mixed, squashable_with?: false) end let(:matchers) { [matcher_allow_a, matcher_ignore_b, matcher_mixed_c] } @@ -286,7 +286,7 @@ describe '#weight' do it 'is the matchers halved' do - expect(subject.weight).to eq 3 + expect(subject.weight).to eq 3.3 end end diff --git a/spec/matcher/path_regexp/case_insensitive_spec.rb b/spec/matcher/path_regexp/case_insensitive_spec.rb index ee0f8ec..c155485 100644 --- a/spec/matcher/path_regexp/case_insensitive_spec.rb +++ b/spec/matcher/path_regexp/case_insensitive_spec.rb @@ -80,13 +80,13 @@ it { is_expected.not_to be_squashable_with(PathList::Matcher::Allow) } it 'is squashable with the same polarity values' do - other = described_class.build([['b']], :allow) + other = described_class.build([['bb']], :allow) expect(subject).to be_squashable_with(other) end it 'is not squashable with a different polarity value' do - other = described_class.build([['b']], :ignore) + other = described_class.build([['bb']], :ignore) expect(subject).not_to be_squashable_with(other) end @@ -95,7 +95,7 @@ describe '#squash' do it 'squashes the regexps together' do subject - other = described_class.build([['b']], polarity) + other = described_class.build([['bb']], polarity) allow(described_class).to receive(:new).and_call_original squashed = subject.squash([subject, other], true) @@ -104,7 +104,7 @@ expect(squashed).not_to be subject expect(squashed).not_to be other - expect(squashed).to be_like(described_class.new(/(?:a|b)/, polarity)) + expect(squashed).to be_like(described_class.new(/(?:a|bb)/, polarity)) end end diff --git a/spec/matcher/path_regexp_spec.rb b/spec/matcher/path_regexp_spec.rb index 2101878..5f7bfb4 100644 --- a/spec/matcher/path_regexp_spec.rb +++ b/spec/matcher/path_regexp_spec.rb @@ -88,13 +88,13 @@ it { is_expected.not_to be_squashable_with(PathList::Matcher::Allow) } it 'is squashable with the same polarity values' do - other = described_class.build([['b']], :allow) + other = described_class.build([['bb']], :allow) expect(subject).to be_squashable_with(other) end it 'is not squashable with a different polarity value' do - other = described_class.build([['b']], :ignore) + other = described_class.build([['bb']], :ignore) expect(subject).not_to be_squashable_with(other) end @@ -103,7 +103,7 @@ describe '#squash' do it 'squashes the regexps together' do subject - other = described_class.build([['b']], polarity) + other = described_class.build([['bb']], polarity) allow(described_class).to receive(:new).and_call_original squashed = subject.squash([subject, other], true) @@ -112,7 +112,7 @@ expect(squashed).not_to be subject expect(squashed).not_to be other - expect(squashed).to be_like(described_class.new(/(?:a|b)/, polarity)) + expect(squashed).to be_like(described_class.new(/(?:a|bb)/, polarity)) end end diff --git a/spec/path_list_spec.rb b/spec/path_list_spec.rb index f3e3718..683de60 100644 --- a/spec/path_list_spec.rb +++ b/spec/path_list_spec.rb @@ -256,7 +256,7 @@ end it 'rescues errors in PathList methods', :aggregate_failures do - expect(subject.include?('foo')).to be false + expect(subject.include?('foo')).to be true expect(subject.match?('foo')).to be true expect(subject.to_a).to contain_exactly('.gitignore', 'foo') end @@ -275,13 +275,13 @@ end it 'rescues errors in PathList methods', :aggregate_failures do - expect(subject.include?('foo')).to be false + expect(subject.include?('foo')).to be true expect(subject.match?('foo')).to be true expect(subject.to_a).to contain_exactly('.gitignore', 'foo', 'foo_target') end it "doesn't rescue the yielded block" do - expect { subject.each { |x| File.read(x) }.to_a }.to raise_error(Errno::ELOOP) + expect { subject.each { |x| File.read(x) } }.to raise_error(Errno::ELOOP) end end diff --git a/spec/pattern_parser/exact_path_spec.rb b/spec/pattern_parser/exact_path_spec.rb index 5b411b6..f7fbe65 100644 --- a/spec/pattern_parser/exact_path_spec.rb +++ b/spec/pattern_parser/exact_path_spec.rb @@ -4,7 +4,7 @@ subject(:matcher) { described_class.new(path, polarity, nil).matcher } let(:case_insensitive) { false } - let(:path) { '/path/to/exact/something' } + let(:path) { "#{FSROOT}path/to/exact/something" } let(:candidate_path) { path } let(:candidate) { PathList::Candidate.new(candidate_path, true) } let(:polarity) { :allow } @@ -20,9 +20,11 @@ expect(matcher).to be_like( PathList::Matcher::Any::Two.new([ PathList::Matcher::MatchIfDir.new( - PathList::Matcher::ExactString::Set.new(['/', '/path', '/path/to', '/path/to/exact'], :allow) + PathList::Matcher::ExactString::Set.new( + [FSROOT, "#{FSROOT}path", "#{FSROOT}path/to", "#{FSROOT}path/to/exact"], :allow + ) ), - PathList::Matcher::PathRegexp.new(%r{\A/path/to/exact/something/}, :allow) + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}path/to/exact/something/}o, :allow) ]) ) end @@ -34,24 +36,29 @@ PathList::Matcher::Any::Two.new([ PathList::Matcher::MatchIfDir.new( PathList::Matcher::ExactString::Set::CaseInsensitive.new( - ['/', '/path', '/path/to', '/path/to/exact'], :allow + [ + FSROOT_LOWER, "#{FSROOT_LOWER}path", "#{FSROOT_LOWER}path/to", + "#{FSROOT_LOWER}path/to/exact" + ], :allow ) ), - PathList::Matcher::PathRegexp::CaseInsensitive.new(%r{\A/path/to/exact/something/}, :allow) + PathList::Matcher::PathRegexp::CaseInsensitive.new(%r{\A#{FSROOT_LOWER}path/to/exact/something/}o, :allow) ]) ) end describe 'with root and relative path' do - subject(:matcher) { described_class.new('./exact/something', polarity, '/path/to').implicit_matcher } + subject(:matcher) { described_class.new('./exact/something', polarity, "#{FSROOT}path/to").implicit_matcher } it 'builds a regex that matches parent and child somethings' do expect(matcher).to be_like( PathList::Matcher::Any::Two.new([ PathList::Matcher::MatchIfDir.new( - PathList::Matcher::ExactString::Set.new(['/', '/path', '/path/to', '/path/to/exact'], :allow) + PathList::Matcher::ExactString::Set.new( + [FSROOT, "#{FSROOT}path", "#{FSROOT}path/to", "#{FSROOT}path/to/exact"], :allow + ) ), - PathList::Matcher::PathRegexp.new(%r{\A/path/to/exact/something/}, :allow) + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}path/to/exact/something/}o, :allow) ]) ) end @@ -63,10 +70,13 @@ PathList::Matcher::Any::Two.new([ PathList::Matcher::MatchIfDir.new( PathList::Matcher::ExactString::Set::CaseInsensitive.new( - ['/', '/path', '/path/to', '/path/to/exact'], :allow + [ + FSROOT_LOWER, "#{FSROOT_LOWER}path", "#{FSROOT_LOWER}path/to", + "#{FSROOT_LOWER}path/to/exact" + ], :allow ) ), - PathList::Matcher::PathRegexp::CaseInsensitive.new(%r{\A/path/to/exact/something/}, :allow) + PathList::Matcher::PathRegexp::CaseInsensitive.new(%r{\A#{FSROOT_LOWER}path/to/exact/something/}o, :allow) ]) ) end @@ -77,24 +87,24 @@ end it 'matches most parent path' do - expect(matcher.match(PathList::Candidate.new('/path', true))) + expect(matcher.match(PathList::Candidate.new("#{FSROOT}path", true))) .to be :allow end it 'matches exact case' do - expect(matcher.match(PathList::Candidate.new('/PATH', true))).to be_nil + expect(matcher.match(PathList::Candidate.new("#{FSROOT}PATH", true))).to be_nil end context 'when case insensitive' do let(:case_insensitive) { true } it 'matches most parent path regardless of case' do - expect(matcher.match(PathList::Candidate.new('/PATH', true))).to be :allow + expect(matcher.match(PathList::Candidate.new("#{FSROOT}PATH", true))).to be :allow end end it 'matches parent path' do - expect(matcher.match(PathList::Candidate.new('/path/to', true))) + expect(matcher.match(PathList::Candidate.new("#{FSROOT}path/to", true))) .to be :allow end @@ -114,17 +124,17 @@ end it "doesn't match parent path starting with the same string" do - expect(matcher.match(PathList::Candidate.new('/path/to/exact-ish/something', true))) + expect(matcher.match(PathList::Candidate.new("#{FSROOT}path/to/exact-ish/something", true))) .to be_nil end it "doesn't match path sibling" do - expect(matcher.match(PathList::Candidate.new('/path/to/exact/other', true))) + expect(matcher.match(PathList::Candidate.new("#{FSROOT}path/to/exact/other", true))) .to be_nil end it "doesn't match path concatenation" do - expect(matcher.match(PathList::Candidate.new('/pathtoexactsomething', true))) # spellr:disable-line + expect(matcher.match(PathList::Candidate.new("#{FSROOT}pathtoexactsomething", true))) # spellr:disable-line .to be_nil end end @@ -136,7 +146,7 @@ it 'builds a matcher that matches exact something' do expect(matcher).to be_like( - PathList::Matcher::ExactString.new('/path/to/exact/something', :ignore) + PathList::Matcher::ExactString.new("#{FSROOT}path/to/exact/something", :ignore) ) end @@ -157,12 +167,12 @@ end it "doesn't match most parent path" do - expect(matcher.match(PathList::Candidate.new('/path', true))) + expect(matcher.match(PathList::Candidate.new("#{FSROOT}path", true))) .to be_nil end it "doesn't match parent path" do - expect(matcher.match(PathList::Candidate.new('/path/to', true))) + expect(matcher.match(PathList::Candidate.new("#{FSROOT}path/to", true))) .to be_nil end diff --git a/spec/pattern_parser/gitignore_spec.rb b/spec/pattern_parser/gitignore_spec.rb index 88c2cf3..eceb2b0 100644 --- a/spec/pattern_parser/gitignore_spec.rb +++ b/spec/pattern_parser/gitignore_spec.rb @@ -24,9 +24,9 @@ def build(pattern) describe 'from the gitignore documentation' do describe 'A blank line matches no files, so it can serve as a separator for readability.' do - it { expect(build('')).to be_like PathList::Matcher::Blank } - it { expect(build(' ')).to be_like PathList::Matcher::Blank } - it { expect(build("\t")).to be_like PathList::Matcher::Blank } + it { expect(build('')).to eq PathList::Matcher::Blank } + it { expect(build(' ')).to eq PathList::Matcher::Blank } + it { expect(build("\t")).to eq PathList::Matcher::Blank } end describe 'The simple case' do @@ -36,9 +36,9 @@ def build(pattern) end describe 'A line starting with # serves as a comment.' do - it { expect(build('#foo')).to be_like PathList::Matcher::Blank } - it { expect(build('# foo')).to be_like PathList::Matcher::Blank } - it { expect(build('#')).to be_like PathList::Matcher::Blank } + it { expect(build('#foo')).to eq PathList::Matcher::Blank } + it { expect(build('# foo')).to eq PathList::Matcher::Blank } + it { expect(build('#')).to eq PathList::Matcher::Blank } it 'must be the first character' do expect(build(' #foo')) @@ -61,7 +61,7 @@ def build(pattern) it 'never matches a literal backslash at the end of the pattern' do expect(build('foo\\')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'matches an escaped backslash at the start of the pattern' do @@ -88,7 +88,7 @@ def build(pattern) it 'considers trailing backslashes to never be matched' do expect(build('foo\\')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it "doesn't ignore trailing spaces if there's a backslash before every space" do @@ -138,7 +138,7 @@ def build(pattern) it 'treats a double slash as matching nothing' do expect(build('doc//frotz')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end end @@ -490,7 +490,7 @@ def build(pattern) it 'empty class matches nothing' do expect(build('b[]b')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it "doesn't match a slash even if you specify it middle" do @@ -507,12 +507,12 @@ def build(pattern) it 'assumes an unfinished [ matches nothing' do expect(build('a[')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'assumes an unfinished [ followed by \ matches nothing' do expect(build('a[\\')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'assumes an escaped [ is literal' do @@ -527,12 +527,12 @@ def build(pattern) it 'assumes an unfinished [ matches nothing when negated' do expect(build('!a[')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'assumes an unfinished [bc matches nothing' do expect(build('a[bc')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end end @@ -559,7 +559,7 @@ def build(pattern) it 'matches nothing with double slash' do expect(build('**//foo')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'matches all directories when only **/ (interpreted as ** then the trailing / for dir only)' do @@ -672,9 +672,9 @@ def build(pattern) describe 'from the gitignore documentation' do describe 'A blank line matches no files, so it can serve as a separator for readability.' do - it { expect(build('')).to be_like PathList::Matcher::Blank } - it { expect(build(' ')).to be_like PathList::Matcher::Blank } - it { expect(build("\t")).to be_like PathList::Matcher::Blank } + it { expect(build('')).to eq PathList::Matcher::Blank } + it { expect(build(' ')).to eq PathList::Matcher::Blank } + it { expect(build("\t")).to eq PathList::Matcher::Blank } end describe 'The simple case' do @@ -682,9 +682,9 @@ def build(pattern) end describe 'A line starting with # serves as a comment.' do - it { expect(build('#foo')).to be_like PathList::Matcher::Blank } - it { expect(build('# foo')).to be_like PathList::Matcher::Blank } - it { expect(build('#')).to be_like PathList::Matcher::Blank } + it { expect(build('#foo')).to eq PathList::Matcher::Blank } + it { expect(build('# foo')).to eq PathList::Matcher::Blank } + it { expect(build('#')).to eq PathList::Matcher::Blank } it 'must be the first character' do expect(build(' #foo')) @@ -707,7 +707,7 @@ def build(pattern) it 'never matches a literal backslash at the end of the pattern' do expect(build('foo\\')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'matches an escaped backslash at the start of the pattern' do @@ -734,7 +734,7 @@ def build(pattern) it 'considers trailing backslashes to never be matched' do expect(build('foo\\')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it "doesn't ignore trailing spaces if there's a backslash before every space" do @@ -784,7 +784,7 @@ def build(pattern) it 'treats a double slash as matching nothing' do expect(build('doc//frotz')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end end @@ -1136,7 +1136,7 @@ def build(pattern) it 'empty class matches nothing' do expect(build('b[]b')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it "doesn't match a slash even if you specify it middle" do @@ -1153,12 +1153,12 @@ def build(pattern) it 'assumes an unfinished [ matches nothing' do expect(build('a[')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'assumes an unfinished [ followed by \ matches nothing' do expect(build('a[\\')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'assumes an escaped [ is literal' do @@ -1173,12 +1173,12 @@ def build(pattern) it 'assumes an unfinished [ matches nothing when negated' do expect(build('!a[')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'assumes an unfinished [bc matches nothing' do expect(build('a[bc')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end end @@ -1205,7 +1205,7 @@ def build(pattern) it 'matches nothing with double slash' do expect(build('**//foo')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'matches all directories when only **/ (interpreted as ** then the trailing / for dir only)' do @@ -1318,9 +1318,9 @@ def build(pattern) describe 'from the gitignore documentation' do describe 'A blank line matches no files, so it can serve as a separator for readability.' do - it { expect(build('')).to be_like PathList::Matcher::Blank } - it { expect(build(' ')).to be_like PathList::Matcher::Blank } - it { expect(build("\t")).to be_like PathList::Matcher::Blank } + it { expect(build('')).to eq PathList::Matcher::Blank } + it { expect(build(' ')).to eq PathList::Matcher::Blank } + it { expect(build("\t")).to eq PathList::Matcher::Blank } end describe 'The simple case' do @@ -1332,9 +1332,9 @@ def build(pattern) end describe 'A line starting with # serves as a comment.' do - it { expect(build('#foo')).to be_like PathList::Matcher::Blank } - it { expect(build('# foo')).to be_like PathList::Matcher::Blank } - it { expect(build('#')).to be_like PathList::Matcher::Blank } + it { expect(build('#foo')).to eq PathList::Matcher::Blank } + it { expect(build('# foo')).to eq PathList::Matcher::Blank } + it { expect(build('#')).to eq PathList::Matcher::Blank } it 'must be the first character' do expect(build(' #foo')) @@ -1357,7 +1357,7 @@ def build(pattern) it 'never matches a literal backslash at the end of the pattern' do expect(build('foo\\')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'matches an escaped backslash at the start of the pattern' do @@ -1384,7 +1384,7 @@ def build(pattern) it 'considers trailing backslashes to never be matched' do expect(build('foo\\')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it "doesn't ignore trailing spaces if there's a backslash before every space" do @@ -1434,7 +1434,7 @@ def build(pattern) it 'treats a double slash as matching nothing' do expect(build('doc//frotz')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end end @@ -1786,7 +1786,7 @@ def build(pattern) it 'empty class matches nothing' do expect(build('b[]b')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it "doesn't match a slash even if you specify it middle" do @@ -1803,12 +1803,12 @@ def build(pattern) it 'assumes an unfinished [ matches nothing' do expect(build('a[')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'assumes an unfinished [ followed by \ matches nothing' do expect(build('a[\\')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'assumes an escaped [ is literal' do @@ -1823,12 +1823,12 @@ def build(pattern) it 'assumes an unfinished [ matches nothing when negated' do expect(build('!a[')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'assumes an unfinished [bc matches nothing' do expect(build('a[bc')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end end @@ -1855,7 +1855,7 @@ def build(pattern) it 'matches nothing with double slash' do expect(build('**//foo')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'matches all directories when only **/ (interpreted as ** then the trailing / for dir only)' do @@ -1970,9 +1970,9 @@ def build(pattern) describe 'from the gitignore documentation' do describe 'A blank line matches no files, so it can serve as a separator for readability.' do - it { expect(build('')).to be_like PathList::Matcher::Blank } - it { expect(build(' ')).to be_like PathList::Matcher::Blank } - it { expect(build("\t")).to be_like PathList::Matcher::Blank } + it { expect(build('')).to eq PathList::Matcher::Blank } + it { expect(build(' ')).to eq PathList::Matcher::Blank } + it { expect(build("\t")).to eq PathList::Matcher::Blank } end describe 'The simple case' do @@ -1994,9 +1994,9 @@ def build(pattern) end describe 'A line starting with # serves as a comment.' do - it { expect(build('#foo')).to be_like PathList::Matcher::Blank } - it { expect(build('# foo')).to be_like PathList::Matcher::Blank } - it { expect(build('#')).to be_like PathList::Matcher::Blank } + it { expect(build('#foo')).to eq PathList::Matcher::Blank } + it { expect(build('# foo')).to eq PathList::Matcher::Blank } + it { expect(build('#')).to eq PathList::Matcher::Blank } it 'must be the first character' do expect(build(' #foo')) @@ -2043,7 +2043,7 @@ def build(pattern) it 'never matches a literal backslash at the end of the pattern' do expect(build('foo\\')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'matches an escaped backslash at the start of the pattern' do @@ -2102,7 +2102,7 @@ def build(pattern) it 'considers trailing backslashes to never be matched' do expect(build('foo\\')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it "doesn't ignore trailing spaces if there's a backslash before every space" do @@ -2185,7 +2185,7 @@ def build(pattern) it 'treats a double slash as matching nothing' do expect(build('doc//frotz')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end end @@ -2224,7 +2224,7 @@ def build(pattern) describe 'any matching file excluded by a previous pattern will become included again.' do it 'includes previously excluded files' do expect(build('!foo')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end end @@ -3010,7 +3010,7 @@ def build(pattern) it 'empty class matches nothing' do expect(build('b[]b')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it "doesn't match a slash even if you specify it middle" do @@ -3041,12 +3041,12 @@ def build(pattern) it 'assumes an unfinished [ matches nothing' do expect(build('a[')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'assumes an unfinished [ followed by \ matches nothing' do expect(build('a[\\')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'assumes an escaped [ is literal' do @@ -3078,12 +3078,12 @@ def build(pattern) it 'assumes an unfinished [ matches nothing when negated' do # nothing matches anything for implicit when negated. expect(build('!a[')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'assumes an unfinished [bc matches nothing' do expect(build('a[bc')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end end @@ -3123,7 +3123,7 @@ def build(pattern) it 'matches nothing with double slash' do expect(build('**//foo')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'matches all directories when only **/ (interpreted as ** then the trailing / for dir only)' do @@ -3381,9 +3381,9 @@ def build(pattern) describe 'from the gitignore documentation' do describe 'A blank line matches no files, so it can serve as a separator for readability.' do - it { expect(build('')).to be_like PathList::Matcher::Blank } - it { expect(build(' ')).to be_like PathList::Matcher::Blank } - it { expect(build("\t")).to be_like PathList::Matcher::Blank } + it { expect(build('')).to eq PathList::Matcher::Blank } + it { expect(build(' ')).to eq PathList::Matcher::Blank } + it { expect(build("\t")).to eq PathList::Matcher::Blank } end describe 'The simple case' do @@ -3400,9 +3400,9 @@ def build(pattern) end describe 'A line starting with # serves as a comment.' do - it { expect(build('#foo')).to be_like PathList::Matcher::Blank } - it { expect(build('# foo')).to be_like PathList::Matcher::Blank } - it { expect(build('#')).to be_like PathList::Matcher::Blank } + it { expect(build('#foo')).to eq PathList::Matcher::Blank } + it { expect(build('# foo')).to eq PathList::Matcher::Blank } + it { expect(build('#')).to eq PathList::Matcher::Blank } it 'must be the first character' do expect(build(' #foo')) @@ -3434,7 +3434,7 @@ def build(pattern) it 'never matches a literal backslash at the end of the pattern' do expect(build('foo\\')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'matches an escaped backslash at the start of the pattern' do @@ -3473,7 +3473,7 @@ def build(pattern) it 'considers trailing backslashes to never be matched' do expect(build('foo\\')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it "doesn't ignore trailing spaces if there's a backslash before every space" do @@ -3536,7 +3536,7 @@ def build(pattern) it 'treats a double slash as matching nothing' do expect(build('doc//frotz')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end end @@ -3565,7 +3565,7 @@ def build(pattern) describe 'any matching file excluded by a previous pattern will become included again.' do it 'includes previously excluded files' do expect(build('!foo')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end end @@ -4065,7 +4065,7 @@ def build(pattern) it 'empty class matches nothing' do expect(build('b[]b')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it "doesn't match a slash even if you specify it middle" do @@ -4086,12 +4086,12 @@ def build(pattern) it 'assumes an unfinished [ matches nothing' do expect(build('a[')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'assumes an unfinished [ followed by \ matches nothing' do expect(build('a[\\')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'assumes an escaped [ is literal' do @@ -4110,12 +4110,12 @@ def build(pattern) it 'assumes an unfinished [ matches nothing when negated' do expect(build('!a[')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'assumes an unfinished [bc matches nothing' do expect(build('a[bc')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end end @@ -4148,7 +4148,7 @@ def build(pattern) it 'matches nothing with double slash' do expect(build('**//foo')) - .to be_like PathList::Matcher::Invalid + .to eq PathList::Matcher::Invalid end it 'matches all directories when only **/ (interpreted as ** then the trailing / for dir only)' do diff --git a/spec/pattern_parser/glob_gitignore_spec.rb b/spec/pattern_parser/glob_gitignore_spec.rb index f8e81d6..be3ea79 100644 --- a/spec/pattern_parser/glob_gitignore_spec.rb +++ b/spec/pattern_parser/glob_gitignore_spec.rb @@ -24,30 +24,106 @@ def build(pattern) describe 'from the gitignore documentation' do describe 'A blank line matches no files, so it can serve as a separator for readability.' do - it { expect(build('')).to be_like PathList::Matcher::Blank } - it { expect(build(' ')).to be_like PathList::Matcher::Blank } - it { expect(build("\t")).to be_like PathList::Matcher::Blank } + it { expect(build('')).to eq PathList::Matcher::Blank } + it { expect(build(' ')).to eq PathList::Matcher::Blank } + it { expect(build("\t")).to eq PathList::Matcher::Blank } end describe 'The simple case' do it { expect(build('foo')).to be_like PathList::Matcher::ExactString.new('/a/path/foo', :ignore) } end + describe 'The windows case' do + before do + allow(File).to receive(:expand_path).and_call_original + # use windows regexp: + allow(File).to receive(:expand_path).with('/').and_return('D:/') + stub_const('::File::ALT_SEPARATOR', '\\') + + # treat these as absolute + allow(File).to receive(:expand_path) + .with(a_string_matching(%r{D:[\\/]foo[\\/]bar[\\/]?}), '/a/path').and_return('D:/foo/bar') + + silence_warnings do + load File.expand_path('../../lib/path_list/pattern_parser/glob_gitignore/expandable_path.rb', __dir__) + end + end + + after do + allow(File).to receive(:expand_path).with('/').and_call_original + + silence_warnings do + load File.expand_path('../../lib/path_list/pattern_parser/glob_gitignore/expandable_path.rb', __dir__) + end + end + + it { expect(build('D:/foo/bar')).to be_like PathList::Matcher::ExactString.new('D:/foo/bar', :ignore) } + it { expect(build('D:\\foo\\bar')).to be_like PathList::Matcher::ExactString.new('D:/foo/bar', :ignore) } + it { expect(build('D:\foo/bar')).to be_like PathList::Matcher::ExactString.new('D:/foo/bar', :ignore) } + + it do + expect(build('D:/foo/bar/')) + .to be_like PathList::Matcher::MatchIfDir.new(PathList::Matcher::ExactString.new('D:/foo/bar', :ignore)) + end + + it do + expect(build('D:\\foo\\bar/')) + .to be_like PathList::Matcher::MatchIfDir.new(PathList::Matcher::ExactString.new('D:/foo/bar', :ignore)) + end + + it do + expect(build('D:\\foo/bar/')) + .to be_like PathList::Matcher::MatchIfDir.new(PathList::Matcher::ExactString.new('D:/foo/bar', :ignore)) + end + + it do + expect(build('D:\\foo/bar\\')) + .to be_like PathList::Matcher::MatchIfDir.new(PathList::Matcher::ExactString.new('D:/foo/bar', :ignore)) + end + + it do + expect(build('foo/bar')) + .to be_like PathList::Matcher::ExactString.new('/a/path/foo/bar', :ignore) + end + + it do + expect(build('foo\bar')) + .to be_like PathList::Matcher::ExactString.new('/a/path/foo/bar', :ignore) + end + + it do + expect(build('**/foo/bar')) + .to be_like PathList::Matcher::PathRegexp.new(%r{\A/a/path/(?:.*/)?foo/bar\z}o, :ignore) + end + + it do + expect(build('**\\foo\\bar')) + .to be_like PathList::Matcher::PathRegexp.new(%r{\A/a/path/(?:.*/)?foo/bar\z}o, :ignore) + end + + it do + expect(build('**\\foo\\bar\\')) + .to be_like PathList::Matcher::MatchIfDir.new( + PathList::Matcher::PathRegexp.new(%r{\A/a/path/(?:.*/)?foo/bar\z}o, :ignore) + ) + end + end + describe 'leading ./ means current directory based on the root' do - it { expect(build('./foo')).to be_like PathList::Matcher::ExactString.new('/a/path/foo', :ignore) } + it { expect(build('./foo')).to be_like PathList::Matcher::ExactString.new("#{FSROOT}a/path/foo", :ignore) } end describe 'A line starting with # serves as a comment.' do - it { expect(build('#foo')).to be_like PathList::Matcher::Blank } - it { expect(build('# foo')).to be_like PathList::Matcher::Blank } - it { expect(build('#')).to be_like PathList::Matcher::Blank } + it { expect(build('#foo')).to eq PathList::Matcher::Blank } + it { expect(build('# foo')).to eq PathList::Matcher::Blank } + it { expect(build('#')).to eq PathList::Matcher::Blank } it 'must be the first character' do expect(build(' #foo')) .to be_like PathList::Matcher::ExactString.new('/a/path/ #foo', :ignore) end - describe 'Put a backslash ("\") in front of the first hash for patterns that begin with a hash' do + describe 'Put a backslash ("\") in front of patterns that begin with a hash', skip: windows? do it do expect(build('\\#foo')) .to be_like PathList::Matcher::ExactString.new('/a/path/#foo', :ignore) @@ -55,7 +131,7 @@ def build(pattern) end end - describe 'literal backslashes in filenames' do + describe 'literal backslashes in filenames', skip: windows? do it 'matches an escaped backslash at the end of the pattern' do expect(build('foo\\\\')) .to be_like PathList::Matcher::ExactString.new('/a/path/foo\\', :ignore) @@ -63,7 +139,7 @@ def build(pattern) it 'never matches a literal backslash at the end of the pattern' do expect(build('foo\\')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'matches an escaped backslash at the start of the pattern' do @@ -77,7 +153,7 @@ def build(pattern) end end - describe 'Trailing spaces are ignored unless they are quoted with backslash ("\")' do + describe 'Trailing spaces are ignored unless they are quoted with backslash ("\")', skip: windows? do it 'ignores trailing spaces in the gitignore file' do expect(build('foo ')) .to be_like PathList::Matcher::ExactString.new('/a/path/foo', :ignore) @@ -90,7 +166,7 @@ def build(pattern) it 'considers trailing backslashes to never be matched' do expect(build('foo\\')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it "doesn't ignore trailing spaces if there's a backslash before every space" do @@ -140,7 +216,7 @@ def build(pattern) it 'treats a double slash as matching nothing' do expect(build('doc//frotz')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end end @@ -174,7 +250,7 @@ def build(pattern) describe 'Put a backslash ("\") in front of the first "!" for patterns that begin with a literal "!"' do # for example, "\!important!.txt".' - it 'matches files starting with a literal ! if its preceded by a backslash' do + it 'matches files starting with a literal ! if its preceded by a backslash', skip: windows? do expect(build('\!important!.txt')) .to be_like PathList::Matcher::ExactString.new('/a/path/!important!.txt', :ignore) end @@ -301,32 +377,32 @@ def build(pattern) .to be_like PathList::Matcher::PathRegexp.new(%r{\A/a/path/a(?!/)[^d]\z}, :ignore) end - it 'treats a escaped backward character class range as only the first character of the range' do + it 'treats escaped backward character class range as the first character of the range', skip: windows? do expect(build('a[\\]-\\[]')) .to be_like PathList::Matcher::PathRegexp.new(%r{\A/a/path/a(?!/)[\]]\z}, :ignore) end - it 'treats a negated escaped backward character class range as only the first character of the range' do + it 'treats negated escaped backward character class range as the first char of range', skip: windows? do expect(build('a[^\\]-\\[]')) .to be_like PathList::Matcher::PathRegexp.new(%r{\A/a/path/a(?!/)[^\]]\z}, :ignore) end - it 'treats a escaped character class range as as a range' do + it 'treats a escaped character class range as as a range', skip: windows? do expect(build('a[\\[-\\]]')) .to be_like PathList::Matcher::PathRegexp.new(%r{\A/a/path/a(?!/)[\[-\]]\z}, :ignore) end - it 'treats a negated escaped character class range as a range' do + it 'treats a negated escaped character class range as a range', skip: windows? do expect(build('a[^\\[-\\]]')) .to be_like PathList::Matcher::PathRegexp.new(%r{\A/a/path/a(?!/)[^\[-\]]\z}, :ignore) end - it 'treats an unnecessarily escaped character class range as a range' do + it 'treats an unnecessarily escaped character class range as a range', skip: windows? do expect(build('a[\\a-\\c]')) .to be_like PathList::Matcher::PathRegexp.new(%r{\A/a/path/a(?!/)[a-c]\z}, :ignore) end - it 'treats a negated unnecessarily escaped character class range as a range' do + it 'treats a negated unnecessarily escaped character class range as a range', skip: windows? do expect(build('a[^\\a-\\c]')) .to be_like PathList::Matcher::PathRegexp.new(%r{\A/a/path/a(?!/)[^a-c]\z}, :ignore) end @@ -470,7 +546,7 @@ def build(pattern) .to be_like PathList::Matcher::PathRegexp.new(%r{\A/a/path/a(?!/)[/\^]\z}, :ignore) end - it '[\\^] matches literal ^' do + it '[\\^] matches literal ^', skip: windows? do expect(build('a[\^]')) .to be_like PathList::Matcher::PathRegexp.new(%r{\A/a/path/a(?!/)[\^]\z}, :ignore) end @@ -492,7 +568,7 @@ def build(pattern) it 'empty class matches nothing' do expect(build('b[]b')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it "doesn't match a slash even if you specify it middle" do @@ -509,32 +585,32 @@ def build(pattern) it 'assumes an unfinished [ matches nothing' do expect(build('a[')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end - it 'assumes an unfinished [ followed by \ matches nothing' do + it 'assumes an unfinished [ followed by \ matches nothing', skip: windows? do expect(build('a[\\')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end - it 'assumes an escaped [ is literal' do + it 'assumes an escaped [ is literal', skip: windows? do expect(build('a\\[')) .to be_like PathList::Matcher::ExactString.new('/a/path/a[', :ignore) end - it 'assumes an escaped [ is literal inside a group' do + it 'assumes an escaped [ is literal inside a group', skip: windows? do expect(build('a[\\[]')) .to be_like PathList::Matcher::PathRegexp.new(%r{\A/a/path/a(?!/)[\[]\z}, :ignore) end it 'assumes an unfinished [ matches nothing when negated' do expect(build('!a[')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'assumes an unfinished [bc matches nothing' do expect(build('a[bc')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end end @@ -545,7 +621,7 @@ def build(pattern) # For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". it 'matches only at the beginning of everything' do expect(build('/*.c')) - .to be_like PathList::Matcher::PathRegexp.new(%r{\A/[^/]*\.c\z}, :ignore) + .to be_like PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}[^/]*\.c\z}o, :ignore) end end @@ -561,7 +637,7 @@ def build(pattern) it 'matches nothing with double slash' do expect(build('**//foo')) - .to be_like PathList::Matcher::Blank + .to eq PathList::Matcher::Blank end it 'matches all directories when only **/ (interpreted as ** then the trailing / for dir only)' do diff --git a/spec/pattern_parser_spec.rb b/spec/pattern_parser_spec.rb index 3ada8d4..e67627f 100644 --- a/spec/pattern_parser_spec.rb +++ b/spec/pattern_parser_spec.rb @@ -11,11 +11,6 @@ ) end - before do - allow(PathList::CanonicalPath).to receive(:case_insensitive?).and_return(false) - stub_file patterns.join("\n"), patterns_from_file if patterns_from_file - end - let(:patterns) { [] } let(:patterns_arg) { patterns_from_file ? [] : Array(patterns) } let(:patterns_from_file) { nil } @@ -23,6 +18,11 @@ let(:root) { nil } let(:polarity) { :ignore } + before do + allow(PathList::CanonicalPath).to receive(:case_insensitive?).and_return(false) + stub_file patterns.join("\n"), patterns_from_file if patterns_from_file + end + around do |e| if patterns_from_file within_temp_dir { e.run } @@ -53,26 +53,56 @@ let(:patterns) { 'a' } let(:root) { '/' } - context 'when ignore' do - let(:polarity) { :ignore } + if windows? + context 'when ignore' do + let(:polarity) { :ignore } - it 'ignores a' do - expect(matchers).to be_like PathList::Matcher::LastMatch::Two.new([ - PathList::Matcher::Allow, - PathList::Matcher::PathRegexp.new(%r{/a\z}, :ignore) - ]) + it 'ignores a' do + expect(matchers).to be_like PathList::Matcher::LastMatch::Two.new([ + PathList::Matcher::Allow, + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}(?:.*/)?a\z}o, :ignore) + ]) + end end - end - context 'when allow' do - let(:polarity) { :allow } + context 'when allow' do + let(:polarity) { :allow } + + it 'allows a, and implicitly any parents and children of a' do + expect(matchers).to be_like PathList::Matcher::LastMatch.new([ + PathList::Matcher::Ignore, + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}(?:.*/)?a(?:\z|/)}o, :allow), + PathList::Matcher::MatchIfDir.new( + PathList::Matcher::Any::Two.new([ + PathList::Matcher::ExactString.new(FSROOT, :allow), + PathList::Matcher::PathRegexp.new(/\A#{FSROOT}/o, :allow) + ]) + ) + ]) + end + end + else + context 'when ignore' do + let(:polarity) { :ignore } + + it 'ignores a' do + expect(matchers).to be_like PathList::Matcher::LastMatch::Two.new([ + PathList::Matcher::Allow, + PathList::Matcher::PathRegexp.new(%r{/a\z}, :ignore) + ]) + end + end - it 'allows a, and implicitly any parents and children of a' do - expect(matchers).to be_like PathList::Matcher::LastMatch.new([ - PathList::Matcher::Ignore, - PathList::Matcher::PathRegexp.new(%r{/a(?:\z|/)}, :allow), - PathList::Matcher::AllowAnyDir - ]) + context 'when allow' do + let(:polarity) { :allow } + + it 'allows a, and implicitly any parents and children of a' do + expect(matchers).to be_like PathList::Matcher::LastMatch.new([ + PathList::Matcher::Ignore, + PathList::Matcher::PathRegexp.new(%r{/a(?:\z|/)}, :allow), + PathList::Matcher::AllowAnyDir + ]) + end end end end @@ -87,7 +117,7 @@ it 'ignores a' do expect(matchers).to be_like PathList::Matcher::LastMatch::Two.new([ PathList::Matcher::Allow, - PathList::Matcher::PathRegexp.new(%r{\A/b/(?:.*/)?a\z}, :ignore) + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}b/(?:.*/)?a\z}o, :ignore) ]) end end @@ -98,11 +128,11 @@ it 'allows a, and implicitly any children of a' do expect(matchers).to be_like PathList::Matcher::LastMatch.new([ PathList::Matcher::Ignore, - PathList::Matcher::PathRegexp.new(%r{\A/b/(?:.*/)?a(?:\z|/)}, :allow), + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}b/(?:.*/)?a(?:\z|/)}o, :allow), PathList::Matcher::MatchIfDir.new( PathList::Matcher::Any::Two.new([ - PathList::Matcher::ExactString::Set.new(['/', '/b'], :allow), - PathList::Matcher::PathRegexp.new(%r{\A/b/}, :allow) + PathList::Matcher::ExactString::Set.new([FSROOT, "#{FSROOT}b"], :allow), + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}b/}o, :allow) ]) ) ]) @@ -114,31 +144,61 @@ let(:patterns) { ['a[b]', 'a[^c]'] } let(:root) { '/' } - context 'when ignore' do - let(:polarity) { :ignore } + if windows? + context 'when ignore' do + let(:polarity) { :ignore } - it "doesn't merge the character classes" do - expect(matchers).to be_like PathList::Matcher::LastMatch::Two.new([ - PathList::Matcher::Allow, - PathList::Matcher::PathRegexp.new(%r{/a(?:(?!/)[b]\z|(?!/)[^c]\z)}, :ignore) - ]) + it "doesn't merge the character classes" do + expect(matchers).to be_like PathList::Matcher::LastMatch::Two.new([ + PathList::Matcher::Allow, + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}(?:.*/)?a(?:(?!/)[b]\z|(?!/)[^c]\z)}o, :ignore) + ]) + end end - end - context 'when allow' do - let(:polarity) { :allow } + context 'when allow' do + let(:polarity) { :allow } + + it "doesn't merge the character classes" do + expect(matchers).to be_like PathList::Matcher::LastMatch.new([ + PathList::Matcher::Ignore, + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}(?:.*/)?a(?:(?!/)[b](?:\z|/)|(?!/)[^c](?:\z|/))}o, :allow), + PathList::Matcher::MatchIfDir.new( + PathList::Matcher::Any::Two.new([ + PathList::Matcher::ExactString.new(FSROOT, :allow), + PathList::Matcher::PathRegexp.new(/\A#{FSROOT}/o, :allow) + ]) + ) + ]) + end + end + else + context 'when ignore' do + let(:polarity) { :ignore } + + it "doesn't merge the character classes" do + expect(matchers).to be_like PathList::Matcher::LastMatch::Two.new([ + PathList::Matcher::Allow, + PathList::Matcher::PathRegexp.new(%r{/a(?:(?!/)[b]\z|(?!/)[^c]\z)}, :ignore) + ]) + end + end - it "doesn't merge the character classes" do - expect(matchers).to be_like PathList::Matcher::LastMatch.new([ - PathList::Matcher::Ignore, - PathList::Matcher::PathRegexp.new(%r{/a(?:(?!/)[b](?:\z|/)|(?!/)[^c](?:\z|/))}, :allow), - PathList::Matcher::AllowAnyDir - ]) + context 'when allow' do + let(:polarity) { :allow } + + it "doesn't merge the character classes" do + expect(matchers).to be_like PathList::Matcher::LastMatch.new([ + PathList::Matcher::Ignore, + PathList::Matcher::PathRegexp.new(%r{/a(?:(?!/)[b](?:\z|/)|(?!/)[^c](?:\z|/))}, :allow), + PathList::Matcher::AllowAnyDir + ]) + end end end end - context 'with patterns: ["*", "!./foo", "!/a/b/c/baz"], root: "/a/b/c", format: :glob_gitignore' do + context 'with patterns: ["*", "!./d", "!/a/b/c/baz"], root: "/a/b/c", format: :glob_gitignore' do let(:patterns) { ['*', '!./foo', '!/a/b/c/baz'] } let(:root) { '/a/b/c' } let(:format_arg) { :glob_gitignore } @@ -149,10 +209,10 @@ it 'builds correct matchers (correctness verified by other tests, i just want visibility)' do expect(matchers).to be_like PathList::Matcher::LastMatch.new([ PathList::Matcher::Allow, - PathList::Matcher::PathRegexp.new(%r{\A/a/b/c/}, :ignore), + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}a/b/c/}o, :ignore), PathList::Matcher::ExactString::Set.new([ - '/a/b/c/foo', - '/a/b/c/baz' + "#{FSROOT}a/b/c/baz", + "#{FSROOT}a/b/c/foo" ], :allow) ]) end @@ -164,24 +224,24 @@ it 'builds correct matchers (correctness verified by other tests, i just want visibility)' do expect(matchers).to be_like PathList::Matcher::LastMatch.new([ PathList::Matcher::Ignore, - PathList::Matcher::PathRegexp.new(%r{\A/a/b/c/}, :allow), + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}a/b/c/}o, :allow), PathList::Matcher::MatchIfDir.new( PathList::Matcher::Any::Two.new([ - PathList::Matcher::ExactString::Set.new(['/', '/a', '/a/b', '/a/b/c'], :allow), - PathList::Matcher::PathRegexp.new(%r{\A/a/b/c/}, :allow) + PathList::Matcher::ExactString::Set.new([FSROOT, "#{FSROOT}a", "#{FSROOT}a/b", "#{FSROOT}a/b/c"], :allow), + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}a/b/c/}o, :allow) ]) ), PathList::Matcher::ExactString::Set.new([ - '/a/b/c/foo', - '/a/b/c/baz' + "#{FSROOT}a/b/c/baz", + "#{FSROOT}a/b/c/foo" ], :ignore) ]) end end end - context 'with patterns: ["a*", "/b*", "d*", "/bb*", "!c/d*", "**/e*", "# comment"], root: "/f/g"' do - let(:patterns) { ['a*', '/b*', 'd*', '/bb*', '!c/d*', '**/e*', '# comment'] } + context 'with patterns: ["a*", "/bb*", "ddd*", "/bbbb*", "!c/d*", "**/eeeee*", "# comment"], root: "/f/g"' do + let(:patterns) { ['a*', '/bb*', 'ddd*', '/bbbb*', '!c/d*', '**/eeeee*', '# comment'] } let(:root) { '/f/g/' } context 'when ignore' do @@ -191,10 +251,11 @@ expect(matchers).to be_like PathList::Matcher::LastMatch.new([ PathList::Matcher::Allow, PathList::Matcher::PathRegexp.new( - %r{\A/f/g/(?:b[^\/]*\z|bb[^\/]*\z|(?:.*/)?(?:a[^\/]*\z|d[^\/]*\z))}, :ignore + %r{\A#{FSROOT}f/g/(?:bb[^/]*\z|bbbb[^/]*\z|(?:.*/)?(?:a[^/]*\z|ddd[^/]*\z))}o, + :ignore ), - PathList::Matcher::PathRegexp.new(%r{\A/f/g/c/d[^\/]*\z}, :allow), - PathList::Matcher::PathRegexp.new(%r{\A/f/g/(?:.*/)?e[^\/]*\z}, :ignore) + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}f/g/c/d[^\/]*\z}o, :allow), + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}f/g/(?:.*/)?eeeee[^\/]*\z}o, :ignore) ]) end end @@ -206,17 +267,17 @@ expect(matchers).to be_like PathList::Matcher::LastMatch.new([ PathList::Matcher::Ignore, PathList::Matcher::PathRegexp.new( - %r{\A/f/g/(?:b[^\/]*(?:\z|/)|bb[^\/]*(?:\z|/)|(?:.*/)?(?:a[^\/]*(?:\z|/)|d[^\/]*(?:\z|/)|e[^\/]*/))}, + %r{\A#{FSROOT}f/g/(?:bb[^/]*(?:\z|/)|bbbb[^/]*(?:\z|/)|(?:.*/)?(?:a[^/]*(?:\z|/)|ddd[^/]*(?:\z|/)|eeeee[^/]*/))}o, # rubocop:disable Layout/LineLength :allow ), PathList::Matcher::MatchIfDir.new( PathList::Matcher::Any::Two.new([ - PathList::Matcher::ExactString::Set.new(['/', '/f', '/f/g'], :allow), - PathList::Matcher::PathRegexp.new(%r{\A/f/g/}, :allow) + PathList::Matcher::ExactString::Set.new([FSROOT, "#{FSROOT}f", "#{FSROOT}f/g"], :allow), + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}f/g/}o, :allow) ]) ), - PathList::Matcher::PathRegexp.new(%r{\A/f/g/c/d[^\/]*\z}, :ignore), - PathList::Matcher::PathRegexp.new(%r{\A/f/g/(?:.*/)?e[^\/]*\z}, :allow) + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}f/g/c/d[^\/]*\z}o, :ignore), + PathList::Matcher::PathRegexp.new(%r{\A#{FSROOT}f/g/(?:.*/)?eeeee[^\/]*\z}o, :allow) ]) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5fd0067..1fe7a3e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,14 +2,19 @@ if RUBY_PLATFORM != 'java' module Warning # leftovers:allow - # def warn(msg) # leftovers:allow - # raise msg unless msg.start_with?('PathList deprecation:', 'PathList gitconfig parser failed') || $allow_warning - # end + def warn(msg) # leftovers:allow + raise msg + end end end $doing_include = false +if RUBY_PLATFORM == 'java' + Encoding.default_external = 'utf-8' + Encoding.default_internal = 'utf-8' +end + require 'fileutils' FileUtils.rm_rf(File.join(__dir__, '..', 'coverage')) @@ -17,13 +22,12 @@ module Warning # leftovers:allow require 'simplecov' if ENV['COVERAGE'] require_relative '../lib/path_list' -require_relative 'support/actual_git_ls_files' -require_relative 'support/inspect_helper' -require_relative 'support/temp_dir_helper' -require_relative 'support/stub_env_helper' -require_relative 'support/stub_file_helper' -require_relative 'support/stub_global_gitignore_helper' -require_relative 'support/matchers' +PathList.only('support/*.rb', root: __dir__).each(__dir__) do |file| + require_relative file +end + +FSROOT = File.expand_path('/') +FSROOT_LOWER = FSROOT.downcase RSpec.configure do |config| config.expect_with :rspec do |c| diff --git a/spec/support/stub_file_helper.rb b/spec/support/stub_file_helper.rb index 9cc4d38..9e5c702 100644 --- a/spec/support/stub_file_helper.rb +++ b/spec/support/stub_file_helper.rb @@ -9,6 +9,7 @@ def stub_file_original def stub_file(*lines, path:) stub_file_original + path = ::File.expand_path(path) stub_file_attributes(lines.join("\n")).each do |method, value| stub = allow(::File).to receive(method).with(path) diff --git a/spec/support/temp_dir_helper.rb b/spec/support/temp_dir_helper.rb index 6198274..70af7f7 100644 --- a/spec/support/temp_dir_helper.rb +++ b/spec/support/temp_dir_helper.rb @@ -66,7 +66,7 @@ def within_temp_dir yield ensure Dir.chdir(original_dir) - dir&.rmtree + ::FileUtils.remove_dir(dir.to_s, true) if dir end end diff --git a/spec/support/warning_helper.rb b/spec/support/warning_helper.rb new file mode 100644 index 0000000..3078da3 --- /dev/null +++ b/spec/support/warning_helper.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module WarningHelper + def silence_warnings + original_verbose = $VERBOSE + $VERBOSE = nil + yield + ensure + $VERBOSE = original_verbose + end +end + +RSpec.configure do |config| + config.include WarningHelper + config.extend WarningHelper +end diff --git a/spec/support/windows_helper.rb b/spec/support/windows_helper.rb new file mode 100644 index 0000000..ec76af2 --- /dev/null +++ b/spec/support/windows_helper.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module WindowsHelper + def windows? + ::RbConfig::CONFIG['host_os'].match?(/mswin|mingw/) + end +end + +RSpec.configure do |config| + config.include WindowsHelper + config.extend WindowsHelper +end diff --git a/spec/token_regexp/path_spec.rb b/spec/token_regexp/path_spec.rb index 976cc57..df1bd74 100644 --- a/spec/token_regexp/path_spec.rb +++ b/spec/token_regexp/path_spec.rb @@ -26,4 +26,71 @@ expect(described_class.new(path)).not_to be_exact_path end end + + describe '.new_from_path' do + it 'works for unix shaped paths' do + expect(described_class.new_from_path('/a/b/c/d').parts) + .to eq [:start_anchor, :dir, 'a', :dir, 'b', :dir, 'c', :dir, 'd', :end_anchor] + end + + it 'works for windows shaped paths' do + expect(described_class.new_from_path('C:/a/b/c/d').parts) + .to eq [:start_anchor, 'C:', :dir, 'a', :dir, 'b', :dir, 'c', :dir, 'd', :end_anchor] + end + + it 'works for unix shaped root path' do + expect(described_class.new_from_path('/').parts) + .to eq [:start_anchor, :dir, :end_anchor] + end + + it 'works for windows shaped root path' do + expect(described_class.new_from_path('C:/').parts) + .to eq [:start_anchor, 'C:', :dir, :end_anchor] + end + + it 'works for windows shaped root path, case insensitively' do + allow(PathList::CanonicalPath).to receive(:case_insensitive?).and_return(true) + + expect(described_class.new_from_path('C:/').parts) + .to eq [:start_anchor, 'c:', :dir, :end_anchor] + end + end + + describe '#compress' do + it 'works for unix shaped paths' do + expect(described_class.new([ + :start_anchor, :dir, :any_dir, 'a', :dir, 'b', :dir, :any_dir, + :end_anchor + ]).compress.parts) + .to eq [:dir, 'a', :dir, 'b', :dir] + end + + it 'works for windows shaped paths' do + expect(described_class.new([ + :start_anchor, 'D:', :dir, :any_dir, 'a', :dir, 'b', :dir, :any_dir, + :end_anchor + ]).compress.parts) + .to eq [:start_anchor, 'D:', :dir, :any_dir, 'a', :dir, 'b', :dir] + end + end + + describe 'ancestors' do + it 'works for unix shaped paths' do + expect(described_class.new_from_path('/a/b/c/d').ancestors.map(&:parts)).to eq [ + [:start_anchor, :dir, :end_anchor], + [:start_anchor, :dir, 'a', :end_anchor], + [:start_anchor, :dir, 'a', :dir, 'b', :end_anchor], + [:start_anchor, :dir, 'a', :dir, 'b', :dir, 'c', :end_anchor] + ] + end + + it 'works for windows shaped paths' do + expect(described_class.new_from_path('C:/a/b/c/d').ancestors.map(&:parts)).to eq [ + [:start_anchor, 'C:', :dir, :end_anchor], + [:start_anchor, 'C:', :dir, 'a', :end_anchor], + [:start_anchor, 'C:', :dir, 'a', :dir, 'b', :end_anchor], + [:start_anchor, 'C:', :dir, 'a', :dir, 'b', :dir, 'c', :end_anchor] + ] + end + end end