fast_ignore 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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