fast_ignore 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55e2502678383f7dec077fb00816f754ca795d6de214ae2950fd2343852e450b
4
- data.tar.gz: ef25eca3ec9c85578f4c74a808f5865d5586d2ce5919b976013723f6d17beedc
3
+ metadata.gz: 69021259195d30c9d3eb2e4461a779ef70b67d664370c5dff06d79f16ff009cb
4
+ data.tar.gz: f3e60391822c26b461316389277f3901801c89e1dfcea59e819976d2b4c4b761
5
5
  SHA512:
6
- metadata.gz: 7dbfbe53878ac26d8b857647da3356b47c687f081f5369270d41576b71b414fc5b6417d8ed0b7c9d72a7bd65e1c727760573586a223c23a16126ad64ac72a099
7
- data.tar.gz: 761d4d757d5660058be97dff4809415070bf610c8c5aa000e71bec1d36c3b7fcfdfe35e8ab1daf4586154d34d341a93dcf76f9055f942465bc7ae6a7cc280e9d
6
+ metadata.gz: c99e8a9d3be9addeca4d18d327e450b9d6c345b6db354beb612e7f7e999af743d4ee4f62df9b1be0dec4a03820f948c52f8cb53d202ff8d3242499bbc453da87
7
+ data.tar.gz: 2d17fe903303a913df5d763789517897e7c444c89069b3f81ce28df0136c1c5c6a94fd1c6f8080e17291713a75da4246da67c1c86a90c89a9084137276ba0b4d
data/.leftovers.yml ADDED
@@ -0,0 +1,5 @@
1
+ exclude_paths:
2
+ - vendor
3
+ rules:
4
+ - names: allowed? # public api
5
+ skip: true
data/.rubocop.yml CHANGED
@@ -211,3 +211,6 @@ Style/HashTransformKeys:
211
211
 
212
212
  Style/HashTransformValues:
213
213
  Enabled: true
214
+
215
+ Style/CommentedKeyword:
216
+ Enabled: false
@@ -8,15 +8,20 @@ cov
8
8
  cyclomatic
9
9
  enoent
10
10
  enumerables
11
+ env
11
12
  errno
12
13
  extensionless
13
14
  fancyignore
15
+ frotz
14
16
  gemfile
15
17
  github
16
18
  gitignore
17
19
  gitkeep
18
20
  hashbang
21
+ includefile
22
+ janky
19
23
  klass
24
+ llo
20
25
  nocov
21
26
  noninfringement
22
27
  params
@@ -33,9 +38,13 @@ rvm
33
38
  sherson
34
39
  simplecov
35
40
  stdin
41
+ substring
36
42
  sudo
43
+ symlinks
37
44
  tmp
38
45
  toplevel
39
46
  txt
40
47
  unrecursive
