fast_ignore 0.17.0 → 0.17.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/README.md +0 -13
  4. data/lib/fast_ignore/builders/gitignore.rb +15 -0
  5. data/lib/fast_ignore/builders/shebang.rb +17 -0
  6. data/lib/fast_ignore/builders/shebang_or_gitignore.rb +15 -0
  7. data/lib/fast_ignore/candidate.rb +85 -0
  8. data/lib/fast_ignore/gitconfig_parser.rb +3 -4
  9. data/lib/fast_ignore/gitignore_include_rule_builder.rb +40 -67
  10. data/lib/fast_ignore/gitignore_rule_builder.rb +66 -27
  11. data/lib/fast_ignore/gitignore_rule_group.rb +31 -0
  12. data/lib/fast_ignore/gitignore_rule_scanner.rb +24 -4
  13. data/lib/fast_ignore/matchers/allow_any_dir.rb +39 -0
  14. data/lib/fast_ignore/matchers/allow_path_regexp.rb +45 -0
  15. data/lib/fast_ignore/matchers/ignore_path_regexp.rb +45 -0
  16. data/lib/fast_ignore/matchers/shebang_regexp.rb +46 -0
  17. data/lib/fast_ignore/matchers/unmatchable.rb +31 -0
  18. data/lib/fast_ignore/matchers/within_dir.rb +50 -0
  19. data/lib/fast_ignore/path_expander.rb +11 -0
  20. data/lib/fast_ignore/path_regexp_builder.rb +130 -0
  21. data/lib/fast_ignore/patterns.rb +33 -0
  22. data/lib/fast_ignore/relative_candidate.rb +20 -0
  23. data/lib/fast_ignore/rule_group.rb +42 -0
  24. data/lib/fast_ignore/rule_groups.rb +55 -0
  25. data/lib/fast_ignore/version.rb +1 -1
  26. data/lib/fast_ignore/walkers/base.rb +26 -0
  27. data/lib/fast_ignore/walkers/file_system.rb +46 -0
  28. data/lib/fast_ignore/walkers/gitignore_collecting_file_system.rb +48 -0
  29. data/lib/fast_ignore.rb +54 -91
  30. metadata +36 -11
  31. data/lib/fast_ignore/backports.rb +0 -66
  32. data/lib/fast_ignore/file_root.rb +0 -39
  33. data/lib/fast_ignore/gitignore_rule_regexp_builder.rb +0 -76
  34. data/lib/fast_ignore/rule.rb +0 -65
  35. data/lib/fast_ignore/rule_builder.rb +0 -41
  36. data/lib/fast_ignore/rule_set.rb +0 -76
  37. data/lib/fast_ignore/rule_sets.rb +0 -113
  38. data/lib/fast_ignore/shebang_rule.rb +0 -80
  39. data/lib/fast_ignore/unmatchable_rule.rb +0 -41
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78cb85cea0888d67c91de09bd54ad644510167c993dc355d187593003d341393
4
- data.tar.gz: fb1fda9c1675ee28f4a6ff23191f95c2dd6e497ca2a70c4791dc5dfb85baecd1
3
+ metadata.gz: c81120885ae42b4893295ecd66b501ed6962d2742adc2bd59433796ca0e41995
4
+ data.tar.gz: 0a15d4651cc7dfc7eeed32b919da373ebee92195385ff0cf22acd92751258b68
5
5
  SHA512:
