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
data/lib/path_list.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './path_list/backports'
|
4
|
+
|
5
|
+
require 'set'
|
6
|
+
require 'strscan'
|
7
|
+
require_relative 'path_list/rule_groups'
|
8
|
+
require_relative 'path_list/global_gitignore'
|
9
|
+
require_relative 'path_list/rule_builder'
|
10
|
+
require_relative 'path_list/gitignore_rule_builder'
|
11
|
+
require_relative 'path_list/gitignore_include_rule_builder'
|
12
|
+
require_relative 'path_list/path_regexp_builder'
|
13
|
+
require_relative 'path_list/gitignore_rule_scanner'
|
14
|
+
require_relative 'path_list/rule_group'
|
15
|
+
require_relative 'path_list/matchers/unmatchable'
|
16
|
+
require_relative 'path_list/matchers/shebang_regexp'
|
17
|
+
require_relative 'path_list/root_candidate'
|
18
|
+
require_relative 'path_list/relative_candidate'
|
19
|
+
require_relative 'path_list/matchers/within_dir'
|
20
|
+
require_relative 'path_list/matchers/allow_any_dir'
|
21
|
+
require_relative 'path_list/matchers/allow_path_regexp'
|
22
|
+
require_relative 'path_list/matchers/ignore_path_regexp'
|
23
|
+
require_relative 'path_list/patterns'
|
24
|
+
require_relative 'path_list/walkers/file_system'
|
25
|
+
require_relative 'path_list/walkers/gitignore_collecting_file_system'
|
26
|
+
require_relative 'path_list/gitignore_rule_group'
|
27
|
+
|
28
|
+
class PathList
|
29
|
+
class Error < StandardError; end
|
30
|
+
|
31
|
+
include ::Enumerable
|
32
|
+
|
33
|
+
# :nocov:
|
34
|
+
using ::PathList::Backports::DeletePrefixSuffix if defined?(::PathList::Backports::DeletePrefixSuffix)
|
35
|
+
using ::PathList::Backports::DirEachChild if defined?(::PathList::Backports::DirEachChild)
|
36
|
+
# :nocov:
|
37
|
+
|
38
|
+
def initialize(root: nil, gitignore: :auto, **rule_group_builder_args)
|
39
|
+
@root = "#{::File.expand_path(root.to_s, Dir.pwd)}/"
|
40
|
+
rule_groups = ::PathList::RuleGroups.new(root: @root, gitignore: gitignore, **rule_group_builder_args)
|
41
|
+
|
42
|
+
walker_class = gitignore ? ::PathList::Walkers::GitignoreCollectingFileSystem : ::PathList::Walkers::FileSystem
|
43
|
+
@walker = walker_class.new(rule_groups)
|
44
|
+
freeze
|
45
|
+
end
|
46
|
+
|
47
|
+
def allowed?(path, directory: nil, content: nil)
|
48
|
+
@walker.allowed?(path, directory: directory, content: content)
|
49
|
+
end
|
50
|
+
alias_method :===, :allowed?
|
51
|
+
|
52
|
+
def to_proc
|
53
|
+
method(:allowed?).to_proc
|
54
|
+
end
|
55
|
+
|
56
|
+
def each(root = ::Dir.pwd, &block)
|
57
|
+
return enum_for(:each, root) unless block_given?
|
58
|
+
|
59
|
+
root = "#{::File.expand_path(root.to_s, Dir.pwd)}/"
|
60
|
+
@walker.each(root, '', &block)
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PathList
|
4
|
+
module Backports
|
5
|
+
ruby_major, ruby_minor = ::RUBY_VERSION.split('.', 2)
|
6
|
+
unless ruby_major.to_i > 2 || ruby_major.to_i == 2 && ruby_minor.to_i > 5
|
7
|
+
module DirEachChild
|
8
|
+
refine ::Dir.singleton_class do
|
9
|
+
def children(path)
|
10
|
+
::Dir.entries(path) - ['.', '..']
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module DeletePrefixSuffix
|
16
|
+
refine ::String do
|
17
|
+
# delete_prefix!(prefix) -> self or nil
|
18
|
+
# Deletes leading prefix from str, returning nil if no change was made.
|
19
|
+
#
|
20
|
+
# "hello".delete_prefix!("hel") #=> "lo"
|
21
|
+
# "hello".delete_prefix!("llo") #=> nil
|
22
|
+
def delete_prefix!(str)
|
23
|
+
return unless start_with?(str)
|
24
|
+
|
25
|
+
slice!(0..(str.length - 1))
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# delete_suffix!(suffix) -> self or nil
|
30
|
+
# Deletes trailing suffix from str, returning nil if no change was made.
|
31
|
+
#
|
32
|
+
# "hello".delete_suffix!("llo") #=> "he"
|
33
|
+
# "hello".delete_suffix!("hel") #=> nil
|
34
|
+
def delete_suffix!(str)
|
35
|
+
return unless end_with?(str)
|
36
|
+
|
37
|
+
slice!(-str.length..-1)
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
# delete_prefix(prefix) -> new_str click to toggle source
|
42
|
+
# Returns a copy of str with leading prefix deleted.
|
43
|
+
#
|
44
|
+
# "hello".delete_prefix("hel") #=> "lo"
|
45
|
+
# "hello".delete_prefix("llo") #=> "hello"
|
46
|
+
def delete_prefix(str)
|
47
|
+
s = dup
|
48
|
+
s.delete_prefix!(str)
|
49
|
+
s
|
50
|
+
end
|
51
|
+
|
52
|
+
# delete_suffix(suffix) -> new_str
|
53
|
+
# Returns a copy of str with trailing suffix deleted.
|
54
|
+
#
|
55
|
+
# "hello".delete_suffix("llo") #=> "he"
|
56
|
+
# "hello".delete_suffix("hel") #=> "hello"
|
57
|
+
def delete_suffix(str) # leftovers:allowed
|
58
|
+
s = dup
|
59
|
+
s.delete_suffix!(str)
|
60
|
+
s
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PathList
|
4
|
+
class GitignoreIncludeRuleBuilder < GitignoreRuleBuilder
|
5
|
+
# :nocov:
|
6
|
+
using ::PathList::Backports::DeletePrefixSuffix if defined?(::PathList::Backports::DeletePrefixSuffix)
|
7
|
+
# :nocov:
|
8
|
+
|
9
|
+
def initialize(rule, expand_path_with: nil)
|
10
|
+
super(rule)
|
11
|
+
|
12
|
+
@negation = true
|
13
|
+
@expand_path_from = expand_path_with
|
14
|
+
end
|
15
|
+
|
16
|
+
def expand_rule_path
|
17
|
+
anchored! unless @s.match?(/\*/)
|
18
|
+
return unless @s.match?(%r{(?:[~/]|\.{1,2}/|.*/\.\./)})
|
19
|
+
|
20
|
+
dir_only! if @s.match?(%r{.*/\s*\z})
|
21
|
+
@s.string.replace(::File.expand_path(@s.rest))
|
22
|
+
@s.string.delete_prefix!(@expand_path_from)
|
23
|
+
@s.pos = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def negated!
|
27
|
+
@negation = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def unmatchable_rule!
|
31
|
+
throw :abort_build, ::PathList::Matchers::Unmatchable
|
32
|
+
end
|
33
|
+
|
34
|
+
def emit_end
|
35
|
+
if @dir_only
|
36
|
+
@child_re = @re.dup
|
37
|
+
@re.append_end_anchor
|
38
|
+
else
|
39
|
+
@re.append_dir_or_end_anchor
|
40
|
+
end
|
41
|
+
|
42
|
+
break!
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_parent_dir_rules
|
46
|
+
return unless @negation
|
47
|
+
|
48
|
+
if @anchored
|
49
|
+
parent_pattern = @s.string.dup
|
50
|
+
if parent_pattern.sub!(%r{/[^/]+/?\s*\z}, '/')
|
51
|
+
::PathList::GitignoreIncludeRuleBuilder.new(parent_pattern).build_as_parent
|
52
|
+
end
|
53
|
+
else
|
54
|
+
[::PathList::Matchers::AllowAnyDir]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def build_child_file_rule # rubocop:disable Metrics/MethodLength
|
59
|
+
if @child_re.end_with?('/')
|
60
|
+
@child_re.append_many_non_dir.append_dir if @dir_only
|
61
|
+
else
|
62
|
+
@child_re.append_dir
|
63
|
+
end
|
64
|
+
|
65
|
+
@child_re.prepend(prefix)
|
66
|
+
|
67
|
+
if @negation
|
68
|
+
::PathList::Matchers::AllowPathRegexp.new(@child_re.to_regexp, @anchored, false)
|
69
|
+
else
|
70
|
+
::PathList::Matchers::IgnorePathRegexp.new(@child_re.to_regexp, @anchored, false)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_as_parent
|
75
|
+
anchored!
|
76
|
+
dir_only!
|
77
|
+
|
78
|
+
catch :abort_build do
|
79
|
+
process_rule
|
80
|
+
build_rule(child_file_rule: false)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def build_rule(child_file_rule: true)
|
85
|
+
@child_re ||= @re.dup # in case emit_end wasn't called
|
86
|
+
|
87
|
+
[super(), *build_parent_dir_rules, (build_child_file_rule if child_file_rule)].compact
|
88
|
+
end
|
89
|
+
|
90
|
+
def process_rule
|
91
|
+
expand_rule_path if @expand_path_from
|
92
|
+
super
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PathList
|
4
|
+
class GitignoreRuleBuilder # rubocop:disable Metrics/ClassLength
|
5
|
+
def initialize(rule)
|
6
|
+
@re = ::PathList::PathRegexpBuilder.new
|
7
|
+
@s = ::PathList::GitignoreRuleScanner.new(rule)
|
8
|
+
|
9
|
+
@negation = false
|
10
|
+
@anchored = false
|
11
|
+
@dir_only = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def break!
|
15
|
+
throw :break
|
16
|
+
end
|
17
|
+
|
18
|
+
def blank!
|
19
|
+
throw :abort_build, []
|
20
|
+
end
|
21
|
+
|
22
|
+
def unmatchable_rule!
|
23
|
+
throw :abort_build, []
|
24
|
+
end
|
25
|
+
|
26
|
+
def negated!
|
27
|
+
@negation = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def anchored!
|
31
|
+
@anchored ||= true
|
32
|
+
end
|
33
|
+
|
34
|
+
def never_anchored!
|
35
|
+
@anchored = :never
|
36
|
+
end
|
37
|
+
|
38
|
+
def dir_only!
|
39
|
+
@dir_only = true
|
40
|
+
end
|
41
|
+
|
42
|
+
def nothing_emitted?
|
43
|
+
@re.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
def emit_dir
|
47
|
+
anchored!
|
48
|
+
@re.append_dir
|
49
|
+
end
|
50
|
+
|
51
|
+
def emit_any_dir
|
52
|
+
anchored!
|
53
|
+
@re.append_any_dir
|
54
|
+
end
|
55
|
+
|
56
|
+
def emit_end
|
57
|
+
@re.append_end_anchor
|
58
|
+
break!
|
59
|
+
end
|
60
|
+
|
61
|
+
def process_backslash
|
62
|
+
return unless @s.backslash?
|
63
|
+
|
64
|
+
@re.append_escaped(@s.next_character) || unmatchable_rule!
|
65
|
+
end
|
66
|
+
|
67
|
+
def process_star_end_after_slash # rubocop:disable Metrics/MethodLength
|
68
|
+
if @s.star_end?
|
69
|
+
@re.append_many_non_dir
|
70
|
+
emit_end
|
71
|
+
elsif @s.two_star_end?
|
72
|
+
break!
|
73
|
+
elsif @s.star_slash_end?
|
74
|
+
@re.append_many_non_dir
|
75
|
+
dir_only!
|
76
|
+
emit_end
|
77
|
+
elsif @s.two_star_slash_end?
|
78
|
+
dir_only!
|
79
|
+
break!
|
80
|
+
else
|
81
|
+
true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def process_slash
|
86
|
+
return unless @s.slash?
|
87
|
+
return dir_only! if @s.end?
|
88
|
+
return unmatchable_rule! if @s.slash?
|
89
|
+
|
90
|
+
emit_dir
|
91
|
+
process_star_end_after_slash
|
92
|
+
end
|
93
|
+
|
94
|
+
def process_two_stars # rubocop:disable Metrics/MethodLength
|
95
|
+
return unless @s.two_stars?
|
96
|
+
return break! if @s.end?
|
97
|
+
|
98
|
+
if @s.slash?
|
99
|
+
if @s.end?
|
100
|
+
@re.append_any_non_dir
|
101
|
+
dir_only!
|
102
|
+
elsif @s.slash?
|
103
|
+
unmatchable_rule!
|
104
|
+
else
|
105
|
+
if nothing_emitted?
|
106
|
+
never_anchored!
|
107
|
+
else
|
108
|
+
emit_any_dir
|
109
|
+
end
|
110
|
+
process_star_end_after_slash
|
111
|
+
end
|
112
|
+
else
|
113
|
+
@re.append_any_non_dir
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def process_character_class # rubocop:disable Metrics/MethodLength
|
118
|
+
return unless @s.character_class_start?
|
119
|
+
|
120
|
+
@re.append_character_class_open
|
121
|
+
@re.append_character_class_negation if @s.character_class_negation?
|
122
|
+
unmatchable_rule! if @s.character_class_end?
|
123
|
+
|
124
|
+
until @s.character_class_end?
|
125
|
+
next if process_backslash
|
126
|
+
next @re.append_character_class_dash if @s.dash?
|
127
|
+
next if @re.append_escaped(@s.character_class_literal)
|
128
|
+
|
129
|
+
unmatchable_rule!
|
130
|
+
end
|
131
|
+
|
132
|
+
@re.append_character_class_close
|
133
|
+
end
|
134
|
+
|
135
|
+
def process_end
|
136
|
+
blank! if nothing_emitted?
|
137
|
+
|
138
|
+
emit_end
|
139
|
+
end
|
140
|
+
|
141
|
+
def process_rule # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
142
|
+
anchored! if @s.slash?
|
143
|
+
|
144
|
+
catch :break do
|
145
|
+
loop do
|
146
|
+
next if process_backslash
|
147
|
+
next if process_slash
|
148
|
+
next if process_two_stars
|
149
|
+
next @re.append_any_non_dir if @s.star?
|
150
|
+
next @re.append_one_non_dir if @s.question_mark?
|
151
|
+
next if process_character_class
|
152
|
+
next if @re.append_escaped(@s.literal)
|
153
|
+
next if @re.append_escaped(@s.significant_whitespace)
|
154
|
+
|
155
|
+
process_end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def prefix
|
161
|
+
out = ::PathList::PathRegexpBuilder.new
|
162
|
+
|
163
|
+
if @anchored
|
164
|
+
out.append_start_anchor
|
165
|
+
else
|
166
|
+
out.append_dir_or_start_anchor
|
167
|
+
end
|
168
|
+
out
|
169
|
+
end
|
170
|
+
|
171
|
+
def build_rule
|
172
|
+
@re.prepend(prefix)
|
173
|
+
if @negation
|
174
|
+
::PathList::Matchers::AllowPathRegexp.new(@re.to_regexp, @anchored, @dir_only)
|
175
|
+
else
|
176
|
+
::PathList::Matchers::IgnorePathRegexp.new(@re.to_regexp, @anchored, @dir_only)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def build
|
181
|
+
catch :abort_build do
|
182
|
+
blank! if @s.hash?
|
183
|
+
negated! if @s.exclamation_mark?
|
184
|
+
process_rule
|
185
|
+
|
186
|
+
@anchored = false if @anchored == :never
|
187
|
+
|
188
|
+
build_rule
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
class PathList
|
6
|
+
class GitignoreRuleGroup < ::PathList::RuleGroup
|
7
|
+
def initialize(root)
|
8
|
+
@root = root
|
9
|
+
@loaded_paths = Set[root]
|
10
|
+
|
11
|
+
super([
|
12
|
+
::PathList::Patterns.new('.git', root: '/'),
|
13
|
+
::PathList::Patterns.new(from_file: ::PathList::GlobalGitignore.path(root: root), root: root),
|
14
|
+
::PathList::Patterns.new(from_file: "#{root}.git/info/exclude", root: root),
|
15
|
+
::PathList::Patterns.new(from_file: "#{root}.gitignore", root: root)
|
16
|
+
], false)
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_gitignore(dir)
|
20
|
+
return if @loaded_paths.include?(dir)
|
21
|
+
|
22
|
+
@loaded_paths << dir
|
23
|
+
matcher = ::PathList::Patterns.new(from_file: "#{dir}.gitignore").build_matchers(include: false)
|
24
|
+
@matchers += matcher unless !matcher || matcher.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_gitignore_to_root(path)
|
28
|
+
add_gitignore(path) until @loaded_paths.include?(path = "#{::File.dirname(path)}/")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|