fast_ignore 0.17.2 → 0.17.4

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +0 -13
  4. data/lib/fast_ignore/builders/gitignore.rb +15 -0
  5. data/lib/fast_ignore/builders/shebang.rb +17 -0
  6. data/lib/fast_ignore/builders/shebang_or_gitignore.rb +15 -0
  7. data/lib/fast_ignore/candidate.rb +85 -0
  8. data/lib/fast_ignore/gitconfig_parser.rb +1 -1
  9. data/lib/fast_ignore/gitignore_include_rule_builder.rb +40 -63
  10. data/lib/fast_ignore/gitignore_rule_builder.rb +49 -26
  11. data/lib/fast_ignore/gitignore_rule_group.rb +31 -0
  12. data/lib/fast_ignore/gitignore_rule_scanner.rb +12 -0
  13. data/lib/fast_ignore/matchers/allow_any_dir.rb +39 -0
  14. data/lib/fast_ignore/matchers/allow_path_regexp.rb +45 -0
  15. data/lib/fast_ignore/matchers/ignore_path_regexp.rb +45 -0
  16. data/lib/fast_ignore/matchers/shebang_regexp.rb +46 -0
  17. data/lib/fast_ignore/matchers/unmatchable.rb +31 -0
  18. data/lib/fast_ignore/matchers/within_dir.rb +50 -0
  19. data/lib/fast_ignore/path_expander.rb +11 -0
  20. data/lib/fast_ignore/path_regexp_builder.rb +130 -0
  21. data/lib/fast_ignore/patterns.rb +33 -0
  22. data/lib/fast_ignore/relative_candidate.rb +20 -0
  23. data/lib/fast_ignore/rule_group.rb +42 -0
  24. data/lib/fast_ignore/rule_groups.rb +55 -0
  25. data/lib/fast_ignore/version.rb +1 -1
  26. data/lib/fast_ignore/walkers/base.rb +26 -0
  27. data/lib/fast_ignore/walkers/file_system.rb +46 -0
  28. data/lib/fast_ignore/walkers/gitignore_collecting_file_system.rb +48 -0
  29. data/lib/fast_ignore.rb +54 -84
  30. metadata +23 -11
  31. data/lib/fast_ignore/file_root.rb +0 -35
  32. data/lib/fast_ignore/gitignore_rule_regexp_builder.rb +0 -76
  33. data/lib/fast_ignore/rule.rb +0 -65
  34. data/lib/fast_ignore/rule_builder.rb +0 -37
  35. data/lib/fast_ignore/rule_set.rb +0 -76
  36. data/lib/fast_ignore/rule_sets.rb +0 -109
  37. data/lib/fast_ignore/shebang_rule.rb +0 -80
  38. data/lib/fast_ignore/unmatchable_rule.rb +0 -41
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastIgnore
4
+ module Matchers
5
+ class ShebangRegexp
6
+ attr_reader :squash_id
7
+ attr_reader :rule
8
+
9
+ def initialize(rule, negation)
10
+ @rule = rule
11
+ @return_value = negation ? :allow : :ignore
12
+ @squash_id = negation ? :allow_shebang : :ignore_shebang
13
+
14
+ freeze
15
+ end
16
+
17
+ def squash(list)
18
+ self.class.new(::Regexp.union(list.map(&:rule)), @return_value == :allow)
19
+ end
20
+
21
+ def file_only?
22
+ true
23
+ end
24
+
25
+ def dir_only?
26
+ false
27
+ end
28
+
29
+ # :nocov:
30
+ def inspect
31
+ "#<ShebangRegexp #{@return_value} /#{@rule.to_s[26..-4]}/>"
32
+ end
33
+ # :nocov:
34
+
35
+ def match?(candidate)
36
+ return false if candidate.filename.include?('.')
37
+
38
+ @return_value if candidate.first_line.match?(@rule)
39
+ end
40
+
41
+ def weight
42
+ 2
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastIgnore
4
+ module Matchers
5
+ module Unmatchable
6
+ class << self
7
+ def dir_only?
8
+ false
9
+ end
10
+
11
+ def file_only?
12
+ false
13
+ end
14
+
15
+ def weight
16
+ 0
17
+ end
18
+
19
+ # :nocov:
20
+ def inspect
21
+ '#<Unmatchable>'
22
+ end
23
+ # :nocov:
24
+
25
+ def match?(_)
26
+ false
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastIgnore
4
+ module Matchers
5
+ class WithinDir
6
+ attr_reader :weight
7
+
8
+ def initialize(matchers, root)
9
+ @dir_matchers = squash_matchers(matchers.reject(&:file_only?))
10
+ @file_matchers = squash_matchers(matchers.reject(&:dir_only?))
11
+
12
+ @weight = @dir_matchers.sum(&:weight) + @file_matchers.sum(&:weight)
13
+ @root = root
14
+
15
+ freeze
16
+ end
17
+
18
+ def match?(candidate)
19
+ relative_candidate = candidate.relative_to(@root)
20
+ return false unless relative_candidate
21
+
22
+ (candidate.directory? ? @dir_matchers : @file_matchers).reverse_each do |rule|
23
+ val = rule.match?(relative_candidate)
24
+ return val if val
25
+ end
26
+
27
+ false
28
+ end
29
+
30
+ def empty?
31
+ @dir_matchers.empty? && @file_matchers.empty?
32
+ end
33
+
34
+ private
35
+
36
+ def squash_matchers(matchers)
37
+ return matchers if matchers.empty?
38
+
39
+ matchers -= [::FastIgnore::Matchers::Unmatchable]
40
+ return [::FastIgnore::Matchers::Unmatchable] if matchers.empty?
41
+
42
+ matchers.chunk_while { |a, b| a.squash_id == b.squash_id }.map do |chunk|
43
+ next chunk.first if chunk.length == 1
44
+
45
+ chunk.first.squash(chunk)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastIgnore
4
+ module PathExpander
5
+ def self.expand_path(path, dir)
6
+ ::File.expand_path(path, dir)
7
+ rescue ::ArgumentError
8
+ ::File.expand_path("./#{path}", dir)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastIgnore
4
+ class PathRegexpBuilder
5
+ def initialize
6
+ @string = +''
7
+ end
8
+
9
+ def to_regexp
10
+ # Regexp::IGNORECASE = 1
11
+ ::Regexp.new(@string, 1)
12
+ end
13
+
14
+ # String methods
15
+
16
+ def dup
17
+ out = super
18
+
19
+ @string = @string.dup
20
+
21
+ out
22
+ end
23
+
24
+ def to_str
25
+ @string
26
+ end
27
+ alias_method :to_s, :to_str
28
+
29
+ def empty?
30
+ @string.empty?
31
+ end
32
+
33
+ def end_with?(str)
34
+ @string.end_with?(str)
35
+ end
36
+
37
+ def append(value)
38
+ @string.<<(value)
39
+
40
+ self
41
+ rescue FrozenError
42
+ # :nocov:
43
+ # the string seems to become inadvertently frozen in 2.5 and 2.6 with specific inputs
44
+ # and i don't understand why
45
+ # it seems like it's happening during the Regexp.new
46
+ # for some reason that i don't understand
47
+ @string = @string.dup
48
+ append(value)
49
+ # :nocov:
50
+ end
51
+ alias_method :<<, :append
52
+
53
+ def prepend(value)
54
+ @string.prepend(value)
55
+
56
+ self
57
+ rescue FrozenError
58
+ # :nocov:
59
+ # the string seems to become inadvertently frozen in 2.5 and 2.6 with specific inputs
60
+ # and i don't understand why
61
+ # it seems like it's happening during the Regexp.new
62
+ # for some reason that i don't understand
63
+ @string = @string.dup
64
+ prepend(value)
65
+ # :nocov:
66
+ end
67
+
68
+ # builder methods
69
+
70
+ def append_escaped(value)
71
+ return unless value
72
+
73
+ append(::Regexp.escape(value))
74
+ end
75
+
76
+ def append_dir
77
+ append('/')
78
+ end
79
+
80
+ def append_any_dir
81
+ append('(?:.*/)?')
82
+ end
83
+
84
+ def append_one_non_dir
85
+ append('[^/]')
86
+ end
87
+
88
+ def append_any_non_dir
89
+ append_one_non_dir
90
+ append('*')
91
+ end
92
+
93
+ def append_many_non_dir
94
+ append_one_non_dir
95
+ append('+')
96
+ end
97
+
98
+ def append_end_anchor
99
+ append('\\z')
100
+ end
101
+
102
+ def append_start_anchor
103
+ append('\\A')
104
+ end
105
+
106
+ def append_dir_or_start_anchor
107
+ append('(?:\\A|/)')
108
+ end
109
+
110
+ def append_dir_or_end_anchor
111
+ append('(?:/|\\z)')
112
+ end
113
+
114
+ def append_character_class_open
115
+ append('(?!/)[')
116
+ end
117
+
118
+ def append_character_class_negation
119
+ append('^')
120
+ end
121
+
122
+ def append_character_class_dash
123
+ append('-')
124
+ end
125
+
126
+ def append_character_class_close
127
+ append(']')
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,33 @@
1
+ # frozen-string-literal: true
2
+
3
+ class FastIgnore
4
+ class Patterns
5
+ def initialize(*patterns, from_file: nil, format: :gitignore, root: nil)
6
+ if from_file
7
+ @root = root || ::File.dirname(from_file)
8
+ @patterns = ::File.exist?(from_file) ? ::File.readlines(from_file) : []
9
+ else
10
+ @root = root || ::Dir.pwd
11
+ @patterns = patterns.flatten.flat_map { |string| string.to_s.lines }
12
+ end
13
+ @root += '/' unless @root.end_with?('/')
14
+ @expand_path_with = (@root if format == :expand_path)
15
+ end
16
+
17
+ def build_matchers(allow: false) # rubocop:disable Metrics/MethodLength
18
+ matchers = @patterns.flat_map do |p|
19
+ ::FastIgnore::Builders::ShebangOrGitignore.build(p, allow, expand_path_with: @expand_path_with)
20
+ end
21
+
22
+ return if matchers.empty?
23
+ return [::FastIgnore::Matchers::WithinDir.new(matchers, @root)] unless allow
24
+
25
+ [
26
+ ::FastIgnore::Matchers::WithinDir.new(matchers, @root),
27
+ ::FastIgnore::Matchers::WithinDir.new(
28
+ ::FastIgnore::GitignoreIncludeRuleBuilder.new(@root).build_as_parent, '/'
29
+ )
30
+ ]
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ # frozen-string-literal: true
2
+
3
+ class FastIgnore
4
+ class RelativeCandidate
5
+ attr_reader :relative_path
6
+
7
+ def initialize(relative_path, root_candidate)
8
+ @relative_path = relative_path
9
+ @root_candidate = root_candidate
10
+ end
11
+
12
+ def filename
13
+ @root_candidate.filename
14
+ end
15
+
16
+ def first_line
17
+ @root_candidate.first_line
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastIgnore
4
+ class RuleGroup
5
+ def initialize(patterns, allow)
6
+ @matchers = Array(patterns).flat_map { |x| x.build_matchers(allow: allow) }.compact
7
+ @allow = allow
8
+ @allowed_recursive = { ::FastIgnore::Candidate.root.key => true }.compare_by_identity
9
+ end
10
+
11
+ def empty?
12
+ @matchers.empty? || @matchers.all?(&:empty?)
13
+ end
14
+
15
+ def weight
16
+ @matchers.sum(&:weight)
17
+ end
18
+
19
+ def freeze
20
+ @matchers.freeze
21
+
22
+ super
23
+ end
24
+
25
+ def allowed_recursive?(candidate)
26
+ @allowed_recursive.fetch(candidate.key) do
27
+ @allowed_recursive[candidate.key] =
28
+ allowed_recursive?(candidate.parent) &&
29
+ allowed_unrecursive?(candidate)
30
+ end
31
+ end
32
+
33
+ def allowed_unrecursive?(candidate)
34
+ @matchers.reverse_each do |matcher|
35
+ val = matcher.match?(candidate)
36
+ return val == :allow if val
37
+ end
38
+
39
+ not @allow
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastIgnore
4
+ class RuleGroups
5
+ def initialize( # rubocop:disable Metrics/ParameterLists, Metrics/AbcSize, Metrics/MethodLength
6
+ root:,
7
+ ignore_rules: nil,
8
+ ignore_files: nil,
9
+ gitignore: true,
10
+ include_rules: nil,
11
+ include_files: nil,
12
+ argv_rules: nil
13
+ )
14
+ @array = []
15
+ if gitignore
16
+ @gitignore_rule_group = ::FastIgnore::GitignoreRuleGroup.new(root)
17
+ @array << @gitignore_rule_group
18
+ end
19
+ @array << ::FastIgnore::RuleGroup.new(::FastIgnore::Patterns.new(ignore_rules, root: root), false).freeze
20
+ @array << ::FastIgnore::RuleGroup.new(::FastIgnore::Patterns.new(include_rules, root: root), true).freeze
21
+ @array << ::FastIgnore::RuleGroup.new(
22
+ ::FastIgnore::Patterns.new(argv_rules, root: root, format: :expand_path),
23
+ true
24
+ ).freeze
25
+
26
+ Array(ignore_files).each do |f|
27
+ path = PathExpander.expand_path(f, root)
28
+ @array << ::FastIgnore::RuleGroup.new(::FastIgnore::Patterns.new(from_file: path), false).freeze
29
+ end
30
+ Array(include_files).each do |f|
31
+ path = PathExpander.expand_path(f, root)
32
+ @array << ::FastIgnore::RuleGroup.new(::FastIgnore::Patterns.new(from_file: path), true).freeze
33
+ end
34
+ @array.reject!(&:empty?)
35
+ @array.sort_by!(&:weight)
36
+ @array.freeze
37
+ end
38
+
39
+ def allowed_recursive?(candidate)
40
+ @array.all? { |r| r.allowed_recursive?(candidate) }
41
+ end
42
+
43
+ def allowed_unrecursive?(candidate)
44
+ @array.all? { |r| r.allowed_unrecursive?(candidate) }
45
+ end
46
+
47
+ def add_gitignore(dir)
48
+ @gitignore_rule_group.add_gitignore(dir)
49
+ end
50
+
51
+ def add_gitignore_to_root(path)
52
+ @gitignore_rule_group.add_gitignore_to_root(path)
53
+ end
54
+ end
55
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class FastIgnore
4
- VERSION = '0.17.2'
4
+ VERSION = '0.17.4'
5
5
  end
