fast_ignore 0.10.1 → 0.13.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: f3d94c089fb5c75d6c9d0650d2391d03425c487c81296495f44f35ed1f6d80ac
4
- data.tar.gz: a9bba05df8121aa49d38b39c7aaaa0e3c7d2b30e4b874d083fe517fa36846b97
3
+ metadata.gz: ee7e9eb231ff6441ca703c935e9c270e4cb6c2f8f3216d993d4fc891807e77f6
4
+ data.tar.gz: 68f1ace7ab6420ce140a43a747a969f29401eba718dd9d1e94b9538774b0ac6d
5
5
  SHA512:
6
- metadata.gz: e8954fbef86b13d7948744b2c803319b261d67b6afe83412f1e099a377b17adae2aa1106a3445e6f5604449130c8dfc815331fe104f205ca09b87f5ff8caa69e
7
- data.tar.gz: e53891e9d8b056a62ede7399695deb2911d5836ad5b8442b2ef90d7876fb4d794f1665697df6e7eeecf616f828217fce841f96c9bdfd39e73ae6c89594a65d5c
6
+ metadata.gz: 83609a93cf79d3d4063e69d213450d85f81c4b9bb7c1200468e46fa7fce01602c751c4afd6dbaef652d1761c0f180fa5806ff29d0913ca184a5e41850875a8d0
7
+ data.tar.gz: dbf3e341e671fa1a51dce4c885c04e1eadf0717d78ffd94f1746c63b1024f78bd9d4b6915e7b892ca3af21d27ef2566df980be41101573c3dd586384b6f6d7c6
@@ -1,3 +1,22 @@
1
+ # v0.13.0
2
+ - Attempt to improve documentation structure
3
+ - Remove `gitignore: true` raising `Errno::ENOENT` if root:/.gitignore didn't exist. I can't think of a use. Now `gitignore: true` is just the default behaviour.
4
+ - Don't ignore `.git` if `gitignore: false`.
5
+
6
+ # v0.12.1
7
+ - Reads all relevant git config files when finding a global .gitignore
8
+
9
+ # v0.12.0
10
+ - Reads all relevant gitignore files (nested .gitignore files, global .gitignore referred to in .gitconfig, and .git/info/exclude)
11
+
12
+ # v0.11.0
13
+ - major performance improvement (use regexp rather than fnmatch)
14
+ - optionally pass directory: and content: into allowed? if these are already loaded.
15
+
16
+ # v0.10.2
17
+ - add FastIgnore#=== as an alias for FastIgnore#allowed? so that FastIgnore objects can be used for case statements.
18
+ - Fix shebangs in non-pwd-root situations
19
+
1
20
  # v0.10.1
2
21
  - Add option to follow symlinks (turns out i needed it)
3
22
  - performance improvements
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # FastIgnore
2
2
 