6
- metadata.gz: 10531a1d819b74fcc442ce7591be124430d3c55c55afbccb79ba295dd89c0e85e4a1a0a75b11996557057fa2f5dbad71e9284789143395d229e3abbbbd42ba3c
7
- data.tar.gz: d0b07a2757c3b962a59973581234ec9e346717d24cdbd5443073c0fd14f840fb287419d63eeb31d4aa52d3fff2e1c0842cc119ed52e836cd55ed73bcd29a3fc7
6
+ metadata.gz: c38a391c2a05c7439899144c2af1db29281538c855a18be47725b3e92f8fc40ecca2f69f15a26508d5f63d7f45f998cbba73ac41feaa1541356daf20067b4ce9
7
+ data.tar.gz: 0e355b08fbb00c9cca020ac06b0062f73477a59f673f6b465bf71d11c0228889dbeeb487e8e519eeccd09afa84290681f65f75300695332b0a4e44ecbec1b445
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ # v0.17.4
2
+ - Deprecated `follow_symlinks:`, it's inaccurately named and awkward.
3
+
4
+ - Plus lots of refactoring that _should_ have no effect on behaviour
5
+ - Some performance regression due to the deprecation logic. we'll improve more than we lost after the deprecations are gone entirely.
6
+
7
+ # v0.17.3
8
+ - Add fuzz tests, fix a couple more edge cases it revealed:
9
+ - `~not_a_user` will be considered literal rather than raising an error, `~a_user` will continue to be expanded to the home directory of `a_user` when used in an `argv_rules:` or `allowed?`
10
+ - an `include_rule:` with a trailing `/` was raising a FrozenError in some circumstances.
11
+
12
+ # v0.17.2
13
+ - Remove unnecessary backport code that was leftover when support for 2.4 was dropped
14
+ - Tiny performance improvements from rubocop-performance's suggestions
15
+
16
+ # v0.17.1
17
+ - fix handling of backward character classes `[z-a]`
18
+ previously this raised a RegexpError, but git just considered it to be identical to `[z]`, now we match the git behaviour (but why would you ever do this?, i only found it because of the fuzz spec in the `leftovers` gem)
19
+
1
20
  # v0.17.0
2
21
  - allow overriding `exists:` in `allowed?`
3
22
  - allow setting `include_directories: true` in `allowed?`
data/README.md CHANGED
@@ -166,19 +166,6 @@ When `relative: true`: FastIgnore#each will yield paths relative to the [`root:`
166
166
  FastIgnore.new(relative: true).to_a
