fast_ignore 0.15.2 → 0.17.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: 9992ae13ee95ee05f55cceda56e5bd1c4210d5817be17d9116193787ac658d38
4
- data.tar.gz: '093e221a737476e92c0c6cf1b6632abb4da103ea01ee3d0f66acf067011f092c'
3
+ metadata.gz: 78cb85cea0888d67c91de09bd54ad644510167c993dc355d187593003d341393
4
+ data.tar.gz: fb1fda9c1675ee28f4a6ff23191f95c2dd6e497ca2a70c4791dc5dfb85baecd1
5
5
  SHA512:
6
- metadata.gz: 78db62b2bfa57b7b509b6f326f3b4f3f7371336bf30caa46c2d1b3ccc4086e2bbdb7abf3cc843700d4a93ff82f368a829e0c05da0ceae52f0e8ab2af4eaa32bd
7
- data.tar.gz: c0df1d280a71bb7140076d39774a96728f2a1c8e29b756216211467a05eb93f7a3001c4472dc47155d0b57ec40d4d65f83c0f4f813014c01f55f7b9e03ffac67
6
+ metadata.gz: 10531a1d819b74fcc442ce7591be124430d3c55c55afbccb79ba295dd89c0e85e4a1a0a75b11996557057fa2f5dbad71e9284789143395d229e3abbbbd42ba3c
7
+ data.tar.gz: d0b07a2757c3b962a59973581234ec9e346717d24cdbd5443073c0fd14f840fb287419d63eeb31d4aa52d3fff2e1c0842cc119ed52e836cd55ed73bcd29a3fc7
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ # v0.17.0
2
+ - allow overriding `exists:` in `allowed?`
3
+ - allow setting `include_directories: true` in `allowed?`
4
+ - subsequent calls to `allowed?` with the same path but different `directory:` or `content:` arguments won't potentially mess up the cache
5
+ - slight performance improvements of the shebang rule matcher loading the first line
6
+ - drop support for ruby 2.4
7
+ - add ruby 3.1 to the test matrix
8
+
9
+ # v0.16.1
10
+ - respect GIT_CONFIG_SYSTEM, GIT_CONFIG_NOSYSTEM and GIT_CONFIG_GLOBAL env vars the same way git does
11
+ - make the tests more resilient to whatever global config is going on.
12
+
13
+ # v0.16.0
14
+ - Entirely rewrite the way that git config files are read. previously it was just a regexp. now we actually parse git config files according to the same rules as git.
15
+ - Add ruby 3 to the test matrix
16
+
1
17
  # v0.15.2
2
18
  - Updated methods with multiple `_` arguments to have different names to make sorbet happy
3
19
 
data/README.md CHANGED
@@ -15,7 +15,7 @@ FastIgnore.new(relative: true).sort == `git ls-files`.split("\n").sort
15
15
  ## Features
16
16
 
17
17
  - Fast (faster than using `` `git ls-files`.split("\n") `` for small repos (because it avoids the overhead of ``` `` ```))
