path_list 0.16
Sign up to get free protection for your applications and to get access to all the features.
- 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
|