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.
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