18
- - Supports ruby 2.4-3.0.0.preview1 & jruby
18
+ - Supports ruby 2.5-3.1.x & jruby
19
19
  - supports all [gitignore rule patterns](https://git-scm.com/docs/gitignore#_pattern_format)
20
20
  - doesn't require git to be installed
21
21
  - supports a gitignore-esque "include" patterns. ([`include_rules:`](#include_rules)/[`include_files:`](#include_files))
@@ -81,20 +81,79 @@ Relative paths will be considered relative to the [`root:`](#root) directory, no
81
81
 
82
82
  This is aliased as `===` so you can use a FastIgnore instance in case statements.
83
83
  ```ruby
84
+ @path_matcher ||= FastIgnore.new
85
+
84
86
  case my_path
85
- when FastIgnore.new
87
+ when @path_matcher
86
88
  puts(my_path)
87
89
  end
88
90
  ```
89
91
 
90
92
  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
93
 
92
- See [Optimising allowed](#optimising_allowed) for ways to make this even faster
94
+ #### directory: true/false/nil
95
+
96
+ If your code already knows the path to test is/not a directory or wants to lie about whether it is/is not a directory, you can pass `directory: true` or `directory: false` as an argument to `allowed?` (to have FastIgnore ask the file system, you can pass `directory: nil` or nothing)
97
+
98
+ ```
99
+ FastIgnore.new.allowed?('relative/path', directory: false) # matches `path` as a file
100
+ FastIgnore.new.allowed?('relative/path', directory: true) # matches `path` as a directory
101
+ FastIgnore.new.allowed?('relative/path', directory: nil) # matches path as whatever it is on the filesystem
102
+ FastIgnore.new.allowed?('relative/path) # or as a file if it doesn't exist on the file system
103
+ ```
104
+
105
+ #### content: true/false/nil
106
+
107
+ default: `nil`
108
+
109
+ If your code already knows the path to test is has a particular text content or wants to lie about the content, you can pass `directory: true` or `directory: false` as an argument to `allowed?` (to have FastIgnore ask the file system, you can pass `directory: nil` or nothing)
110
+
111
+ ```
112
+ FastIgnore.new.allowed?('relative/path', content: "#!/usr/bin/env ruby\n\nputs 'hello'") # matches ruby shebang
113
+ FastIgnore.new.allowed?('relative/path', content: "#!/usr/bin/env bash\n\necho 'hello'") # matches bash shebang
114
+ FastIgnore.new.allowed?('relative/path', content: nil) # matches path as whatever content is on the filesystem
115
+ FastIgnore.new.allowed?('relative/path) # or as an empty file if it doesn't actually exist
116
+ ```
117
+
118
+ #### content: true/false/nil
119
+
120
+ default: `nil`
121
+
122
+ If your code already knows the path to test is has a particular text content or wants to lie about the content, you can pass `directory: true` or `directory: false` as an argument to `allowed?` (to have FastIgnore ask the file system, you can pass `directory: nil` or nothing)
123
+
124
+ ```
125
+ FastIgnore.new.allowed?('relative/path', content: "#!/usr/bin/env ruby\n\nputs 'hello'") # matches ruby shebang
126
+ FastIgnore.new.allowed?('relative/path', content: "#!/usr/bin/env bash\n\necho 'hello'") # matches bash shebang
127
+ FastIgnore.new.allowed?('relative/path', content: nil) # matches path as whatever content is on the filesystem
128
+ FastIgnore.new.allowed?('relative/path) # or as an empty file if it doesn't actually exist
129
+ ```
93
130
 
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.
131
+ #### exist: true/false/nil
97
132
 
133
+ default: `nil`
134
+
135
+ If your code already knows the path to test exists or wants to lie about its existence, you can pass `exists: true` or `exists: false` as an argument to `allowed?` (to have FastIgnore ask the file system, you can pass `exists: nil` or nothing)
136
+
137
+ ```
138
+ FastIgnore.new.allowed?('relative/path', exists: true) # will check the path regardless of whether it actually truly exists
139
+ FastIgnore.new.allowed?('relative/path', exists: false) # will always return false
140
+ FastIgnore.new.allowed?('relative/path', exists: nil) # asks the filesystem
141
+ FastIgnore.new.allowed?('relative/path) # asks the filesystem
142
+ ```
143
+
144
+ #### include_directories: true/false
145
+
146
+ default: `false`
147
+
148
+ By default a file must not be a directory for it to be considered allowed. This is intended to match the behaviour of `git ls-files` which only lists files.
149
+
150
+ To match directories you can pass `include_directories: true` to `allowed?`
151
+
152
+ ```
153
+ FastIgnore.new.allowed?('relative/path', include_directories: true) # will test the path even if it's a directory
154
+ FastIgnore.new.allowed?('relative/path', include_directories: false) # will always return false if the path is a directory
155
+ FastIgnore.new.allowed?('relative/path) # will always return false if the path is a directory
156
+ ```
98
157
 
99
158
  ### `relative: true`
100
159
 
@@ -293,15 +352,6 @@ FastIgnore.new(argv_rules: ["my/rule", File.read('/my/path')]).to_a
293
352
  ```
294
353
 
295
354
  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
-
305
355
  ## Limitations
306
356
  - Doesn't know what to do if you change the current working directory inside the [`FastIgnore#each`](#each_map_etc) block.
307
357
  So don't do that.
@@ -309,6 +359,7 @@ This is not required, and if FastIgnore does have to go to the filesystem for th
309
359
  (It does handle changing the current working directory between [`FastIgnore#allowed?`](#allowed) calls)
310
360
  - FastIgnore always matches patterns case-insensitively. (git varies by filesystem).
311
361
  - FastIgnore always outputs paths as literal UTF-8 characters. (git depends on your core.quotepath setting but by default outputs non ascii paths with octal escapes surrounded by quotes).
362
+ - git has a system-wide config file installed at `$(prefix)/etc/gitconfig`, where `prefix` is defined for git at install time. FastIgnore assumes that it will always be `/usr/local/etc/gitconfig`. if it's important your system config file is looked at, as that's where you have the core.excludesfile defined, use git's built-in way to override this by adding `export GIT_CONFIG_SYSTEM='/the/actual/location'` to your shell profile.
312
363
  - Because git looks at its own index objects and FastIgnore looks at the file system there may be some differences between FastIgnore and `git ls-files`. To avoid these differences you may want to use the [`git_ls`](https://github.com/robotdana/git_ls) gem instead
313
364
  - Tracked files that were committed before the matching ignore rule was committed will be returned by `git ls-files`, but not by FastIgnore.
314
365
  - Untracked files will be returned by FastIgnore, but not by `git ls-files`
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'strscan'
4
+ class FastIgnore
5
+ class GitconfigParseError < FastIgnore::Error; end
6
+
7
+ class GitconfigParser # rubocop:disable Metrics/ClassLength
8
+ def self.parse(file, root: Dir.pwd, nesting: 1)
9
+ new(file, root: root, nesting: nesting).parse
10
+ end
11
+
12
+ def initialize(path, root: Dir.pwd, nesting: 1)
13
+ @path = path
14
+ @root = root
15
+ @nesting = nesting
16
+ end
17
+
18
+ def parse
19
+ raise ::FastIgnore::GitconfigParseError if nesting >= 10
20
+
21
+ read_file(path)
22
+ return unless value
23
+
24
+ value
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :nesting
30
+ attr_reader :path
31
+ attr_reader :root
32
+ attr_accessor :value
33
+ attr_accessor :within_quotes
34
+ attr_accessor :section
35
+
36
+ def read_file(path) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
37
+ return unless ::File.readable?(path)
38
+
39
+ file = StringScanner.new(::File.read(path))
40
+
41
+ until file.eos?
42
+ if file.skip(/(\s+|[#;].*\n)/)
43
+ # skip
44
+ elsif file.skip(/\[core\]/i)
45
+ self.section = :core
46
+ elsif file.skip(/\[include\]/i)
47
+ self.section = :include
48
+ elsif file.skip(/\[(?i:includeif) +"/)
49
+ self.section = include_if(file) ? :include : :not_include
50
+ elsif file.skip(/\[[\w.]+( "([^\0\\"]|\\(\\{2})*"|\\{2}*)+")?\]/)
51
+ self.section = :other
52
+ elsif section == :core && file.skip(/excludesfile\s*=(\s|\\\n)*/i)
53
+ self.value = scan_value(file)
54
+ elsif section == :include && file.skip(/path\s*=(\s|\\\n)*/)
55
+ include_path = scan_value(file)
56
+
57
+ value = ::FastIgnore::GitconfigParser.parse(
58
+ ::File.expand_path(include_path, ::File.dirname(path)),
59
+ root: root,
60
+ nesting: nesting + 1
61
+ )
62
+ self.value = value if value
63
+ self.section = :include
64
+ elsif file.skip(/[a-zA-Z0-9]\w*\s*([#;].*)?\n/)
65
+ nil
66
+ elsif file.skip(/[a-zA-Z0-9]\w*\s*=(\s|\\\n)*/)
67
+ skip_value(file)
68
+ else
69
+ raise ::FastIgnore::GitconfigParseError
70
+ end
71
+ end
72
+ end
73
+
74
+ def scan_condition_value(file)
75
+ if file.scan(/([^\0\\\n"]|\\(\\{2})*"|\\{2}*)+(?="\])/)
76
+ value = file.matched
77
+ file.skip(/"\]/)
78
+ value
79
+ else
80
+ raise ::FastIgnore::GitconfigParseError
81
+ end
82
+ end
83
+
84
+ def skip_condition_value(file)
85
+ raise ::FastIgnore::GitconfigParseError unless file.skip(/([^\0\\\n"]|\\(\\{2})*"|\\{2}*)+"\]/)
86
+ end
87
+
88
+ def include_if(file)
89
+ if file.skip(/onbranch:/)
90
+ on_branch?(scan_condition_value(file))
91
+ elsif file.skip(/gitdir:/)
92
+ gitdir?(scan_condition_value(file), path: path)
93
+ elsif file.skip(%r{gitdir/i:})
94
+ gitdir?(scan_condition_value(file), case_insensitive: true, path: path)
95
+ else
96
+ skip_condition_value(file)
97
+ false
98
+ end
99
+ end
100
+
101
+ def on_branch?(branch_pattern)
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
+ )
106
+ return unless current_branch
107
+
108
+ # goddamit git what does 'a pattern with standard globbing wildcards' mean
109
+ ::File.fnmatch(branch_pattern, current_branch, ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH)
110
+ end
111
+
112
+ def gitdir?(gitdir, path:, case_insensitive: false)
113
+ gitdir += '**' if gitdir.end_with?('/')
114
+ gitdir.sub!(%r{\A~/}, ENV['HOME'] + '/')
115
+ gitdir.sub!(/\A\./, path + '/')
116
+ gitdir = "**/#{gitdir}" unless gitdir.start_with?('/')
117
+ options = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH
118
+ options |= ::File::FNM_CASEFOLD if case_insensitive
119
+ ::File.fnmatch(gitdir, ::File.join(root, '.git'), options)
120
+ end
121
+
122
+ def scan_value(file) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
123
+ value = +''
124
+ until file.eos?
125
+ if file.skip(/\\\n/)
126
+ # continue
127
+ elsif file.skip(/\\\\/)
128
+ value << '\\'
129
+ elsif file.skip(/\\n/)
130
+ value << "\n"
131
+ elsif file.skip(/\\t/)
132
+ value << "\t"
133
+ elsif file.skip(/\\b/)
134
+ value.chop!
135
+ elsif file.skip(/\\"/)
136
+ value << '"'
137
+ elsif file.skip(/\\/)
138
+ raise ::FastIgnore::GitconfigParseError
139
+ elsif within_quotes
140
+ if file.skip(/"/)
141
+ self.within_quotes = false
142
+ elsif file.scan(/[^"\\\n]+/)
143
+ value << file.matched
144
+ elsif file.skip(/\n/)
145
+ raise ::FastIgnore::GitconfigParseError
146
+ # :nocov:
147
+ else
148
+ raise "Unmatched #{file.rest}"
149
+ # :nocov:
150
+ end
151
+ elsif file.skip(/"/)
152
+ self.within_quotes = true
153
+ elsif file.scan(/[^;#"\s\\]+/)
154
+ value << file.matched
155
+ elsif file.skip(/\s*[;#\n]/)
156
+ break
157
+ elsif file.scan(/\s+/) # rubocop:disable Lint/DuplicateBranch
158
+ value << file.matched
159
+ # :nocov:
160
+ else
161
+ raise "Unmatched #{file.rest}"
162
+ # :nocov:
163
+ end
164
+ end
165
+
166
+ raise ::FastIgnore::GitconfigParseError if within_quotes
167
+
168
+ value
169
+ end
170
+
171
+ def skip_value(file) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
172
+ until file.eos?
173
+ if file.skip(/\\(?:\n|\\|n|t|b|")/)
174
+ nil
175
+ elsif file.skip(/\\/)
176
+ raise ::FastIgnore::GitconfigParseError
177
+ elsif within_quotes
178
+ if file.skip(/"/)
179
+ self.within_quotes = false
180
+ elsif file.skip(/[^"\\\n]+/)
181
+ nil
182
+ elsif file.scan(/\n/)
183
+ raise ::FastIgnore::GitconfigParseError
184
+ # :nocov:
185
+ else
186
+ raise "Unmatched #{file.rest}"
187
+ # :nocov:
188
+ end
189
+ elsif file.skip(/"/)
190
+ self.within_quotes = true
191
+ elsif file.skip(/[^;#"\s\\]+/) # rubocop:disable Lint/DuplicateBranch
192
+ nil
193
+ elsif file.skip(/\s*[;#\n]/)
194
+ break
195
+ elsif file.skip(/\s+/) # rubocop:disable Lint/DuplicateBranch
196
+ nil
197
+ # :nocov:
198
+ else
199
+ raise "Unmatched #{file.rest}"
200
+ # :nocov:
201
+ end
202
+ end
203
+
204
+ raise ::FastIgnore::GitconfigParseError if within_quotes
205
+ end
206
+ end
207
+ end
@@ -4,46 +4,84 @@ class FastIgnore
4
4
  module GlobalGitignore
5
5
  class << self
6
6
  def path(root:)
7
- gitconfig_gitignore_path(::File.expand_path('.git/config', root)) ||
8
- gitconfig_gitignore_path(::File.expand_path('~/.gitconfig')) ||
9
- gitconfig_gitignore_path(xdg_config_path) ||
10
- gitconfig_gitignore_path('/etc/gitconfig') ||
7
+ ignore_path = gitconfigs_gitignore_path(root) ||
11
8
  default_global_gitignore_path
9
+
10
+ ignore_path unless ignore_path.empty?
12
11
  end
13
12
 
14
13
  private
15
14
 
15
+ def gitconfigs_gitignore_path(root)
16
+ gitconfig_gitignore_path(repo_config_path(root)) ||
17
+ gitconfig_gitignore_path(global_config_path) ||
18
+ gitconfig_gitignore_path(default_user_config_path) ||
19
+ gitconfig_gitignore_path(system_config_path)
20
+ rescue ::FastIgnore::GitconfigParseError
21
+ ''
22
+ end
23
+
16
24
  def gitconfig_gitignore_path(config_path)
17
25
  return unless config_path
18
- return unless ::File.exist?(config_path)
26
+ return unless ::File.readable?(config_path)
19
27
 
20
- ignore_path = ::File.readlines(config_path).find { |l| l.sub!(/\A\s*excludesfile\s*=/, '') }
28
+ ignore_path = ::FastIgnore::GitconfigParser.parse(config_path)
21
29
  return unless ignore_path
22
30
 
23
31
  ignore_path.strip!
24
- return ignore_path if ignore_path.empty? # don't expand path in this case
32
+ return '' if ignore_path.empty? # don't expand path in this case
25
33
 
26
34
  ::File.expand_path(ignore_path)
27
35
  end
28
36
 
29
- def xdg_config_path
30
- xdg_config_home? && ::File.expand_path('git/config', xdg_config_home)
37
+ def default_user_config_path
38
+ return if env('GIT_CONFIG_GLOBAL')
39
+
40
+ ::File.expand_path('git/config', default_config_home)
31
41
  end
32
42
 
33
43
  def default_global_gitignore_path
34
- if xdg_config_home?
35
- ::File.expand_path('git/ignore', xdg_config_home)
44
+ ::File.expand_path('git/ignore', default_config_home)
45
+ end
46
+
47
+ def repo_config_path(root)
48
+ ::File.expand_path('.git/config', root)
49
+ end
50
+
51
+ def global_config_path
52
+ ::File.expand_path(env('GIT_CONFIG_GLOBAL', '~/.gitconfig'))
53
+ end
54
+
55
+ def system_config_path
56
+ return if env?('GIT_CONFIG_NOSYSTEM')
57
+
58
+ ::File.expand_path(env('GIT_CONFIG_SYSTEM', '/usr/local/etc/gitconfig'))
59
+ end
60
+
61
+ def default_config_home
62
+ env('XDG_CONFIG_HOME', '~/.config')
63
+ end
64
+
65
+ def env(env_var, default = nil)
66
+ value = ::ENV[env_var]
67
+
68
+ if value && (not value.empty?)
69
+ value
36
70
  else
37
- ::File.expand_path('~/.config/git/ignore')
71
+ default
38
72
  end
39
73
  end
40
74
 
41
- def xdg_config_home
42
- ::ENV['XDG_CONFIG_HOME']
43
- end
75
+ def env?(env_var)
76
+ value = ::ENV[env_var]
44
77
 
45
- def xdg_config_home?
46
- xdg_config_home && (not xdg_config_home.empty?)
78
+ if value&.match?(/\A(yes|on|true|1)\z/i)
79
+ true
80
+ elsif !value || value.match?(/\A(no|off|false|0|)\z/i)
81
+ false
82
+ else
83
+ raise ::FastIgnore::GitconfigParseError
84
+ end
47
85
  end
48
86
  end
49
87
  end
@@ -11,7 +11,7 @@ class FastIgnore
11
11
  @file_rules = (squash ? squash_rules(rules.reject(&:dir_only?)) : rules.reject(&:dir_only?)).freeze
12
12
  @has_shebang_rules = rules.any?(&:shebang?)
13
13
 
14
- @allowed_recursive = { '.' => true }
14
+ @allowed_recursive = { ['.', true, nil] => true }
15
15
  @allow = allow
16
16
  @gitignore = gitignore
17
17
 
@@ -27,8 +27,8 @@ class FastIgnore
27
27
  end
28
28
 
29
29
  def allowed_recursive?(relative_path, dir, full_path, filename, content = nil)
30
- @allowed_recursive.fetch(relative_path) do
31
- @allowed_recursive[relative_path] =
30
+ @allowed_recursive.fetch([relative_path, dir, content]) do |key|
31
+ @allowed_recursive[key] =
32
32
  allowed_recursive?(::File.dirname(relative_path), true, nil, nil, nil) &&
33
33
  allowed_unrecursive?(relative_path, dir, full_path, filename, content)
34
34
  end
@@ -27,8 +27,8 @@ class FastIgnore
27
27
  @array.freeze if @gitignore_rule_set
28
28
  end
29
29
 
30
- def allowed_recursive?(relative_path, full_path, filename, content)
31
- @array.all? { |r| r.allowed_recursive?(relative_path, false, full_path, filename, content) }
30
+ def allowed_recursive?(relative_path, dir, full_path, filename, content)
31
+ @array.all? { |r| r.allowed_recursive?(relative_path, dir, full_path, filename, content) }
32
32
  end
33
33
 
34
34
  def allowed_unrecursive?(relative_path, dir, full_path, filename)
@@ -73,7 +73,7 @@ class FastIgnore
73
73
  end
74
74
 
75
75
  def build_from_root_gitignore_file(path)
76
- return unless ::File.exist?(path)
76
+ return unless path && ::File.exist?(path)
77
77
 
78
78
  build_rule_set(::File.readlines(path), false, gitignore: true)
79
79
  end
@@ -61,12 +61,9 @@ class FastIgnore
61
61
 
62
62
  def first_line(path) # rubocop:disable Metrics/MethodLength
63
63
  file = ::File.new(path)
64
- first_line = new_fragment = file.sysread(64)
64
+ first_line = file.sysread(64)
65
65
  if first_line.start_with?('#!')
66
- until new_fragment.include?("\n")
67
- new_fragment = file.sysread(64)
68
- first_line += new_fragment
69
- end
66
+ first_line += file.readline unless first_line.match?(/\n/)
70
67
  else
71
68
  file.close
72
69
  return
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class FastIgnore
4
- VERSION = '0.15.2'
4
+ VERSION = '0.17.0'
5
5
  end
data/lib/fast_ignore.rb CHANGED
@@ -4,22 +4,24 @@ require_relative './fast_ignore/backports'
4
4
 
5
5
  require 'set'
6
6
  require 'strscan'
7
- require_relative './fast_ignore/rule_sets'
8
- require_relative './fast_ignore/rule_set'
9
- require_relative './fast_ignore/global_gitignore'
10
- require_relative './fast_ignore/rule_builder'
11
- require_relative './fast_ignore/gitignore_rule_builder'
12
- require_relative './fast_ignore/gitignore_include_rule_builder'
13
- require_relative './fast_ignore/gitignore_rule_regexp_builder'
14
- require_relative './fast_ignore/gitignore_rule_scanner'
15
- require_relative './fast_ignore/file_root'
16
- require_relative './fast_ignore/rule'
17
- require_relative './fast_ignore/unmatchable_rule'
18
- require_relative './fast_ignore/shebang_rule'
19
7
 
20
8
  class FastIgnore
21
9
  class Error < StandardError; end
22
10
 
11
+ require_relative './fast_ignore/rule_sets'
12
+ require_relative './fast_ignore/rule_set'
13
+ require_relative './fast_ignore/global_gitignore'
14
+ require_relative './fast_ignore/rule_builder'
15
+ require_relative './fast_ignore/gitignore_rule_builder'
16
+ require_relative './fast_ignore/gitignore_include_rule_builder'
17
+ require_relative './fast_ignore/gitignore_rule_regexp_builder'
18
+ require_relative './fast_ignore/gitignore_rule_scanner'
19
+ require_relative './fast_ignore/file_root'
20
+ require_relative './fast_ignore/rule'
21
+ require_relative './fast_ignore/unmatchable_rule'
22
+ require_relative './fast_ignore/shebang_rule'
23
+ require_relative './fast_ignore/gitconfig_parser'
24
+
23
25
  include ::Enumerable
24
26
 
25
27
  # :nocov:
@@ -47,19 +49,30 @@ class FastIgnore
47
49
  each_recursive(root_from_pwd, '', &block)
48
50
  end
49
51
 
50
- def allowed?(path, directory: nil, content: nil)
52
+ def allowed?(path, directory: nil, content: nil, exists: nil, include_directories: false) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
51
53
  full_path = ::File.expand_path(path, @root)
52
54
  return false unless full_path.start_with?(@root)
53
- return false if directory.nil? ? @follow_symlinks_method.call(full_path).directory? : directory
55
+
56
+ begin
57
+ directory = directory.nil? ? @follow_symlinks_method.call(full_path).directory? : directory
58
+ rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
59
+ exists = false if exists.nil?
60
+ directory = false
61
+ end
62
+
63
+ return false if !include_directories && directory
64
+
65
+ exists = exists.nil? ? ::File.exist?(full_path) : exists
66
+
67
+ return false unless exists
54
68
 
55
69
  relative_path = full_path.delete_prefix(@root)
56
70
  load_gitignore_recursive(relative_path) if @gitignore_enabled
57
71
 
58
72
  filename = ::File.basename(relative_path)
73
+ content = content.slice(/.*/) if content # we only care about the first line
59
74
 
60
- @rule_sets.allowed_recursive?(relative_path, full_path, filename, content)
61
- rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
62
- false
75
+ @rule_sets.allowed_recursive?(relative_path, directory, full_path, filename, content)
63
76
  end
64
77
  alias_method :===, :allowed?
65
78
 
@@ -75,7 +88,7 @@ class FastIgnore
75
88
  paths << path
76
89
  end
77
90
 
78
- paths.reverse_each(&method(:load_gitignore))
91
+ paths.reverse_each { |p| load_gitignore(p) }
79
92
  end
80
93
 
81
94
  def load_gitignore(parent_path, check_exists: true)
@@ -91,21 +104,19 @@ class FastIgnore
91
104
  load_gitignore(parent_relative_path, check_exists: false) if @gitignore_enabled && children.include?('.gitignore')
92
105
 
93
106
  children.each do |filename|
94
- begin
95
- full_path = parent_full_path + filename
96
- relative_path = parent_relative_path + filename
97
- dir = @follow_symlinks_method.call(full_path).directory?
98
-
99
- next unless @rule_sets.allowed_unrecursive?(relative_path, dir, full_path, filename)
100
-
101
- if dir
102
- each_recursive(full_path + '/', relative_path + '/', &block)
103
- else
104
- yield(@relative ? relative_path : @root + relative_path)
105
- end
106
- rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
107
- nil
107
+ full_path = parent_full_path + filename
108
+ relative_path = parent_relative_path + filename
109
+ dir = @follow_symlinks_method.call(full_path).directory?
110
+
111
+ next unless @rule_sets.allowed_unrecursive?(relative_path, dir, full_path, filename)
112
+
113
+ if dir
114
+ each_recursive(full_path + '/', relative_path + '/', &block)
115
+ else
116
+ yield(@relative ? relative_path : @root + relative_path)
108
117
  end
118
+ rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
119
+ nil
109
120
  end
110
121
  end
111
122
  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.15.2
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dana Sherson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-18 00:00:00.000000000 Z
11
+ date: 2022-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -87,6 +87,9 @@ dependencies:
87
87
  - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: 0.93.1
90
+ - - "<"
91
+ - !ruby/object:Gem::Version
92
+ version: '1.12'
90
93
  type: :development
91
94
  prerelease: false
92
95
  version_requirements: !ruby/object:Gem::Requirement
@@ -94,6 +97,23 @@ dependencies:
94
97
  - - ">="
95
98
  - !ruby/object:Gem::Version
96
99
  version: 0.93.1
100
+ - - "<"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.12'
103
+ - !ruby/object:Gem::Dependency
104
+ name: rubocop-rake
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
97
117
  - !ruby/object:Gem::Dependency
98
118
  name: rubocop-rspec
99
119
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +142,20 @@ dependencies:
122
142
  - - "~>"
123
143
  - !ruby/object:Gem::Version
124
144
  version: 0.18.5
145
+ - !ruby/object:Gem::Dependency
146
+ name: simplecov-console
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
125
159
  - !ruby/object:Gem::Dependency
126
160
  name: spellr
127
161
  requirement: !ruby/object:Gem::Requirement
@@ -136,7 +170,7 @@ dependencies:
136
170
  - - ">="
137
171
  - !ruby/object:Gem::Version
138
172
  version: 0.8.3
139
- description:
173
+ description:
140
174
  email:
141
175
  - robot@dana.sh
142
176
  executables: []
@@ -149,6 +183,7 @@ files:
149
183
  - lib/fast_ignore.rb
150
184
  - lib/fast_ignore/backports.rb
151
185
  - lib/fast_ignore/file_root.rb
186
+ - lib/fast_ignore/gitconfig_parser.rb
152
187
  - lib/fast_ignore/gitignore_include_rule_builder.rb
153
188
  - lib/fast_ignore/gitignore_rule_builder.rb
154
189
  - lib/fast_ignore/gitignore_rule_regexp_builder.rb
@@ -168,7 +203,7 @@ metadata:
168
203
  homepage_uri: https://github.com/robotdana/fast_ignore
169
204
  source_code_uri: https://github.com/robotdana/fast_ignore
170
205
  changelog_uri: https://github.com/robotdana/fast_ignore/blob/main/CHANGELOG.md
171
- post_install_message:
206
+ post_install_message:
172
207
  rdoc_options: []
173
208
  require_paths:
174
209
  - lib
@@ -176,15 +211,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
176
211
  requirements:
177
212
  - - ">="
178
213
  - !ruby/object:Gem::Version
179
- version: 2.4.0
214
+ version: 2.5.0
180
215
  required_rubygems_version: !ruby/object:Gem::Requirement
181
216
  requirements:
182
217
  - - ">="
183
218
  - !ruby/object:Gem::Version
184
219
  version: '0'
185
220
  requirements: []
186
- rubygems_version: 3.1.2
187
- signing_key:
221
+ rubygems_version: 3.2.15
222
+ signing_key:
188
223
  specification_version: 4
189
224
  summary: Parse gitignore files, quickly
190
225
  test_files: []