48
+ upcase
49
+ usr
41
50
  zsh
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ # v0.10.0
2
+ - patterns with middle slashes are anchored to the root (like the gitignore documentation, now that it more clearly explains)
3
+ - new shebang pattern (#!:), the previous version was extremely janky.
4
+ - now you can ignore by shebang pattern
5
+ - symlinks aren't followed when deciding if a path is a directory or not (this now matches git)
6
+ - documentation improvements
7
+ - root can be given as a path relative to PWD
8
+ - includes with 'a/**/d' now matches a/b/c/d properly
9
+ -
10
+
1
11
  # v0.9.0
2
12
  - speed improvements, which may break things (Specifically, only using relative paths internally, is about 30% faster (depending on root depth))
3
13
  - using a `ignore_files:` or `include_files:` that are outside the `root: (default $PWD)` will now raise an error.
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  This started as a way to quickly and natively ruby-ly parse gitignore files and find matching files.
6
6
  It's now gained an equivalent includes file functionality, ARGV awareness, and some shebang matching, while still being extremely fast, to be a one-stop file-list for your linter.
7
7
 
8
- Filter a directory tree using a .gitignore file. Recognises all of the [gitignore rules](https://www.git-scm.com/docs/gitignore#_pattern_format) ([except one](#known-issues))
8
+ Filter a directory tree using a .gitignore file. Recognises all of the [gitignore rules](https://www.git-scm.com/docs/gitignore#_pattern_format)
9
9
 
10
10
  ```ruby
11
11
  FastIgnore.new(relative: true).sort == `git ls-files`.split("\n").sort
@@ -34,61 +34,82 @@ $ gem install fast_ignore
34
34
  FastIgnore.new.each { |file| puts "#{file} is not ignored by the .gitignore file" }
35
35
  ```
36
36
 
37
- Like many other enumerables, `FastIgnore#each` can return an enumerator
37
+ ### `#each`, `#map` etc
38
+
39
+ The FastIgnore object is an enumerable and responds to all Enumerable methods
40
+
41
+ ```ruby
42
+ FastIgnore.new.to_a
43
+ FastIgnore.new.map { |file| file.upcase }
44
+ ```
45
+
46
+ Like other enumerables, `FastIgnore#each` can return an enumerator
38
47
 
39
48
  ```ruby
40
49
  FastIgnore.new.each.with_index { |file, index| puts "#{file}#{index}" }
41
50
  ```
42
51
 
52
+ ### `relative: true`
43
53
  By default, FastIgnore will return full paths. To return paths relative to the current working directory, use:
44
54
 
45
55
  ```ruby
46
56
  FastIgnore.new(relative: true).to_a
47
57
  ```
48
58
 
49
- By default, FastIgnore will look at the current working directory (PWD) for looking for .gitignore files, handling array rules, and the path that relative returns.
50
- To use a different directory:
51
- ```ruby
52
- FastIgnore.new(root: '/absolute/path/to/root').to_a
53
- ```
59
+ ### `root:`
54
60
 
55
- You can specify other gitignore-style files to ignore as well. Missing files will raise an `Errno::ENOENT` error.
61
+ By default, root is PWD (the current working directory)
62
+ This directory is used for:
63
+ - looking for .gitignore files
64
+ - as the root directory for array rules starting with `/` or ending with `/**`
65
+ - and the path that relative is relative to
66
+ - which files get checked
56
67
 
68
+ To use a different directory:
57
69
  ```ruby
58
- FastIgnore.new(ignore_files: '/absolute/path/to/my/ignore/file').to_a
59
- FastIgnore.new(ignore_files: ['/absolute/path/to/my/ignore/file', '/and/another']).to_a
70
+ FastIgnore.new(root: '/absolute/path/to/root').to_a
71
+ FastIgnore.new(root: '../relative/path/to/root').to_a
60
72
  ```
61
73
 
62
- You can also supply an array of rule strings.
74
+ ### `gitignore:`
63
75
 
76
+ By default, the .gitignore file in root directory is loaded.
77
+ To not do this use
64
78
  ```ruby
65
- FastIgnore.new(ignore_rules: '.DS_Store').to_a
66
- FastIgnore.new(ignore_rules: ['.git', '.gitkeep']).to_a
79
+ FastIgnore.new(gitignore: false).to_a
67
80
  ```
68
81
 
69
- To use only another ignore file or an array of rules, and not even try to load a gitignore file:
82
+ To raise an `Errno::ENOENT` error if the .gitignore file is not found use:
70
83
  ```ruby
71
- FastIgnore.new(ignore_files: '/absolute/path/to/my/ignore/file', gitignore: false).to_a
72
- FastIgnore.new(ignore_rules: %w{my*rule /and/another !rule}, gitignore: false).to_a
84
+ FastIgnore.new(gitignore: true).to_a
73
85
  ```
74
86
 
75
- By default, FastIgnore will look in the root directory for a gitignore file. If it's somewhere else:
87
+ If the gitignore file is somewhere else
76
88
  ```ruby
77
89
  FastIgnore.new(ignore_file: '/absolute/path/to/.gitignore', gitignore: false).to_a
78
90
  ```
79
91
  Note that the location of the .gitignore file will affect rules beginning with `/` or ending in `/**`
80
92
 
81
- To raise an `Errno::ENOENT` error if the .gitignore file is not found use:
93
+ ### `ignore_files:`
94
+ You can specify other gitignore-style files to ignore as well.
95
+ Missing files will raise an `Errno::ENOENT` error.
96
+
82
97
  ```ruby
83
- FastIgnore.new(gitignore: true).to_a
98
+ FastIgnore.new(ignore_files: '/absolute/path/to/my/ignore/file').to_a
99
+ FastIgnore.new(ignore_files: ['/absolute/path/to/my/ignore/file', '/and/another']).to_a
84
100
  ```
85
101
 
86
- To filter by extensionless files shebang/hashbang/etc:
102
+ ### `ignore_rules:`
103
+ You can also supply an array of rule strings.
104
+
87
105
  ```ruby
88
- FastIgnore.new(include_rules: '*.rb', include_shebangs: 'ruby').to_a
89
- FastIgnore.new(include_rules: '*.sh', include_shebangs: ['sh', 'bash', 'zsh']).to_a
106
+ FastIgnore.new(ignore_rules: '.DS_Store').to_a
107
+ FastIgnore.new(ignore_rules: ['.git', '.gitkeep']).to_a
108
+ FastIgnore.new(ignore_rules: ".git\n.gitkeep").to_a
90
109
  ```
91
110
 
111
+ # `#allowed?`
112
+
92
113
  To check if a single file is allowed, use
93
114
  ```ruby
94
115
  FastIgnore.new.allowed?('relative/path')
@@ -97,7 +118,7 @@ FastIgnore.new.allowed?('/absolute/path')
97
118
  FastIgnore.new.allowed?('~/home/path')
98
119
  ```
99
120
 
100
- ### Using an includes list.
121
+ ### `include_files:` and `include_rules:`
101
122
 
102
123
  Building on the gitignore format, FastIgnore also accepts a list of allowed or included files.
103
124
 
@@ -132,22 +153,50 @@ It assumes all rules are anchored unless they begin with `*` or `!*`.
132
153
 
133
154
  Note: it will *not* resolve e.g. `/../` in the middle of a rule that doesn't begin with any of `~`,`../`,`./`,`/`.
134
155
 
156
+ ### shebang rules
157
+
158
+ Sometimes you need to match files by their shebang rather than their path or filename
159
+
160
+ To match extensionless files by shebang/hashbang/etc:
161
+
162
+ Lines beginning with `#!:` will match whole words in the shebang line of extensionless files.
163
+ e.g.
164
+ ```gitignore
165
+ #!:ruby
166
+ ```
167
+ will match shebang lines: `#!/usr/bin/env ruby` or `#!/usr/bin/ruby` or `#!/usr/bin/ruby -w`
168
+ e.g.
169
+ ```gitignore
170
+ #!:bin/ruby
171
+ ```
172
+ will match `#!/bin/ruby` or `#!/usr/bin/ruby` or `#!/usr/bin/ruby -w`
173
+ Currently only exact substring matches are available, There's no special handling of * or / or etc.
174
+
175
+ ```ruby
176
+ FastIgnore.new(include_rules: ['*.rb', '#!:ruby']).to_a
177
+ FastIgnore.new(ignore_rules: ['*.sh', '#!:sh', '#!:bash', '#!:zsh']).to_a
178
+ ```
179
+
135
180
  ## Combinations
136
181
 
137
182
  In the simplest case a file must be allowed by each ignore file, each include file, and each array of rules. That is, they are combined using AND.
138
183
 
139
- To combine files using `OR`, that is, a file may be included by either file it doesn't have to be referred to in both:
140
-
184
+ To combine files using `OR`, that is, a file may be matched by either file it doesn't have to be referred to in both:
185
+ provide the files as strings to `include_rules:` or `ignore_rules:`
141
186
  ```ruby
142
- FastIgnore.new(include_files: StringIO.new([File.read('/my/path'), File.read('/another/path')]).join("\n")).to_a
187
+ FastIgnore.new(include_rules: [File.read('/my/path'), File.read('/another/path')])).to_a
143
188
  ```
189
+ This does unfortunately lose the file path as the root for `/` and `/**` rules.
190
+ If that's important, combine the files in the file system and use `include_files:` or `ignore_files:` as normal.
144
191
 
145
- To use the additional ARGV handling rules mentioned above for files
192
+ To use the additional ARGV handling rules mentioned above for files, read the file into the array as a string.
146
193
 
147
194
  ```ruby
148
195
  FastIgnore.new(argv_rules: ["my/rule", File.read('/my/path')]).to_a
149
196
  ```
150
197
 
198
+ This does unfortunately lose the file path as the root for `/` and `/**` rules.
199
+
151
200
  ## Known issues
152
201
  - Doesn't take into account project excludes in `.git/info/exclude`
153
202
  - Doesn't take into account globally ignored files in `git config core.excludesFile`.
data/Rakefile CHANGED
@@ -4,9 +4,11 @@ require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
5
  require 'rubocop/rake_task'
6
6
  require 'spellr/rake_task'
7
+ require 'leftovers/rake_task'
7
8
 
8
9
  RuboCop::RakeTask.new
9
10
  RSpec::Core::RakeTask.new(:spec)
10
11
  Spellr::RakeTask.generate_task
12
+ Leftovers::RakeTask.generate_task
11
13
 
12
- task default: [:spec, :rubocop, :spellr]
14
+ task default: [:spec, :rubocop, :spellr, :leftovers]
data/fast_ignore.gemspec CHANGED
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.require_paths = ['lib']
31
31
 
32
32
  spec.add_development_dependency 'bundler', '>= 1.17'
33
+ spec.add_development_dependency 'leftovers', '>= 0.2.1'
33
34
  spec.add_development_dependency 'pry', '> 0'
34
35
  spec.add_development_dependency 'rake', '>= 12.3.3'
35
36
  spec.add_development_dependency 'rspec', '~> 3.0'
data/lib/fast_ignore.rb CHANGED
@@ -22,22 +22,27 @@ class FastIgnore
22
22
  end
23
23
  # :nocov:
24
24
 
25
- def initialize(
25
+ def initialize( # rubocop:disable Metrics/MethodLength
26
26
  relative: false,
27
- root: ::Dir.pwd,
27
+ root: nil,
28
28
  include_shebangs: nil,
29
29
  **rule_set_builder_args
30
30
  )
31
- @root = root.end_with?('/') ? root : "#{root}/"
32
- @shebang_pattern = prepare_shebang_pattern(include_shebangs)
31
+ # :nocov:
32
+ if include_shebangs && !Array(include_shebangs).empty?
33
+
34
+ warn <<~WARNING
35
+ Removed FastIgnore `include_shebangs:` argument.
36
+ It will be ignored. Please replace with the include_rules: in the shebang format
37
+ https://github.com/robotdana/fast_ignore#shebang-rules
38
+ WARNING
39
+ end
40
+ # :nocov:
33
41
 
34
- rule_sets = ::FastIgnore::RuleSetBuilder.from_args(
35
- root: @root,
36
- **rule_set_builder_args
37
- )
42
+ root = root ? File.expand_path(root, ::Dir.pwd) : ::Dir.pwd
43
+ @root = "#{root}/"
38
44
 
39
- @include_rule_sets, @ignore_rule_sets = rule_sets.partition(&:allow?)
40
- @has_include_rule_sets = !@include_rule_sets.empty?
45
+ @rule_sets = ::FastIgnore::RuleSetBuilder.from_args(root: @root, **rule_set_builder_args)
41
46
  @relative = relative
42
47
 
43
48
  freeze
@@ -51,52 +56,35 @@ class FastIgnore
51
56
  end
52
57
  end
53
58
 
54
- def allowed?(path) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
59
+ def allowed?(path)
55
60
  full_path = ::File.expand_path(path, @root)
56
61
  return false unless full_path.start_with?(@root)
57
62
 
58
- dir = ::File.stat(full_path).directory? # shortcut for exists? && directory?
59
-
63
+ dir = ::File.lstat(full_path).directory?
60
64
  return false if dir
61
65
 
62
66
  relative_path = full_path.delete_prefix(@root)
67
+ filename = ::File.basename(relative_path)
63
68
 
64
- return false unless @ignore_rule_sets.all? { |r| r.allowed_recursive?(relative_path, dir) }
65
- return @include_rule_sets.all? { |r| r.allowed_recursive?(relative_path, dir) } unless @shebang_pattern
66
-
67
- (@has_include_rule_sets &&
68
- @include_rule_sets.all? { |r| r.allowed_unrecursive?(relative_path, false) }) ||
69
- match_shebang?(full_path, ::File.basename(relative_path))
69
+ @rule_sets.all? { |r| r.allowed_recursive?(relative_path, dir, filename) }
70
70
  rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
71
71
  false
72
72
  end
73
73
 
74
74
  private
75
75
 
76
- def each_allowed(full_path = @root, relative_path = '', &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
77
- ::Dir.each_child(full_path) do |basename|
76
+ def each_allowed(full_path = @root, relative_path = '', &block) # rubocop:disable Metrics/MethodLength
77
+ ::Dir.each_child(full_path) do |filename|
78
78
  begin
79
- full_child = full_path + basename
80
- relative_child = relative_path + basename
81
- dir = ::File.directory?(full_child)
79
+ full_child = full_path + filename
80
+ relative_child = relative_path + filename
81
+ dir = ::File.lstat(full_child).directory?
82
82
 
83
- next unless @ignore_rule_sets.all? { |r| r.allowed_unrecursive?(relative_child, dir) }
83
+ next unless @rule_sets.all? { |r| r.allowed_unrecursive?(relative_child, dir, filename) }
84
84
 
85
85
  if dir
86
- next unless @shebang_pattern || @include_rule_sets.all? { |r| r.allowed_unrecursive?(relative_child, dir) }
87
-
88
86
  each_allowed("#{full_child}/", "#{relative_child}/", &block)
89
87
  else
90
- if @shebang_pattern
91
- unless (@has_include_rule_sets &&
92
- @include_rule_sets.all? { |r| r.allowed_unrecursive?(relative_child, dir) }) ||
93
- match_shebang?(full_child, basename)
94
- next
95
- end
96
- else
97
- next unless @include_rule_sets.all? { |r| r.allowed_unrecursive?(relative_child, dir) }
98
- end
99
-
100
88
  yield(@relative ? relative_child : full_child)
101
89
  end
102
90
  rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
@@ -104,28 +92,4 @@ class FastIgnore
104
92
  end
105
93
  end
106
94
  end
107
-
108
- def match_shebang?(path, basename)
109
- return false if basename.include?('.')
110
-
111
- begin
112
- f = ::File.new(path)
113
- # i can't imagine a shebang being longer than 20 characters, lets multiply that by 10 just in case.
114
- fragment = f.sysread(256)
115
- f.close
116
- rescue ::SystemCallError, ::EOFError
117
- return false
118
- end
119
-
120
- @shebang_pattern.match?(fragment)
121
- end
122
-
123
- def prepare_shebang_pattern(rules)
124
- return if !rules || (rules = Array(rules)).empty?
125
-
126
- rules = rules.flat_map { |s| s.to_s.split("\n") }
127
- rules_re = rules.map { |s| Regexp.escape(s.to_s) }.join('|')
128
-
129
- /\A#!.*\b(?:#{rules_re})\b/.freeze
130
- end
131
95
  end
@@ -5,22 +5,50 @@ class FastIgnore
5
5
  module Backports
6
6
  module DeletePrefixSuffix
7
7
  refine ::String do
8
+ # delete_prefix!(prefix) -> self or nil
9
+ # Deletes leading prefix from str, returning nil if no change was made.
10
+ #
11
+ # "hello".delete_prefix!("hel") #=> "lo"
12
+ # "hello".delete_prefix!("llo") #=> nil
8
13
  def delete_prefix!(str)
9
- slice!(0..(str.length - 1)) if start_with?(str)
14
+ return unless start_with?(str)
15
+
16
+ slice!(0..(str.length - 1))
10
17
  self
11
18
  end
12
19
 
20
+ # delete_suffix!(suffix) -> self or nil
21
+ # Deletes trailing suffix from str, returning nil if no change was made.
22
+ #
23
+ # "hello".delete_suffix!("llo") #=> "he"
24
+ # "hello".delete_suffix!("hel") #=> nil
13
25
  def delete_suffix!(str)
14
- slice!(-str.length..-1) if end_with?(str)
26
+ return unless end_with?(str)
27
+
28
+ slice!(-str.length..-1)
15
29
  self
16
30
  end
17
31
 
32
+ # delete_prefix(prefix) -> new_str click to toggle source
33
+ # Returns a copy of str with leading prefix deleted.
34
+ #
35
+ # "hello".delete_prefix("hel") #=> "lo"
36
+ # "hello".delete_prefix("llo") #=> "hello"
18
37
  def delete_prefix(str)
19
- dup.delete_prefix!(str)
38
+ s = dup
39
+ s.delete_prefix!(str)
40
+ s
20
41
  end
21
42
 
22
- def delete_suffix(str)
23
- dup.delete_suffix!(str)
43
+ # delete_suffix(suffix) -> new_str
44
+ # Returns a copy of str with trailing suffix deleted.
45
+ #
46
+ # "hello".delete_suffix("llo") #=> "he"
47
+ # "hello".delete_suffix("hel") #=> "hello"
48
+ def delete_suffix(str) # leftovers:allowed
49
+ s = dup
50
+ s.delete_suffix!(str)
51
+ s
24
52
  end
25
53
  end
26
54
  end
@@ -12,21 +12,57 @@ class FastIgnore
12
12
  alias_method :negation?, :negation
13
13
  attr_reader :dir_only
14
14
  alias_method :dir_only?, :dir_only
15
+ attr_reader :file_only
16
+ alias_method :file_only?, :file_only
15
17
 
18
+ attr_reader :shebang
16
19
  attr_reader :rule
17
20
 
18
- def initialize(rule, dir_only, negation)
21
+ def initialize(rule, dir_only, file_only, negation, shebang = nil)
19
22
  @rule = rule
20
23
  @dir_only = dir_only
24
+ @file_only = file_only
21
25
  @negation = negation
26
+ @shebang = shebang
22
27
 
23
28
  freeze
24
29
  end
25
30
 
26
31
  # :nocov:
27
32
  def inspect
28
- "#<Rule #{'!' if negation?}#{rule}#{'/' if dir_only?}>"
33
+ if shebang
34
+ "#<Rule #{'allow ' if negation?}#!:#{shebang.to_s[15..-4]}>"
35
+ else
36
+ "#<Rule #{'!' if negation?}#{rule}#{'/' if dir_only?}>"
37
+ end
29
38
  end
30
39
  # :nocov:
40
+
41
+ def match?(path, filename)
42
+ if @shebang
43
+ match_shebang?(path, filename)
44
+ else
45
+ ::File.fnmatch?(@rule, path, 14)
46
+ end
47
+ end
48
+
49
+ def match_shebang?(path, filename)
50
+ return false if filename.include?('.')
51
+
52
+ first_line(path)&.match?(@shebang)
53
+ end
54
+
55
+ def first_line(path)
56
+ file = ::File.new(path)
57
+ first_line = file.sysread(25)
58
+ first_line += file.sysread(50) until first_line.include?("\n")
59
+ file.close
60
+ first_line
61
+ rescue ::EOFError, ::SystemCallError
62
+ # :nocov:
63
+ file&.close
64
+ # :nocov:
65
+ first_line
66
+ end
31
67
  end
32
68
  end
@@ -11,13 +11,15 @@ class FastIgnore
11
11
 
12
12
  # rule or nil
13
13
  class << self
14
- def new_rule(rule, rule_set:, allow: false, expand_path: false, file_root: nil) # rubocop:disable Metrics/MethodLength
14
+ def new_rule(rule, rule_set:, allow: false, expand_path: false, file_root: nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
15
15
  rule = rule.dup
16
16
  strip(rule)
17
- dir_only = extract_dir_only(rule)
17
+
18
+ return rule_set.append_rules(false, shebang_rules(rule, allow)) if extract_shebang(rule)
18
19
 
19
20
  return if skip?(rule)
20
21
 
22
+ dir_only = extract_dir_only(rule)
21
23
  negation = extract_negation(rule, allow)
22
24
 
23
25
  expand_rule_path(rule, expand_path) if expand_path
@@ -36,34 +38,41 @@ class FastIgnore
36
38
 
37
39
  private
38
40
 
39
- DOT = '.'
40
- def rules(rule, allow, dir_only, negation)
41
- rules = [::FastIgnore::Rule.new(rule, dir_only, negation)]
41
+ def shebang_rules(rule, allow)
42
+ rules = [::FastIgnore::Rule.new(nil, false, true, allow, /\A#!.*\b#{Regexp.escape(rule)}\b/)]
42
43
  return rules unless allow
43
44
 
44
- rules << ::FastIgnore::Rule.new("#{rule}/**/*", false, negation)
45
- parent = File.dirname(rule)
46
- while parent != DOT
47
- rules << ::FastIgnore::Rule.new(parent.freeze, true, true)
48
- parent = File.dirname(parent)
49
- end
45
+ rules << ::FastIgnore::Rule.new('**/*', true, false, true)
50
46
  rules
51
47
  end
52
48
 
53
- def extract_negation(rule, allow)
54
- return allow unless rule.start_with?('!')
49
+ def rules(rule, allow, dir_only, negation)
50
+ rules = [::FastIgnore::Rule.new(rule, dir_only, false, negation)]
51
+ return rules unless allow
52
+
53
+ rules << ::FastIgnore::Rule.new("#{rule}/**/*", false, false, negation)
54
+ rules + ancestor_rules(rule)
55
+ end
56
+
57
+ def ancestor_rules(parent)
58
+ ancestor_rules = []
55
59
 
56
- rule.slice!(0)
60
+ while (parent = ::File.dirname(parent)) != '.'
61
+ rule = ::File.basename(parent) == '**' ? "#{parent}/*" : parent.freeze
62
+ ancestor_rules << ::FastIgnore::Rule.new(rule, true, false, true)
63
+ end
57
64
 
58
- !allow
65
+ ancestor_rules
59
66
  end
60
67
 
61
- def extract_dir_only(rule)
62
- return false unless rule.end_with?('/')
68
+ def extract_negation(rule, allow)
69
+ return allow unless rule.delete_prefix!('!')
63
70
 
64
- rule.chop!
71
+ not allow
72
+ end
65
73
 
66
- true
74
+ def extract_dir_only(rule)
75
+ rule.delete_suffix!('/')
67
76
  end
68
77
 
69
78
  def strip(rule)
@@ -72,9 +81,7 @@ class FastIgnore
72
81
  end
73
82
 
74
83
  def anchored?(rule)
75
- rule.start_with?('/') ||
76
- rule.end_with?('/**') ||
77
- rule.include?('/**/')
84
+ rule.include?('/') # we've already removed the trailing '/' with extract_dir_only
78
85
  end
79
86
 
80
87
  EXPAND_PATH_RE = %r{^(?:[~/]|\.{1,2}/)}.freeze
@@ -84,6 +91,10 @@ class FastIgnore
84
91
  rule.prepend('/') unless rule.start_with?('*') || rule.start_with?('/')
85
92
  end
86
93
 
94
+ def extract_shebang(rule)
95
+ rule.delete_prefix!('#!:') && (rule.strip! || true)
96
+ end
97
+
87
98
  def skip?(rule)
88
99
  rule.empty? || rule.start_with?('#')
89
100
  end
@@ -2,10 +2,6 @@
2
2
 
3
3
  class FastIgnore
4
4
  class RuleSet
5
- attr_reader :rules
6
- attr_reader :allow
7
- alias_method :allow?, :allow
8
-
9
5
  def initialize(allow: false)
10
6
  @dir_rules = []
11
7
  @file_rules = []
@@ -21,17 +17,18 @@ class FastIgnore
21
17
  super
22
18
  end
23
19
 
24
- def allowed_recursive?(path, dir)
20
+ def allowed_recursive?(path, dir, filename)
25
21
  @allowed_recursive.fetch(path) do
26
22
  @allowed_recursive[path] =
27
- allowed_recursive?(::File.dirname(path), true) && allowed_unrecursive?(path, dir)
23
+ allowed_recursive?(::File.dirname(path), true, nil) && allowed_unrecursive?(path, dir, filename)
28
24
  end
29
25
  end
30
26
 
31
- def allowed_unrecursive?(path, dir)
27
+ def allowed_unrecursive?(path, dir, filename)
32
28
  (dir ? @dir_rules : @file_rules).reverse_each do |rule|
33
29
  # 14 = Rule::FNMATCH_OPTIONS
34
- return rule.negation? if ::File.fnmatch?(rule.rule, path, 14)
30
+
31
+ return rule.negation? if rule.match?(path, filename)
35
32
  end
36
33
 
37
34
  (not @allow) || (@any_not_anchored if dir)
@@ -39,18 +36,19 @@ class FastIgnore
39
36
 
40
37
  def append_rules(anchored, rules)
41
38
  rules.each do |rule|
42
- @dir_rules << rule
43
- @file_rules << rule unless rule.dir_only?
39
+ (@dir_rules << rule) unless rule.file_only?
40
+ (@file_rules << rule) unless rule.dir_only?
44
41
  @any_not_anchored ||= !anchored
42
+ @has_shebang_rules ||= rule.shebang
45
43
  end
46
44
  end
47
45
 
48
- def length
49
- @dir_rules.length
46
+ def weight
47
+ @dir_rules.length + (@has_shebang_rules ? 10 : 0)
50
48
  end
51
49
 
52
50
  def empty?
53
- @dir_rules.empty?
51
+ @dir_rules.empty? && @file_rules.empty?
54
52
  end
55
53
  end
56
54
  end
@@ -30,7 +30,7 @@ class FastIgnore
30
30
 
31
31
  rule_sets.compact!
32
32
  rule_sets.reject!(&:empty?)
33
- rule_sets.sort_by!(&:length)
33
+ rule_sets.sort_by!(&:weight)
34
34
  rule_sets
35
35
  end
36
36
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class FastIgnore
4
- VERSION = '0.9.0'
4
+ VERSION = '0.10.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast_ignore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dana Sherson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-19 00:00:00.000000000 Z
11
+ date: 2020-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: leftovers
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.1
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: pry
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -130,6 +144,7 @@ extensions: []
130
144
  extra_rdoc_files: []
131
145
  files:
132
146
  - ".gitignore"
147
+ - ".leftovers.yml"
133
148
  - ".rspec"
134
149
  - ".rubocop.yml"
135
150
  - ".simplecov"
@@ -146,7 +161,6 @@ files:
146
161
  - bin/console
147
162
  - bin/ls
148
163
  - bin/ls-disable-gems
149
- - bin/ls-shebangs
150
164
  - bin/setup
151
165
  - fast_ignore.gemspec
152
166
  - lib/fast_ignore.rb
data/bin/ls-shebangs DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require_relative '../lib/fast_ignore'
5
-
6
- puts FastIgnore.new(relative: true, include_shebangs: ARGV).to_a