3
- [![travis](https://travis-ci.org/robotdana/fast_ignore.svg?branch=master)](https://travis-ci.org/robotdana/fast_ignore)
3
+ [![travis](https://travis-ci.com/robotdana/fast_ignore.svg?branch=master)](https://travis-ci.com/robotdana/fast_ignore)
4
+ [![Gem Version](https://badge.fury.io/rb/fast_ignore.svg)](https://rubygems.org/gems/fast_ignore)
4
5
 
5
6
  This started as a way to quickly and natively ruby-ly parse gitignore files and find matching files.
6
7
  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.
@@ -11,6 +12,19 @@ Filter a directory tree using a .gitignore file. Recognises all of the [gitignor
11
12
  FastIgnore.new(relative: true).sort == `git ls-files`.split("\n").sort
12
13
  ```
13
14
 
15
+ ## Features
16
+
17
+ - Fast (faster than using `` `git ls-files`.split("\n") `` for small repos (because it avoids the overhead of ``` `` ```))
18
+ - Supports ruby 2.4-2.7 & jruby
19
+ - supports all [gitignore rule patterns](https://git-scm.com/docs/gitignore#_pattern_format)
20
+ - doesn't require git to be installed
21
+ - supports a gitignore-esque "include" patterns. ([`include_rules:`](#include_rules)/[`include_files:`](#include_files))
22
+ - supports an expansion of include patterns, expanding and anchoring paths ([`argv_rules:`](#argv_rules))
23
+ - supports [matching by shebang](#shebang_rules) rather than filename for extensionless files: `#!:`
24
+ - reads .gitignore in all subdirectories
25
+ - reads .git/info/excludes
26
+ - reads the ignore file mentioned in your git config
27
+
14
28
  ## Installation
15
29
 
16
30
  Add this line to your application's Gemfile:
@@ -36,29 +50,71 @@ FastIgnore.new.each { |file| puts "#{file} is not ignored by the .gitignore file
36
50
 
37
51
  ### `#each`, `#map` etc
38
52
 
39
- The FastIgnore object is an enumerable and responds to all Enumerable methods
53
+ This yields paths that are _not_ ignored by the gitignore, i.e. the paths that would be returned by `git ls-files`.
54
+
55
+ A FastIgnore instance is an Enumerable and responds to all Enumerable methods:
40
56
 
41
57
  ```ruby
42
58
  FastIgnore.new.to_a
43
59
  FastIgnore.new.map { |file| file.upcase }
44
60
  ```
45
61
 
46
- Like other enumerables, `FastIgnore#each` can return an enumerator
62
+ Like other enumerables, `FastIgnore#each` can return an enumerator:
47
63
 
48
64
  ```ruby
49
65
  FastIgnore.new.each.with_index { |file, index| puts "#{file}#{index}" }
50
66
  ```
51
67
 
68
+ **Warning: Do not change directory (e.g. `Dir.chdir`) in the block.**
69
+
70
+ ### `#allowed?`
71
+
72
+ To check if a single path is allowed, use
73
+ ```ruby
74
+ FastIgnore.new.allowed?('relative/path')
75
+ FastIgnore.new.allowed?('./relative/path')
76
+ FastIgnore.new.allowed?('/absolute/path')
77
+ FastIgnore.new.allowed?('~/home/path')
78
+ ```
79
+
80
+ Relative paths will be considered relative to the [`root:`](#root) directory, not the current directory.
81
+
82
+ This is aliased as `===` so you can use a FastIgnore instance in case statements.
83
+ ```ruby
84
+ case my_path
85
+ when FastIgnore.new
86
+ puts(my_path)
87
+ end
88
+ ```
89
+
90
+ It's recommended to save the FastIgnore instance to a variable to avoid having to read and parse the gitignore file and gitconfig files repeatedly.
91
+
92
+ See [Optimising allowed](#optimising_allowed) for ways to make this even faster
93
+
94
+ **Note: A file must exist at that path and not be a directory for it to be considered allowed.**
95
+ Essentially it can be thought of as `` `git ls-files`.include?(path) `` but much faster.
96
+ This excludes all directories and all possible path names that don't exist.
97
+
98
+
52
99
  ### `relative: true`
53
- By default, FastIgnore will return full paths. To return paths relative to the current working directory, or the supplied [`root:`](#root), use:
100
+
101
+ **Default: false**
102
+
103
+ When `relative: false`: FastIgnore#each will yield full paths.
104
+ When `relative: true`: FastIgnore#each will yield paths relative to the [`root:`](#root) directory
54
105
 
55
106
  ```ruby
56
107
  FastIgnore.new(relative: true).to_a
57
108
  ```
58
109
 
59
110
  ### `follow_symlinks: true`
60
- By default, FastIgnore will match git's behaviour and not follow symbolic links.
61
- To make it follow symlinks, use:
111
+
112
+ **Default: false**
113
+
114
+ When `follow_symlinks: false`: FastIgnore#each will match git's behaviour and not follow symbolic links.
115
+ 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.
116
+
117
+ **This doesn't use the real path for matching or yield or return it.**
62
118
 
63
119
  ```ruby
64
120
  FastIgnore.new(follow_symlinks: true).to_a
@@ -66,12 +122,16 @@ FastIgnore.new(follow_symlinks: true).to_a
66
122
 
67
123
  ### `root:`
68
124
 
69
- By default, root is PWD (the current working directory)
125
+ **Default: Dir.pwd ($PWD, the current working directory)**
126
+
70
127
  This directory is used for:
71
- - looking for .gitignore files
72
- - as the root directory for array rules starting with `/` or ending with `/**`
73
- - and the path that relative is relative to
74
- - which files get checked
128
+ - the location of `.git/core/exclude`
129
+ - the ancestor of all non-global [automatically loaded `.gitignore` files](#gitignore_false)
130
+ - the root directory for array rules ([`ignore_rules:`](#ignore_rules), [`include_rules:`](#include_rules), [`argv_rules:`](#argv_rules)) containing `/`
131
+ - the path that [`relative:`](#relative_true) is relative to
132
+ - the ancestor of all paths yielded by [`#each`](#each_map_etc)
133
+ - the path that [`#allowed?`](#allowed) considers relative paths relative to
134
+ - the ancestor of all [`include_files:`](#include_files) and [`ignore_files:`](#ignore_files)
75
135
 
76
136
  To use a different directory:
77
137
  ```ruby
@@ -79,36 +139,49 @@ FastIgnore.new(root: '/absolute/path/to/root').to_a
79
139
  FastIgnore.new(root: '../relative/path/to/root').to_a
80
140
  ```
81
141
 
142
+ A relative root will be found relative to the current working directory when the FastIgnore instance is initialized, and that will be the last time the current working directory is relevant.
143
+
144
+ **Note: Changes to the current working directory (e.g. with `Dir.chdir`), after initialising a FastIgnore instance, will _not_ affect the FastIgnore instance. `root:` will always be what it was when the instance was initialized.**
145
+
82
146
  ### `gitignore:`
83
147
 
84
- By default, the .gitignore file in root directory is loaded.
85
- To not do this use
86
- ```ruby
87
- FastIgnore.new(gitignore: false).to_a
88
- ```
148
+ **Default: true**
89
149
 
90
- To raise an `Errno::ENOENT` error if the .gitignore file is not found use:
91
- ```ruby
92
- FastIgnore.new(gitignore: true).to_a
93
- ```
150
+ When `gitignore: true`: the .gitignore file in the [`root:`](#root) directory is loaded, plus any .gitignore files in its subdirectories, the global git ignore file as described in git config, and .git/info/exclude. `.git` directories are also excluded to match the behaviour of `git ls-files`.
151
+ When `gitignore: false`: no ignore files or git config files are automatically read, and `.git` will not be automatically excluded.
94
152
 
95
- If the gitignore file is somewhere else
96
153
  ```ruby
97
- FastIgnore.new(ignore_file: '/absolute/path/to/.gitignore', gitignore: false).to_a
154
+ FastIgnore.new(gitignore: false).to_a
98
155
  ```
99
- Note that the location of the .gitignore file will affect rules beginning with `/` or ending in `/**`
100
156
 
101
157
  ### `ignore_files:`
158
+
159
+ **This is a list of files in the gitignore format to parse and match paths against, not a list of files to ignore** If you want an array of files use [`ignore_rules:`](#ignore_rules)
160
+
161
+ Additional gitignore-style files, either as a path or an array of paths.
162
+
102
163
  You can specify other gitignore-style files to ignore as well.
103
164
  Missing files will raise an `Errno::ENOENT` error.
104
165
 
166
+ Relative paths are relative to the [`root:`](#root) directory.
167
+ Absolute paths also need to be within the [`root:`](#root) directory.
168
+
169
+
105
170
  ```ruby
106
- FastIgnore.new(ignore_files: '/absolute/path/to/my/ignore/file').to_a
171
+ FastIgnore.new(ignore_files: 'relative/path/to/my/ignore/file').to_a
107
172
  FastIgnore.new(ignore_files: ['/absolute/path/to/my/ignore/file', '/and/another']).to_a
108
173
  ```
109
174
 
175
+ Note: the location of the files will affect rules beginning with or containing `/`.
176
+
177
+ To avoid raising `Errno::ENOENT` when the file doesn't exist:
178
+ ```ruby
179
+ FastIgnore.new(ignore_files: ['/ignore/file'].select { |f| File.exist?(f) }).to_a
180
+ ```
181
+
110
182
  ### `ignore_rules:`
111
- You can also supply an array of rule strings.
183
+
184
+ This can be a string, or an array of strings, and multiline strings can be used with one rule per line.
112
185
 
113
186
  ```ruby
114
187
  FastIgnore.new(ignore_rules: '.DS_Store').to_a
@@ -116,108 +189,142 @@ FastIgnore.new(ignore_rules: ['.git', '.gitkeep']).to_a
116
189
  FastIgnore.new(ignore_rules: ".git\n.gitkeep").to_a
117
190
  ```
118
191
 
119
- # `#allowed?`
192
+ These rules use the [`root:`](#root) argument to resolve rules containing `/`.
193
+
194
+ ### `include_files:`
195
+
196
+ **This is an array of files in the gitignore format to parse and match paths against, not a list of files to include.** If you want an array of files use [`include_rules:`](#include_rules).
197
+
198
+ Building on the gitignore format, FastIgnore also accepts rules to include matching paths (rather than ignoring them).
199
+ A rule matching a directory will include all descendants of that directory.
200
+
201
+ These rules can be provided in files either as absolute or relative paths, or an array of paths.
202
+ Relative paths are relative to the [`root:`](#root) directory.
203
+ Absolute paths also need to be within the [`root:`](#root) directory.
120
204
 
121
- To check if a single file is allowed, use
122
205
  ```ruby
123
- FastIgnore.new.allowed?('relative/path')
124
- FastIgnore.new.allowed?('./relative/path')
125
- FastIgnore.new.allowed?('/absolute/path')
126
- FastIgnore.new.allowed?('~/home/path')
206
+ FastIgnore.new(include_files: 'my_include_file').to_a
207
+ FastIgnore.new(include_files: ['/absolute/include/file', './relative/include/file']).to_a
127
208
  ```
128
209
 
129
- ### `include_files:` and `include_rules:`
210
+ Missing files will raise an `Errno::ENOENT` error.
130
211
 
131
- Building on the gitignore format, FastIgnore also accepts a list of allowed or included files.
212
+ To avoid raising `Errno::ENOENT` when the file doesn't exist:
213
+ ```ruby
214
+ FastIgnore.new(include_files: ['include/file'].select { |f| File.exist?(f) }).to_a
215
+ ```
132
216
 
133
- ```gitignore
134
- # a line like this means any files named foo will be included
135
- # as well as any files within directories named foo
136
- foo
137
- # a line beginning with a slash will be anything in a directory that is a child of the $PWD
138
- /foo
139
- # a line ending in a slash will will include any files in any directories named foo
140
- # but not any files named foo
141
- foo/
142
- fo*
143
- !foe
144
- # otherwise this format deals with !'s, *'s and ?'s and etc as you'd expect from gitignore.
145
- ```
146
-
147
- These can be passed either as files or as an array or string rules
148
- ```ruby
149
- FastIgnore.new(include_files: '/absolute/path/to/my/include/file', gitignore: false).to_a
217
+ **Note: All paths checked must not be excluded by any ignore files AND each included by include file separately AND the [`include_rules:`](#include_rules) AND the [`argv_rules:`](#argv_rules). see [Combinations](#combinations) for solutions to using OR.**
218
+
219
+ ### `include_rules:`
220
+
221
+ Building on the gitignore format, FastIgnore also accepts rules to include matching paths (rather than ignoring them).
222
+ A rule matching a directory will include all descendants of that directory.
223
+
224
+ This can be a string, or an array of strings, and multiline strings can be used with one rule per line.
225
+ ```ruby
150
226
  FastIgnore.new(include_rules: %w{my*rule /and/another !rule}, gitignore: false).to_a
151
227
  ```
152
228
 
153
- There is an additional argument meant for dealing with humans and `ARGV` values.
229
+ Rules use the [`root:`](#root) argument to resolve rules containing `/`.
230
+
231
+ **Note: All paths checked must not be excluded by any ignore files AND each included by [include file](#include_files) separately AND the `include_rules:` AND the [`argv_rules:`](#argv_rules). see [Combinations](#combinations) for solutions to using OR.**
232
+
233
+ ### `argv_rules:`
234
+ This is like [`include_rules:`](#include_rules) with additional features meant for dealing with humans and `ARGV` values.
235
+
236
+ It expands rules that are absolute paths, and paths beginning with `~`, `../` and `./` (with and without `!`).
237
+ This means rules beginning with `/` are absolute. Not relative to [`root:`](#root).
238
+
239
+ Additionally it assumes all rules are relative to the [`root:`](#root) directory (after resolving absolute paths) unless they begin with `*` (or `!*`).
240
+
241
+ This can be a string, or an array of strings, and multiline strings can be used with one rule per line.
154
242
 
155
243
  ```ruby
156
244
  FastIgnore.new(argv_rules: ['./a/pasted/path', '/or/a/path/from/stdin', 'an/argument', '*.txt']).to_a
157
245
  ```
158
246
 
159
- It resolves absolute paths, and paths beginning with `~`, `../` and `./` (with and without `!`)
160
- It assumes all rules are anchored unless they begin with `*` or `!*`.
247
+ **Warning: it will *not* expand e.g. `/../` in the middle of a rule that doesn't begin with any of `~`,`../`,`./`,`/`.**
161
248
 
162
- Note: it will *not* resolve e.g. `/../` in the middle of a rule that doesn't begin with any of `~`,`../`,`./`,`/`.
249
+ **Note: All paths checked must not be excluded by any ignore files AND each included by [include file](#include_files) separately AND the [`include_rules:`](#include_rules) AND the `argv_rules:`. see [Combinations](#combinations) for solutions to using OR.**
163
250
 
164
251
  ### shebang rules
165
252
 
166
- Sometimes you need to match files by their shebang rather than their path or filename
253
+ Sometimes you need to match files by their shebang/hashbang/etc rather than their path or filename
167
254
 
168
- To match extensionless files by shebang/hashbang/etc:
169
-
170
- Lines beginning with `#!:` will match whole words in the shebang line of extensionless files.
255
+ Rules beginning with `#!:` will match whole words in the shebang line of extensionless files.
171
256
  e.g.
172
257
  ```gitignore
173
258
  #!:ruby
174
259
  ```
175
260
  will match shebang lines: `#!/usr/bin/env ruby` or `#!/usr/bin/ruby` or `#!/usr/bin/ruby -w`
261
+
176
262
  e.g.
177
263
  ```gitignore
178
264
  #!:bin/ruby
179
265
  ```
180
266
  will match `#!/bin/ruby` or `#!/usr/bin/ruby` or `#!/usr/bin/ruby -w`
181
- Currently only exact substring matches are available, There's no special handling of * or / or etc.
267
+ Only exact substring matches are available, There's no special handling of * or / or etc.
182
268
 
269
+ These rules can be supplied any way regular rules are, whether in a .gitignore file or files mentioned in [`include_files:`](#include_files) or [`ignore_files:`](#ignore_files) or [`include_rules:`](#include_rules) or [`ignore_rules:`](#ignore_rules) or [`argv_rules:`](#argv_rules)
183
270
  ```ruby
184
271
  FastIgnore.new(include_rules: ['*.rb', '#!:ruby']).to_a
185
272
  FastIgnore.new(ignore_rules: ['*.sh', '#!:sh', '#!:bash', '#!:zsh']).to_a
186
273
  ```
187
274
 
275
+ **Note: git considers rules like this as a comment and will ignore them.**
276
+
188
277
  ## Combinations
189
278
 
190
- 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.
279
+ 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`.
191
280
 
192
281
  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:
193
- provide the files as strings to `include_rules:` or `ignore_rules:`
282
+ provide the files as strings to [`include_rules:`](#include_rules) or [`ignore_rules:`](#ignore_rules)
194
283
  ```ruby
195
284
  FastIgnore.new(include_rules: [File.read('/my/path'), File.read('/another/path')])).to_a
196
285
  ```
197
- This does unfortunately lose the file path as the root for `/` and `/**` rules.
198
- If that's important, combine the files in the file system and use `include_files:` or `ignore_files:` as normal.
286
+ This does unfortunately lose the file path as the root for rules containing `/`.
287
+ If that's important, combine the files in the file system and use [`include_files:`](#include_files) or [`ignore_files:`](#ignore_files) as normal.
199
288
 
200
- To use the additional ARGV handling rules mentioned above for files, read the file into the array as a string.
289
+ To use the additional `ARGV` handling of [`argv_rules:`](#argv_rules) on a file, read the file into the array.
201
290
 
202
291
  ```ruby
203
292
  FastIgnore.new(argv_rules: ["my/rule", File.read('/my/path')]).to_a
204
293
  ```
205
294
 
206
- This does unfortunately lose the file path as the root for `/` and `/**` rules.
295
+ This does unfortunately lose the file path as the root `/` and there is no workaround except setting the [`root:`](#root) for the whole FastIgnore instance.
296
+
297
+ ### optimising #allowed?
298
+
299
+ To avoid unnecessary calls to the filesystem, if your code already knows whether or not it's a directory, or if you're checking shebangs and you have already read the content of the file: use
300
+ ```ruby
301
+ FastIgnore.new.allowed?('relative/path', directory: false, content: "#!/usr/bin/ruby\n\nputs 'ok'\n")
302
+ ```
303
+ This is not required, and if FastIgnore does have to go to the filesystem for this information it's well optimised to only read what is necessary.
304
+
207
305
 
208
306
  ## Known issues
209
- - Doesn't take into account project excludes in `.git/info/exclude`
210
- - Doesn't take into account globally ignored files in `git config core.excludesFile`.
211
- - Doesn't know what to do if you change the current working directory inside the `FastIgnore#each` block.
307
+ - Doesn't know what to do if you change the current working directory inside the [`FastIgnore#each`](#each_map_etc) block.
212
308
  So don't do that.
213
309
 
214
- (It does handle changing the current working directory between `FastIgnore#allowed?` calls.)
310
+ (It does handle changing the current working directory between [`FastIgnore#allowed?`](#allowed) calls) (changing directories doesn't affect the [`root:`](#root) directory, that's frozen at FastIgnore.new (this is a design decision, not an issue)).
311
+ - FastIgnore always matches patterns case-insensitively. (git varies by filesystem).
215
312
 
216
313
  ## Development
217
314
 
218
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
315
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests and linters.
316
+
317
+ You can run `bin/console` for an interactive prompt that will allow you to experiment.
318
+ `bin/ls [argv_rules]` will return something equivalent to `git ls-files` and `bin/time [argv_rules]` will give you the average time for 30 runs.
319
+ This repo is too small to stress bin/time more than 0.01s, switch to a large repo and find the average time before and after changes.
320
+
321
+ To install this gem onto your local machine, run `bundle exec rake install`.
322
+
323
+ ### Goals
219
324
 
220
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
325
+ 1. Match `git ls-files` behaviour quirk for quirk.
326
+ 2. Provide a convenient interface for allowlist/denylist files in ruby.
327
+ 3. Be fast.
221
328
 
222
329
  ## Contributing
223
330
 
@@ -2,10 +2,13 @@
2
2
 
3
3
  require_relative './fast_ignore/backports'
4
4
 
5
+ require 'set'
5
6
  require_relative './fast_ignore/rule_set_builder'
6
7
  require_relative './fast_ignore/rule_builder'
7
8
  require_relative './fast_ignore/rule_set'
8
9
  require_relative './fast_ignore/rule'
10
+ require_relative './fast_ignore/shebang_rule'
11
+ require_relative './fast_ignore/fn_match_to_re'
9
12
 
10
13
  class FastIgnore
11
14
  class Error < StandardError; end
@@ -13,21 +16,17 @@ class FastIgnore
13
16
  include ::Enumerable
14
17
 
15
18
  # :nocov:
16
- if ::FastIgnore::Backports.ruby_version_less_than?(2, 5)
17
- require_relative 'fast_ignore/backports/delete_prefix_suffix'
18
- using ::FastIgnore::Backports::DeletePrefixSuffix
19
-
20
- require_relative 'fast_ignore/backports/dir_each_child'
21
- using ::FastIgnore::Backports::DirEachChild
22
- end
19
+ using ::FastIgnore::Backports::DeletePrefixSuffix if defined?(::FastIgnore::Backports::DeletePrefixSuffix)
20
+ using ::FastIgnore::Backports::DirEachChild if defined?(::FastIgnore::Backports::DirEachChild)
23
21
  # :nocov:
24
22
 
25
- def initialize(relative: false, root: nil, follow_symlinks: false, **rule_set_builder_args)
23
+ def initialize(relative: false, root: nil, gitignore: :auto, follow_symlinks: false, **rule_set_builder_args)
26
24
  @relative = relative
27
- @follow_symlinks = follow_symlinks
28
- dir_pwd = Dir.pwd
29
- @root = "#{::File.expand_path(root.to_s, dir_pwd)}/"
30
- @rule_sets = ::FastIgnore::RuleSetBuilder.build(root: @root, **rule_set_builder_args)
25
+ @follow_symlinks_method = ::File.method(follow_symlinks ? :stat : :lstat)
26
+ @gitignore_enabled = gitignore
27
+ @loaded_gitignore_files = ::Set[''] if gitignore
28
+ @root = "#{::File.expand_path(root.to_s, Dir.pwd)}/"
29
+ @rule_sets = ::FastIgnore::RuleSetBuilder.build(root: @root, gitignore: gitignore, **rule_set_builder_args)
31
30
 
32
31
  freeze
33
32
  end
@@ -41,37 +40,54 @@ class FastIgnore
41
40
  each_recursive(root_from_pwd, '', &block)
42
41
  end
43
42
 
44
- def directory?(path)
45
- if @follow_symlinks
46
- ::File.stat(path).directory?
47
- else
48
- ::File.lstat(path).directory?
49
- end
50
- end
51
-
52
- def allowed?(path)
43
+ def allowed?(path, directory: nil, content: nil)
53
44
  full_path = ::File.expand_path(path, @root)
54
45
  return false unless full_path.start_with?(@root)
55
- return false if directory?(full_path)
46
+ return false if directory.nil? ? @follow_symlinks_method.call(full_path).directory? : directory
56
47
 
57
48
  relative_path = full_path.delete_prefix(@root)
49
+ load_gitignore_recursive(relative_path) if @gitignore_enabled
50
+
58
51
  filename = ::File.basename(relative_path)
59
52
 
60
- @rule_sets.all? { |r| r.allowed_recursive?(relative_path, false, filename) }
53
+ @rule_sets.all? { |r| r.allowed_recursive?(relative_path, false, full_path, filename, content) }
61
54
  rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
62
55
  false
63
56
  end
57
+ alias_method :===, :allowed?
64
58
 
65
59
  private
66
60
 
67
- def each_recursive(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength
68
- ::Dir.each_child(parent_full_path) do |filename|
61
+ def load_gitignore_recursive(path)
62
+ paths = []
63
+ while (path = ::File.dirname(path)) != '.'
64
+ paths << path
65
+ end
66
+
67
+ paths.reverse_each(&method(:load_gitignore))
68
+ end
69
+
70
+ def load_gitignore(parent_path, soft: true)
71
+ return if @loaded_gitignore_files.include?(parent_path)
72
+
73
+ ::FastIgnore::RuleSetBuilder.append_gitignore(
74
+ @rule_sets, project_root: @root, relative_path: parent_path + '.gitignore', soft: soft
75
+ )
76
+ @loaded_gitignore_files << parent_path
77
+ end
78
+
79
+ def each_recursive(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
80
+ children = ::Dir.children(parent_full_path)
81
+
82
+ load_gitignore(parent_relative_path, soft: false) if @gitignore_enabled && children.include?('.gitignore')
83
+
84
+ children.each do |filename|
69
85
  begin
70
86
  full_path = parent_full_path + filename
71
87
  relative_path = parent_relative_path + filename
72
- dir = directory?(full_path)
88
+ dir = @follow_symlinks_method.call(full_path).directory?
73
89
 
74
- next unless @rule_sets.all? { |r| r.allowed_unrecursive?(relative_path, dir, filename) }
90
+ next unless @rule_sets.all? { |r| r.allowed_unrecursive?(relative_path, dir, full_path, filename, nil) }
75
91
 
76
92
  if dir
77
93
  each_recursive(full_path + '/', relative_path + '/', &block)