@@ -0,0 +1,26 @@
1
+ # frozen-string-literal: true
2
+
3
+ class FastIgnore
4
+ module Walkers
5
+ class Base
6
+ def initialize(rule_groups, follow_symlinks:)
7
+ warn 'FastIgnore deprecation: follow_symlinks argument is deprecated' if follow_symlinks
8
+
9
+ @follow_symlinks = follow_symlinks
10
+ @rule_groups = rule_groups
11
+ end
12
+
13
+ private
14
+
15
+ def directory?(full_path, directory)
16
+ if !directory.nil?
17
+ directory
18
+ elsif @follow_symlinks
19
+ ::File.stat(full_path).directory?
20
+ else
21
+ ::File.lstat(full_path).directory?
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ # frozen-string-literal: true
2
+
3
+ class FastIgnore
4
+ module Walkers
5
+ class FileSystem < Base
6
+ def allowed?(path, root: Dir.pwd, directory: nil, content: nil, exists: nil, include_directories: false) # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
7
+ full_path = PathExpander.expand_path(path, root)
8
+ return false unless full_path.start_with?(root)
9
+
10
+ begin
11
+ dir = directory?(full_path, directory)
12
+ rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
13
+ nil
14
+ end
15
+
16
+ return false if !include_directories && dir
17
+
18
+ candidate = ::FastIgnore::Candidate.new(full_path, nil, dir, exists, content)
19
+
20
+ return false unless candidate.exists?
21
+
22
+ @rule_groups.allowed_recursive?(candidate)
23
+ end
24
+
25
+ def each(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength
26
+ ::Dir.children(parent_full_path).each do |filename|
27
+ full_path = parent_full_path + filename
28
+ dir = directory?(full_path, nil)
29
+ candidate = ::FastIgnore::Candidate.new(full_path, filename, dir, true, nil)
30
+
31
+ next unless @rule_groups.allowed_unrecursive?(candidate)
32
+
33
+ relative_path = parent_relative_path + filename
34
+
35
+ if dir
36
+ each(full_path + '/', relative_path + '/', &block)
37
+ else
38
+ yield(relative_path)
39
+ end
40
+ rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
41
+ nil
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,48 @@
1
+ # frozen-string-literal: true
2
+
3
+ class FastIgnore
4
+ module Walkers
5
+ class GitignoreCollectingFileSystem < Base
6
+ def allowed?(path, root: Dir.pwd, directory: nil, content: nil, exists: nil, include_directories: false) # rubocop:disable Metrics/ParameterLists
7
+ full_path = PathExpander.expand_path(path, root)
8
+ return false unless full_path.start_with?(root)
9
+
10
+ candidate = ::FastIgnore::Candidate.new(full_path, nil, directory, exists, content)
11
+
12
+ begin
13
+ return false if !include_directories && directory?(full_path, directory)
14
+ rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
15
+ # nil
16
+ end
17
+
18
+ return false unless candidate.exists?
19
+
20
+ @rule_groups.add_gitignore_to_root(full_path)
21
+ @rule_groups.allowed_recursive?(candidate)
22
+ end
23
+
24
+ def each(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength
25
+ children = ::Dir.children(parent_full_path)
26
+ @rule_groups.add_gitignore(parent_full_path) if children.include?('.gitignore')
27
+
28
+ children.each do |filename|
29
+ full_path = parent_full_path + filename
30
+ dir = directory?(full_path, nil)
31
+ candidate = ::FastIgnore::Candidate.new(full_path, filename, dir, true, nil)
32
+
33
+ next unless @rule_groups.allowed_unrecursive?(candidate)
34
+
35
+ relative_path = parent_relative_path + filename
36
+
37
+ if dir
38
+ each(full_path + '/', relative_path + '/', &block)
39
+ else
40
+ yield(relative_path)
41
+ end
42
+ rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
43
+ nil
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end