path_list 0.16
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 +7 -0
- data/CHANGELOG.md +125 -0
- data/LICENSE.txt +21 -0
- data/README.md +317 -0
- data/lib/path_list.rb +62 -0
- data/lib/path_list/backports.rb +66 -0
- data/lib/path_list/gitignore_include_rule_builder.rb +95 -0
- data/lib/path_list/gitignore_rule_builder.rb +192 -0
- data/lib/path_list/gitignore_rule_group.rb +31 -0
- data/lib/path_list/gitignore_rule_scanner.rb +85 -0
- data/lib/path_list/global_gitignore.rb +50 -0
- data/lib/path_list/matchers/allow_any_dir.rb +35 -0
- data/lib/path_list/matchers/allow_path_regexp.rb +45 -0
- data/lib/path_list/matchers/ignore_path_regexp.rb +45 -0
- data/lib/path_list/matchers/shebang_regexp.rb +46 -0
- data/lib/path_list/matchers/unmatchable.rb +31 -0
- data/lib/path_list/matchers/within_dir.rb +54 -0
- data/lib/path_list/path_regexp_builder.rb +78 -0
- data/lib/path_list/patterns.rb +33 -0
- data/lib/path_list/relative_candidate.rb +20 -0
- data/lib/path_list/root_candidate.rb +56 -0
- data/lib/path_list/rule_builder.rb +42 -0
- data/lib/path_list/rule_group.rb +42 -0
- data/lib/path_list/rule_groups.rb +59 -0
- data/lib/path_list/version.rb +5 -0
- data/lib/path_list/walkers/file_system.rb +45 -0
- data/lib/path_list/walkers/gitignore_collecting_file_system.rb +47 -0
- metadata +198 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PathList
|
4
|
+
class GitignoreRuleScanner < StringScanner
|
5
|
+
def character_class_end?
|
6
|
+
skip(/\]/)
|
7
|
+
end
|
8
|
+
|
9
|
+
def character_class_start?
|
10
|
+
skip(/\[/)
|
11
|
+
end
|
12
|
+
|
13
|
+
def character_class_negation?
|
14
|
+
skip(/\^|!/)
|
15
|
+
end
|
16
|
+
|
17
|
+
def end?
|
18
|
+
skip(/\s*\z/)
|
19
|
+
end
|
20
|
+
|
21
|
+
def slash?
|
22
|
+
skip(%r{/})
|
23
|
+
end
|
24
|
+
|
25
|
+
def backslash?
|
26
|
+
skip(/\\/)
|
27
|
+
end
|
28
|
+
|
29
|
+
def two_stars?
|
30
|
+
skip(/\*{2,}/)
|
31
|
+
end
|
32
|
+
|
33
|
+
def star?
|
34
|
+
skip(/\*/)
|
35
|
+
end
|
36
|
+
|
37
|
+
def next_character
|
38
|
+
matched if scan(/./)
|
39
|
+
end
|
40
|
+
|
41
|
+
def star_end?
|
42
|
+
skip(/\*\s*\z/)
|
43
|
+
end
|
44
|
+
|
45
|
+
def two_star_end?
|
46
|
+
skip(/\*{2,}\s*\z/)
|
47
|
+
end
|
48
|
+
|
49
|
+
def star_slash_end?
|
50
|
+
skip(%r{\*/\s*\z})
|
51
|
+
end
|
52
|
+
|
53
|
+
def two_star_slash_end?
|
54
|
+
skip(%r{\*{2,}/\s*\z})
|
55
|
+
end
|
56
|
+
|
57
|
+
def question_mark?
|
58
|
+
skip(/\?/)
|
59
|
+
end
|
60
|
+
|
61
|
+
def dash?
|
62
|
+
skip(/-/)
|
63
|
+
end
|
64
|
+
|
65
|
+
def character_class_literal
|
66
|
+
matched if scan(/[^\]\-\\]+/)
|
67
|
+
end
|
68
|
+
|
69
|
+
def literal
|
70
|
+
matched if scan(%r{[^*/?\[\\\s]+})
|
71
|
+
end
|
72
|
+
|
73
|
+
def significant_whitespace
|
74
|
+
matched if scan(/\s+(?!\s|\z)/)
|
75
|
+
end
|
76
|
+
|
77
|
+
def exclamation_mark?
|
78
|
+
skip(/!/)
|
79
|
+
end
|
80
|
+
|
81
|
+
def hash?
|
82
|
+
skip(/#/)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PathList
|
4
|
+
module GlobalGitignore
|
5
|
+
class << self
|
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') ||
|
11
|
+
default_global_gitignore_path
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def gitconfig_gitignore_path(config_path)
|
17
|
+
return unless config_path
|
18
|
+
return unless ::File.exist?(config_path)
|
19
|
+
|
20
|
+
ignore_path = ::File.readlines(config_path).find { |l| l.sub!(/\A\s*excludesfile\s*=/, '') }
|
21
|
+
return unless ignore_path
|
22
|
+
|
23
|
+
ignore_path.strip!
|
24
|
+
return ignore_path if ignore_path.empty? # don't expand path in this case
|
25
|
+
|
26
|
+
::File.expand_path(ignore_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def xdg_config_path
|
30
|
+
xdg_config_home? && ::File.expand_path('git/config', xdg_config_home)
|
31
|
+
end
|
32
|
+
|
33
|
+
def default_global_gitignore_path
|
34
|
+
if xdg_config_home?
|
35
|
+
::File.expand_path('git/ignore', xdg_config_home)
|
36
|
+
else
|
37
|
+
::File.expand_path('~/.config/git/ignore')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def xdg_config_home
|
42
|
+
::ENV['XDG_CONFIG_HOME']
|
43
|
+
end
|
44
|
+
|
45
|
+
def xdg_config_home?
|
46
|
+
xdg_config_home && (not xdg_config_home.empty?)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PathList
|
4
|
+
module Matchers
|
5
|
+
module AllowAnyDir
|
6
|
+
class << self
|
7
|
+
def squash_id
|
8
|
+
:allow
|
9
|
+
end
|
10
|
+
|
11
|
+
def dir_only?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def file_only?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def shebang?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
# :nocov:
|
24
|
+
def inspect
|
25
|
+
'#<AllowAnyDir>'
|
26
|
+
end
|
27
|
+
# :nocov:
|
28
|
+
|
29
|
+
def match?(_)
|
30
|
+
:allow
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PathList
|
4
|
+
module Matchers
|
5
|
+
class AllowPathRegexp
|
6
|
+
attr_reader :dir_only
|
7
|
+
alias_method :dir_only?, :dir_only
|
8
|
+
undef :dir_only
|
9
|
+
|
10
|
+
attr_reader :squash_id
|
11
|
+
attr_reader :rule
|
12
|
+
|
13
|
+
def initialize(rule, anchored, dir_only)
|
14
|
+
@rule = rule
|
15
|
+
@dir_only = dir_only
|
16
|
+
@anchored = anchored
|
17
|
+
@squash_id = anchored ? :allow : object_id
|
18
|
+
|
19
|
+
freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
def squash(list)
|
23
|
+
self.class.new(::Regexp.union(list.map(&:rule)), @anchored, @dir_only)
|
24
|
+
end
|
25
|
+
|
26
|
+
def file_only?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def shebang?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
# :nocov:
|
35
|
+
def inspect
|
36
|
+
"#<AllowPathRegexp #{'dir_only ' if @dir_only}#{@rule.inspect}>"
|
37
|
+
end
|
38
|
+
# :nocov:
|
39
|
+
|
40
|
+
def match?(candidate)
|
41
|
+
:allow if @rule.match?(candidate.relative_path)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PathList
|
4
|
+
module Matchers
|
5
|
+
class IgnorePathRegexp
|
6
|
+
attr_reader :dir_only
|
7
|
+
alias_method :dir_only?, :dir_only
|
8
|
+
undef :dir_only
|
9
|
+
|
10
|
+
attr_reader :squash_id
|
11
|
+
attr_reader :rule
|
12
|
+
|
13
|
+
def initialize(rule, anchored, dir_only)
|
14
|
+
@rule = rule
|
15
|
+
@dir_only = dir_only
|
16
|
+
@anchored = anchored
|
17
|
+
@squash_id = anchored ? :ignore : object_id
|
18
|
+
|
19
|
+
freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
def squash(list)
|
23
|
+
self.class.new(::Regexp.union(list.map(&:rule)), @anchored, @dir_only)
|
24
|
+
end
|
25
|
+
|
26
|
+
def file_only?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def shebang?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
# :nocov:
|
35
|
+
def inspect
|
36
|
+
"#<IgnorePathRegexp #{'dir_only ' if @dir_only}#{@rule.inspect}>"
|
37
|
+
end
|
38
|
+
# :nocov:
|
39
|
+
|
40
|
+
def match?(candidate)
|
41
|
+
:ignore if @rule.match?(candidate.relative_path)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PathList
|
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 shebang?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PathList
|
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 shebang?
|
16
|
+
false
|
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,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PathList
|
4
|
+
module Matchers
|
5
|
+
class WithinDir
|
6
|
+
def initialize(matchers, root)
|
7
|
+
@dir_matchers = squash_matchers(matchers.reject(&:file_only?))
|
8
|
+
@file_matchers = squash_matchers(matchers.reject(&:dir_only?))
|
9
|
+
@has_shebang_matchers = matchers.any?(&:shebang?)
|
10
|
+
@root = root
|
11
|
+
|
12
|
+
freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def match?(root_candidate)
|
16
|
+
relative_candidate = root_candidate.relative_to(@root)
|
17
|
+
return false unless relative_candidate
|
18
|
+
|
19
|
+
(root_candidate.directory? ? @dir_matchers : @file_matchers).reverse_each do |rule|
|
20
|
+
val = rule.match?(relative_candidate)
|
21
|
+
return val if val
|
22
|
+
end
|
23
|
+
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def empty?
|
28
|
+
@dir_matchers.empty? && @file_matchers.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def weight
|
32
|
+
@dir_matchers.length + (@has_shebang_matchers ? 10 : 0)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def squash_matchers(matchers)
|
38
|
+
return matchers if matchers.empty?
|
39
|
+
|
40
|
+
matchers -= [::PathList::Matchers::Unmatchable]
|
41
|
+
return [::PathList::Matchers::Unmatchable] if matchers.empty?
|
42
|
+
|
43
|
+
matchers.chunk_while { |a, b| a.squash_id == b.squash_id }.map do |chunk|
|
44
|
+
next ::PathList::Matchers::AllowAnyDir if chunk.include?(::PathList::Matchers::AllowAnyDir)
|
45
|
+
|
46
|
+
chunk.uniq!(&:rule)
|
47
|
+
next chunk.first if chunk.length == 1
|
48
|
+
|
49
|
+
chunk.first.squash(chunk)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PathList
|
4
|
+
class PathRegexpBuilder < String
|
5
|
+
def to_regexp
|
6
|
+
# Regexp::IGNORECASE = 1
|
7
|
+
::Regexp.new(self, 1)
|
8
|
+
end
|
9
|
+
|
10
|
+
def append_escaped(value)
|
11
|
+
return unless value
|
12
|
+
|
13
|
+
append_unescaped(::Regexp.escape(value))
|
14
|
+
end
|
15
|
+
|
16
|
+
def append_dir
|
17
|
+
append_unescaped('/')
|
18
|
+
end
|
19
|
+
|
20
|
+
def append_any_dir
|
21
|
+
append_unescaped('(?:.*/)?')
|
22
|
+
end
|
23
|
+
|
24
|
+
def append_one_non_dir
|
25
|
+
append_unescaped('[^/]')
|
26
|
+
end
|
27
|
+
|
28
|
+
def append_any_non_dir
|
29
|
+
append_one_non_dir
|
30
|
+
append_unescaped('*')
|
31
|
+
end
|
32
|
+
|
33
|
+
def append_many_non_dir
|
34
|
+
append_one_non_dir
|
35
|
+
append_unescaped('+')
|
36
|
+
end
|
37
|
+
|
38
|
+
def append_end_anchor
|
39
|
+
append_unescaped('\\z')
|
40
|
+
end
|
41
|
+
|
42
|
+
def append_start_anchor
|
43
|
+
append_unescaped('\\A')
|
44
|
+
end
|
45
|
+
|
46
|
+
def append_dir_or_start_anchor
|
47
|
+
append_unescaped('(?:\\A|/)')
|
48
|
+
end
|
49
|
+
|
50
|
+
def append_dir_or_end_anchor
|
51
|
+
append_unescaped('(?:/|\\z)')
|
52
|
+
end
|
53
|
+
|
54
|
+
def append_character_class_open
|
55
|
+
append_unescaped('(?!/)[')
|
56
|
+
end
|
57
|
+
|
58
|
+
def append_character_class_negation
|
59
|
+
append_unescaped('^')
|
60
|
+
end
|
61
|
+
|
62
|
+
def append_character_class_dash
|
63
|
+
append_unescaped('-')
|
64
|
+
end
|
65
|
+
|
66
|
+
def append_character_class_close
|
67
|
+
append_unescaped(']')
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def append_unescaped(value)
|
73
|
+
self.<<(value)
|
74
|
+
|
75
|
+
self
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|