167
167
  ```
168
168
 
169
- ### `follow_symlinks: true`
170
-
171
- **Default: false**
172
-
173
- When `follow_symlinks: false`: FastIgnore#each will match git's behaviour and not follow symbolic links.
174
- When `follow_symlinks: true`: FastIgnore#each will check if a symlink points to a directory, and files in linked directories must also match rules using the symlink path as the directory location, not the real directory location.
175
-
176
- **This doesn't use the real path for matching or yield or return it.**
177
-
178
- ```ruby
179
- FastIgnore.new(follow_symlinks: true).to_a
180
- ```
181
-
182
169
  ### `root:`
183
170
 
184
171
  **Default: Dir.pwd ($PWD, the current working directory)**
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastIgnore
4
+ module Builders
5
+ module Gitignore
6
+ def self.build(rule, allow, expand_path_with: nil)
7
+ if allow
8
+ ::FastIgnore::GitignoreIncludeRuleBuilder.new(rule, expand_path_with: expand_path_with).build
9
+ else
10
+ ::FastIgnore::GitignoreRuleBuilder.new(rule, expand_path_with: expand_path_with).build
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastIgnore
4
+ module Builders
5
+ module Shebang
6
+ def self.build(shebang, allow)
7
+ shebang.strip!
8
+ pattern = /\A#!.*\b#{::Regexp.escape(shebang)}\b/i
9
+ rule = ::FastIgnore::Matchers::ShebangRegexp.new(pattern, allow)
10
+ return rule unless allow
11
+
12
+ # also allow all directories in case they include a file with the matching shebang file
13
+ [::FastIgnore::Matchers::AllowAnyDir, rule]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastIgnore
4
+ module Builders
5
+ module ShebangOrGitignore
6
+ def self.build(rule, allow, expand_path_with: nil)
7
+ if rule.delete_prefix!('#!:')
8
+ ::FastIgnore::Builders::Shebang.build(rule, allow)
9
+ else
10
+ ::FastIgnore::Builders::Gitignore.build(rule, allow, expand_path_with: expand_path_with)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,85 @@
1
+ # frozen-string-literal: true
2
+
3
+ class FastIgnore
4
+ class Candidate
5
+ class << self
6
+ def root
7
+ @root ||= new('/', nil, true, true, nil)
8
+ end
9
+ end
10
+
11
+ def initialize(full_path, filename, directory, exists, content)
12
+ @full_path = full_path
13
+ @filename = filename
14
+ (@directory = directory) unless directory.nil?
15
+ (@exists = exists) unless exists.nil?
16
+ (@first_line = content.slice(/.*/)) if content # we only care about the first line
17
+ end
18
+
19
+ def parent
20
+ @parent ||= ::FastIgnore::Candidate.new(::File.dirname(@full_path), nil, true, true, nil)
21
+ end
22
+
23
+ # use \0 because it can't be in paths
24
+ def key
25
+ @key ||= :"#{
26
+ "\0" if defined?(@directory) && @directory
27
+ }#{
28
+ @full_path
29
+ }\0#{
30
+ @first_line if defined?(@first_line)
31
+ }"
32
+ end
33
+
34
+ def relative_to(dir)
35
+ return unless @full_path.start_with?(dir)
36
+
37
+ ::FastIgnore::RelativeCandidate.new(@full_path.delete_prefix(dir), self)
38
+ end
39
+
40
+ def directory?
41
+ return @directory if defined?(@directory)
42
+
43
+ @directory = ::File.lstat(@full_path).directory?
44
+ rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENAMETOOLONG
45
+ @exists ||= false
46
+ @directory = false
47
+ end
48
+
49
+ def exists?
50
+ return @exists if defined?(@exists)
51
+
52
+ @exists = ::File.exist?(@full_path)
53
+ rescue ::Errno::EACCES, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
54
+ # :nocov: can't quite get this set up in a test
55
+ @exists = false
56
+ # :nocov:
57
+ end
58
+
59
+ def filename
60
+ @filename ||= ::File.basename(@full_path)
61
+ end
62
+
63
+ # how long can a shebang be?
64
+ # https://www.in-ulm.de/~mascheck/various/shebang/
65
+ def first_line # rubocop:disable Metrics/MethodLength
66
+ @first_line ||= begin
67
+ file = ::File.new(@full_path)
68
+ first_line = file.sysread(64)
69
+ if first_line.start_with?('#!')
70
+ first_line += file.readline unless first_line.include?("\n")
71
+ file.close
72
+ first_line
73
+ else
74
+ file.close
75
+ ''
76
+ end
77
+ rescue ::EOFError, ::SystemCallError
78
+ # :nocov:
79
+ file&.close
80
+ # :nocov:
81
+ ''
82
+ end
83
+ end
84
+ end
85
+ end
@@ -55,7 +55,7 @@ class FastIgnore
55
55
  include_path = scan_value(file)
56
56
 
57
57
  value = ::FastIgnore::GitconfigParser.parse(
58
- ::File.expand_path(include_path, ::File.dirname(path)),
58
+ PathExpander.expand_path(include_path, ::File.dirname(path)),
59
59
  root: root,
60
60
  nesting: nesting + 1
61
61
  )
@@ -100,9 +100,8 @@ class FastIgnore
100
100
 
101
101
  def on_branch?(branch_pattern)
102
102
  branch_pattern += '**' if branch_pattern.end_with?('/')
103
- current_branch = ::File.readable?("#{root}/.git/HEAD") && ::File.read("#{root}/.git/HEAD").sub!(
104
- %r{\Aref: refs/heads/}, ''
105
- )
103
+ current_branch = ::File.readable?("#{root}/.git/HEAD") &&
104
+ ::File.read("#{root}/.git/HEAD").delete_prefix('ref: refs/heads/')
106
105
  return unless current_branch
107
106
 
108
107
  # goddamit git what does 'a pattern with standard globbing wildcards' mean
@@ -2,26 +2,10 @@
2
2
 
3
3
  class FastIgnore
4
4
  class GitignoreIncludeRuleBuilder < GitignoreRuleBuilder
5
- # :nocov:
6
- using ::FastIgnore::Backports::DeletePrefixSuffix if defined?(::FastIgnore::Backports::DeletePrefixSuffix)
7
- # :nocov:
8
-
9
- def initialize(rule, file_path, expand_path_from = nil)
10
- super(rule, file_path)
5
+ def initialize(rule, expand_path_with: nil)
6
+ super
11
7
 
12
- @parent_segments = []
13
8
  @negation = true
14
- @expand_path_from = expand_path_from
15
- end
16
-
17
- def expand_rule_path
18
- anchored! unless @s.match?(/\*/)
19
- return unless @s.match?(%r{(?:[~/]|\.{1,2}/|.*/\.\./)})
20
-
21
- dir_only! if @s.match?(%r{.*/\s*\z})
22
- @s.string.replace(::File.expand_path(@s.rest))
23
- @s.string.delete_prefix!(@expand_path_from)
24
- @s.pos = 0
25
9
  end
26
10
 
27
11
  def negated!
@@ -29,74 +13,63 @@ class FastIgnore
29
13
  end
30
14
 
31
15
  def unmatchable_rule!
32
- throw :abort_build, ::FastIgnore::UnmatchableRule
33
- end
34
-
35
- def nothing_emitted?
36
- @re.empty? && @parent_segments.empty?
37
- end
38
-
39
- def emit_dir
40
- anchored!
41
-
42
- @parent_segments << @re
43
- @re = ::FastIgnore::GitignoreRuleRegexpBuilder.new
16
+ throw :abort_build, ::FastIgnore::Matchers::Unmatchable
44
17
  end
45
18
 
46
19
  def emit_end
47
- @dir_only || @re.append_end_dir_or_anchor
20
+ if @dir_only
21
+ @child_re = @re.dup
22
+ @re.append_end_anchor
23
+ else
24
+ @re.append_dir_or_end_anchor
25
+ end
26
+
48
27
  break!
49
28
  end
50
29
 
51
- def parent_dir_re # rubocop:disable Metrics/MethodLength
52
- segment_joins_count = @parent_segments.length
53
- parent_prefix = if @file_path
54
- segment_joins_count += @file_path.escaped_segments_length
30
+ def build_parent_dir_rules
31
+ return unless @negation
55
32
 
56
- if @anchored
57
- "\\A#{@file_path.escaped_segments_joined}"
58
- else
59
- "\\A#{@file_path.escaped_segments_joined}(?:.*/)?"
33
+ if @anchored
34
+ parent_pattern = @s.string.dup
35
+ if parent_pattern.sub!(%r{/[^/]+/?\s*\z}, '/')
36
+ ::FastIgnore::GitignoreIncludeRuleBuilder.new(parent_pattern).build_as_parent
60
37
  end
61
38
  else
62
- prefix
39
+ [::FastIgnore::Matchers::AllowAnyDir]
63
40
  end
41
+ end
64
42
 
65
- out = parent_prefix.dup
66
- unless @parent_segments.empty?
67
- out << '(?:'
68
- out << @parent_segments.join('/(?:')
69
- out << '/'
43
+ def build_child_file_rule # rubocop:disable Metrics/MethodLength
44
+ if @child_re.end_with?('/')
45
+ @child_re.append_many_non_dir.append_dir if @dir_only
46
+ else
47
+ @child_re.append_dir
70
48
  end
71
- out << (')?' * segment_joins_count)
72
- out
73
- end
74
49
 
75
- def build_parent_dir_rule
76
- # Regexp::IGNORECASE = 1
77
- ::FastIgnore::Rule.new(::Regexp.new(parent_dir_re, 1), true, anchored_or_file_path, true)
78
- end
50
+ @child_re.prepend(prefix)
79
51
 
80
- def build_child_file_rule
81
- # Regexp::IGNORECASE = 1
82
- ::FastIgnore::Rule.new(@re.append_dir.to_regexp, @negation, anchored_or_file_path, false)
52
+ if @negation
53
+ ::FastIgnore::Matchers::AllowPathRegexp.new(@child_re.to_regexp, @anchored, false)
54
+ else
55
+ ::FastIgnore::Matchers::IgnorePathRegexp.new(@child_re.to_regexp, @anchored, false)
56
+ end
83
57
  end
84
58
 
85
- def build_rule
86
- joined_re = ::FastIgnore::GitignoreRuleRegexpBuilder.new
87
- joined_re.append(@parent_segments.join('/'))
88
- joined_re.append_dir unless @parent_segments.empty?
89
- joined_re.append(@re)
90
- @re = joined_re
59
+ def build_as_parent
60
+ anchored!
61
+ dir_only!
91
62
 
92
- rules = [super, build_parent_dir_rule]
93
- (rules << build_child_file_rule) if @dir_only
94
- rules
63
+ catch :abort_build do
64
+ process_rule
65
+ build_rule(child_file_rule: false)
66
+ end
95
67
  end
96
68
 
97
- def process_rule
98
- expand_rule_path if @expand_path_from
99
- super
69
+ def build_rule(child_file_rule: true)
70
+ @child_re ||= @re.dup # in case emit_end wasn't called
71
+
72
+ [super(), *build_parent_dir_rules, (build_child_file_rule if child_file_rule)].compact
100
73
  end
101
74
  end
102
75
  end
@@ -2,11 +2,11 @@
2
2
 
3
3
  class FastIgnore
4
4
  class GitignoreRuleBuilder # rubocop:disable Metrics/ClassLength
5
- def initialize(rule, file_path)
6
- @re = ::FastIgnore::GitignoreRuleRegexpBuilder.new
5
+ def initialize(rule, expand_path_with: nil)
6
+ @re = ::FastIgnore::PathRegexpBuilder.new
7
7
  @s = ::FastIgnore::GitignoreRuleScanner.new(rule)
8
8
 
9
- @file_path = file_path
9
+ @expand_path_with = expand_path_with
10
10
  @negation = false
11
11
  @anchored = false
12
12
  @dir_only = false
@@ -49,6 +49,11 @@ class FastIgnore
49
49
  @re.append_dir
50
50
  end
51
51
 
52
+ def emit_any_dir
53
+ anchored!
54
+ @re.append_any_dir
55
+ end
56
+
52
57
  def emit_end
53
58
  @re.append_end_anchor
54
59
  break!
@@ -60,11 +65,22 @@ class FastIgnore
60
65
  @re.append_escaped(@s.next_character) || unmatchable_rule!
61
66
  end
62
67
 
63
- def process_star_end_after_slash
64
- return true unless @s.star_end?
65
-
66
- @re.append_many_non_dir
67
- emit_end
68
+ def process_star_end_after_slash # rubocop:disable Metrics/MethodLength
69
+ if @s.star_end?
70
+ @re.append_many_non_dir
71
+ emit_end
72
+ elsif @s.two_star_end?
73
+ break!
74
+ elsif @s.star_slash_end?
75
+ @re.append_many_non_dir
76
+ dir_only!
77
+ emit_end
78
+ elsif @s.two_star_slash_end?
79
+ dir_only!
80
+ break!
81
+ else
82
+ true
83
+ end
68
84
  end
69
85
 
70
86
  def process_slash
@@ -76,7 +92,7 @@ class FastIgnore
76
92
  process_star_end_after_slash
77
93
  end
78
94
 
79
- def process_two_stars # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
95
+ def process_two_stars # rubocop:disable Metrics/MethodLength
80
96
  return unless @s.two_stars?
81
97
  return break! if @s.end?
82
98
 
@@ -90,8 +106,7 @@ class FastIgnore
90
106
  if nothing_emitted?
91
107
  never_anchored!
92
108
  else
93
- @re.append_any_dir
94
- anchored!
109
+ emit_any_dir
95
110
  end
96
111
  process_star_end_after_slash
97
112
  end
@@ -108,8 +123,8 @@ class FastIgnore
108
123
  unmatchable_rule! if @s.character_class_end?
109
124
 
110
125
  until @s.character_class_end?
126
+ next if process_character_class_range
111
127
  next if process_backslash
112
- next @re.append_character_class_dash if @s.dash?
113
128
  next if @re.append_escaped(@s.character_class_literal)
114
129
 
115
130
  unmatchable_rule!
@@ -118,6 +133,22 @@ class FastIgnore
118
133
  @re.append_character_class_close
119
134
  end
120
135
 
136
+ def process_character_class_range
137
+ start = @s.character_class_range_start
138
+ return unless start
139
+
140
+ start = start.delete_prefix('\\')
141
+
142
+ @re.append_escaped(start)
143
+
144
+ finish = @s.character_class_range_end.delete_prefix('\\')
145
+
146
+ return true unless start < finish
147
+
148
+ @re.append_character_class_dash
149
+ @re.append_escaped(finish)
150
+ end
151
+
121
152
  def process_end
122
153
  blank! if nothing_emitted?
123
154
 
@@ -125,6 +156,7 @@ class FastIgnore
125
156
  end
126
157
 
127
158
  def process_rule # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
159
+ expand_rule_path! if @expand_path_with
128
160
  anchored! if @s.slash?
129
161
 
130
162
  catch :break do
@@ -143,28 +175,24 @@ class FastIgnore
143
175
  end
144
176
  end
145
177
 
146
- def prefix # rubocop:disable Metrics/MethodLength
147
- out = ::FastIgnore::GitignoreRuleRegexpBuilder.new
148
- if @file_path
149
- out.append_start_anchor.append(@file_path.escaped)
150
- out.append_any_dir unless @anchored
178
+ def prefix
179
+ out = ::FastIgnore::PathRegexpBuilder.new
180
+
181
+ if @anchored
182
+ out.append_start_anchor
151
183
  else
152
- if @anchored
153
- out.append_start_anchor
154
- else
155
- out.append_start_dir_or_anchor
156
- end
184
+ out.append_dir_or_start_anchor
157
185
  end
158
186
  out
159
187
  end
160
188
 
161
189
  def build_rule
162
190
  @re.prepend(prefix)
163
- ::FastIgnore::Rule.new(@re.to_regexp, @negation, anchored_or_file_path, @dir_only)
164
- end
165
-
166
- def anchored_or_file_path
167
- @anchored || @file_path
191
+ if @negation
192
+ ::FastIgnore::Matchers::AllowPathRegexp.new(@re.to_regexp, @anchored, @dir_only)
193
+ else
194
+ ::FastIgnore::Matchers::IgnorePathRegexp.new(@re.to_regexp, @anchored, @dir_only)
195
+ end
168
196
  end
169
197
 
170
198
  def build
@@ -178,5 +206,16 @@ class FastIgnore
178
206
  build_rule
179
207
  end
180
208
  end
209
+
210
+ def expand_rule_path!
211
+ anchored! unless @s.match?(/\*/) # rubocop:disable Performance/StringInclude # it's StringScanner#match?
212
+ return unless @s.match?(%r{(?:[~/]|\.{1,2}/|.*/\.\./)})
213
+
214
+ dir_only! if @s.match?(%r{.*/\s*\z})
215
+
216
+ @s.string.replace(PathExpander.expand_path(@s.rest, @expand_path_with))
217
+ @s.string.delete_prefix!(@expand_path_with)
218
+ @s.pos = 0
219
+ end
181
220
  end
182
221
  end
@@ -0,0 +1,31 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'set'
4
+
5
+ class FastIgnore
6
+ class GitignoreRuleGroup < ::FastIgnore::RuleGroup
7
+ def initialize(root)
8
+ @root = root
9
+ @loaded_paths = Set[root]
10
+
11
+ super([
12
+ ::FastIgnore::Patterns.new('.git', root: '/'),
13
+ ::FastIgnore::Patterns.new(from_file: ::FastIgnore::GlobalGitignore.path(root: root), root: root),
14
+ ::FastIgnore::Patterns.new(from_file: "#{root}.git/info/exclude", root: root),
15
+ ::FastIgnore::Patterns.new(from_file: "#{root}.gitignore", root: root)
16
+ ], false)
17
+ end
18
+
19
+ def add_gitignore(dir)
20
+ return if @loaded_paths.include?(dir)
21
+
22
+ @loaded_paths << dir
23
+ matcher = ::FastIgnore::Patterns.new(from_file: "#{dir}.gitignore").build_matchers(allow: false)
24
+ @matchers += matcher unless !matcher || matcher.empty?
25
+ end
26
+
27
+ def add_gitignore_to_root(path)
28
+ add_gitignore(path) until @loaded_paths.include?(path = "#{::File.dirname(path)}/")
29
+ end
30
+ end
31
+ end
@@ -42,16 +42,36 @@ class FastIgnore
42
42
  skip(/\*\s*\z/)
43
43
  end
44
44
 
45
+ def two_star_end?
46
+ skip(/\*{2,}\s*\z/)
47
+ end
48
+
49
+ def star_slash_end?
50
+ skip(%r{\*/\s*\z})
51
+ end
52
+
53
+ def two_star_slash_end?
54
+ skip(%r{\*{2,}/\s*\z})
55
+ end
56
+
45
57
  def question_mark?
46
58
  skip(/\?/)
47
59
  end
48
60
 
49
- def dash?
50
- skip(/-/)
61
+ def character_class_literal
62
+ matched if scan(/[^\]\\][^\]\\-]*(?!-)/)
51
63
  end
52
64
 
53
- def character_class_literal
54
- matched if scan(/[^\]\-\\]+/)
65
+ def character_class_range_start
66
+ matched if scan(/(\\.|[^\\\]])(?=-(\\.|[^\\\]]))/)
67
+ end
68
+
69
+ def character_class_range_end
70
+ # we already confirmed this was going to match
71
+ # with the lookahead in character_class_range_start
72
+ skip(/-/)
73
+ scan(/(\\.|[^\\\]])/)
74
+ matched
55
75
  end
56
76
 
57
77
  def literal
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastIgnore
4
+ module Matchers
5
+ module AllowAnyDir
6
+ class << self
7
+ def squash_id
8
+ :allow_any_dir
9
+ end
10
+
11
+ def dir_only?
12
+ true
13
+ end
14
+
15
+ def file_only?
16
+ false
17
+ end
18
+
19
+ def squash(_)
20
+ self
21
+ end
22
+
23
+ def weight
24
+ 0
25
+ end
26
+
27
+ # :nocov:
28
+ def inspect
29
+ '#<AllowAnyDir>'
30
+ end
31
+ # :nocov:
32
+
33
+ def match?(_)
34
+ :allow
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastIgnore
4
+ module Matchers
5
+ class AllowPathRegexp
6
+ attr_reader :dir_only
7
+ alias_method :dir_only?, :dir_only
8
+ undef :dir_only
9
+
10
+ attr_reader :squash_id
11
+ attr_reader :rule
12
+
13
+ def initialize(rule, squashable, dir_only)
14
+ @rule = rule
15
+ @dir_only = dir_only
16
+ @squashable = squashable
17
+ @squash_id = squashable ? :allow : object_id
18
+
19
+ freeze
20
+ end
21
+
22
+ def squash(list)
23
+ self.class.new(::Regexp.union(list.map(&:rule)), @squashable, @dir_only)
24
+ end
25
+
26
+ def file_only?
27
+ false
28
+ end
29
+
30
+ def weight
31
+ 1
32
+ end
33
+
34
+ # :nocov:
35
+ def inspect
36
+ "#<AllowPathRegexp #{'dir_only ' if @dir_only}#{@rule.inspect}>"
37
+ end
38
+ # :nocov:
39
+
40
+ def match?(candidate)
41
+ :allow if @rule.match?(candidate.relative_path)
42
+ end
43
+ end
44
+ end
45
+ end