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.
@@ -0,0 +1,33 @@
1
+ # frozen-string-literal: true
2
+
3
+ class PathList
4
+ class Patterns
5
+ def initialize(*patterns, from_file: nil, format: :gitignore, root: nil)
6
+ raise ArgumentError, "from_file: can't be used with patterns arguments" unless patterns.empty? || !from_file
7
+
8
+ @format = format
9
+ if from_file
10
+ @root = root || ::File.dirname(from_file)
11
+ @patterns = ::File.exist?(from_file) ? ::File.readlines(from_file) : []
12
+ else
13
+ @root = root || ::Dir.pwd
14
+ @patterns = patterns.flatten.flat_map { |string| string.to_s.lines }
15
+ end
16
+ @root += '/' unless @root.end_with?('/')
17
+ end
18
+
19
+ def build_matchers(include: false)
20
+ matchers = @patterns.flat_map { |p| ::PathList::RuleBuilder.build(p, include, @format, @root) }
21
+
22
+ return if matchers.empty?
23
+ return [::PathList::Matchers::WithinDir.new(matchers, @root)] unless include
24
+
25
+ [
26
+ ::PathList::Matchers::WithinDir.new(matchers, @root),
27
+ ::PathList::Matchers::WithinDir.new(
28
+ ::PathList::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 PathList
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,56 @@
1
+ # frozen-string-literal: true
2
+
3
+ class PathList
4
+ class RootCandidate
5
+ attr_reader :full_path
6
+
7
+ def initialize(full_path, filename, directory, content)
8
+ @full_path = full_path
9
+ @filename = filename
10
+ (@directory = directory) unless directory.nil?
11
+ @first_line = content
12
+ end
13
+
14
+ def parent
15
+ @parent ||= ::PathList::RootCandidate.new(
16
+ ::File.dirname(@full_path),
17
+ nil,
18
+ true,
19
+ nil
20
+ )
21
+ end
22
+
23
+ def relative_to(dir)
24
+ return unless @full_path.start_with?(dir)
25
+
26
+ ::PathList::RelativeCandidate.new(@full_path.delete_prefix(dir), self)
27
+ end
28
+
29
+ def directory?
30
+ return @directory if defined?(@directory)
31
+
32
+ @directory ||= ::File.directory?(@full_path)
33
+ end
34
+
35
+ def filename
36
+ @filename ||= ::File.basename(@full_path)
37
+ end
38
+
39
+ # how long can a shebang be?
40
+ # https://www.in-ulm.de/~mascheck/various/shebang/
41
+ # 512 feels like a reasonable limit
42
+ def first_line # rubocop:disable Metrics/MethodLength
43
+ @first_line ||= begin
44
+ file = ::File.new(@full_path)
45
+ first_line = new_fragment = file.sysread(512)
46
+ file.close
47
+ first_line || ''
48
+ rescue ::EOFError, ::SystemCallError
49
+ # :nocov:
50
+ file&.close
51
+ # :nocov:
52
+ first_line || ''
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PathList
4
+ module RuleBuilder
5
+ class << self
6
+ # :nocov:
7
+ using ::PathList::Backports::DeletePrefixSuffix if defined?(::PathList::Backports::DeletePrefixSuffix)
8
+ # :nocov:
9
+
10
+ def build(rule, allow, format, root)
11
+ if rule.delete_prefix!('#!:')
12
+ shebang_rules(rule, allow)
13
+ else
14
+ gitignore_rules(rule, allow, format, root)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ # how long can a shebang be?
21
+ # https://www.in-ulm.de/~mascheck/various/shebang/
22
+ # Theoretically the limit is 65536, but that feels utterly unreasonable
23
+ def shebang_rules(shebang, allow)
24
+ shebang.strip!
25
+ pattern = /\A#![^\n]{,#{510 - shebang.length}}\b#{::Regexp.escape(shebang)}\b/i
26
+ rule = ::PathList::Matchers::ShebangRegexp.new(pattern, allow)
27
+ return rule unless allow
28
+
29
+ # also allow all directories in case they include a file with the matching shebang file
30
+ [::PathList::Matchers::AllowAnyDir, rule]
31
+ end
32
+
33
+ def gitignore_rules(rule, allow, format, root)
34
+ if allow
35
+ ::PathList::GitignoreIncludeRuleBuilder.new(rule, expand_path_with: (root if format == :expand_path)).build
36
+ else
37
+ ::PathList::GitignoreRuleBuilder.new(rule).build
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PathList
4
+ class RuleGroup
5
+ def initialize(patterns, allow)
6
+ @matchers = Array(patterns).flat_map { |x| x.build_matchers(include: allow) }.compact
7
+ @allow = allow
8
+ @allowed_recursive = { '/' => true }
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?(root_candidate)
26
+ @allowed_recursive.fetch(root_candidate.full_path) do
27
+ @allowed_recursive[root_candidate.full_path] =
28
+ allowed_recursive?(root_candidate.parent) &&
29
+ allowed_unrecursive?(root_candidate)
30
+ end
31
+ end
32
+
33
+ def allowed_unrecursive?(root_candidate)
34
+ @matchers.reverse_each do |matcher|
35
+ val = matcher.match?(root_candidate)
36
+ return val == :allow if val
37
+ end
38
+
39
+ not @allow
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PathList
4
+ class RuleGroups
5
+ # :nocov:
6
+ using ::PathList::Backports::DeletePrefixSuffix if defined?(::PathList::Backports::DeletePrefixSuffix)
7
+ # :nocov:
8
+
9
+ def initialize( # rubocop:disable Metrics/ParameterLists, Metrics/AbcSize, Metrics/MethodLength
10
+ root:,
11
+ ignore_rules: nil,
12
+ ignore_files: nil,
13
+ gitignore: true,
14
+ include_rules: nil,
15
+ include_files: nil,
16
+ argv_rules: nil
17
+ )
18
+ @array = []
19
+ if gitignore
20
+ @gitignore_rule_group = ::PathList::GitignoreRuleGroup.new(root)
21
+ @array << @gitignore_rule_group
22
+ end
23
+ @array << ::PathList::RuleGroup.new(::PathList::Patterns.new(ignore_rules, root: root), false).freeze
24
+ @array << ::PathList::RuleGroup.new(::PathList::Patterns.new(include_rules, root: root), true).freeze
25
+ @array << ::PathList::RuleGroup.new(
26
+ ::PathList::Patterns.new(argv_rules, root: root, format: :expand_path),
27
+ true
28
+ ).freeze
29
+
30
+ Array(ignore_files).each do |f|
31
+ path = ::File.expand_path(f, root)
32
+ @array << ::PathList::RuleGroup.new(::PathList::Patterns.new(from_file: path), false).freeze
33
+ end
34
+ Array(include_files).each do |f|
35
+ path = ::File.expand_path(f, root)
36
+ @array << ::PathList::RuleGroup.new(::PathList::Patterns.new(from_file: path), true).freeze
37
+ end
38
+ @array.reject!(&:empty?)
39
+ @array.sort_by!(&:weight)
40
+ @array.freeze
41
+ end
42
+
43
+ def allowed_recursive?(candidate)
44
+ @array.all? { |r| r.allowed_recursive?(candidate) }
45
+ end
46
+
47
+ def allowed_unrecursive?(candidate)
48
+ @array.all? { |r| r.allowed_unrecursive?(candidate) }
49
+ end
50
+
51
+ def add_gitignore(dir)
52
+ @gitignore_rule_group.add_gitignore(dir)
53
+ end
54
+
55
+ def add_gitignore_to_root(path)
56
+ @gitignore_rule_group.add_gitignore_to_root(path)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PathList
4
+ VERSION = '0.16'
5
+ end
@@ -0,0 +1,45 @@
1
+ # frozen-string-literal: true
2
+
3
+ class PathList
4
+ module Walkers
5
+ class FileSystem
6
+ def initialize(rule_groups)
7
+ @rule_groups = rule_groups
8
+ end
9
+
10
+ def allowed?(path, directory: nil, content: nil)
11
+ full_path = ::File.expand_path(path)
12
+
13
+ return false if directory.nil? ? ::File.lstat(full_path).directory? : directory
14
+
15
+ candidate = ::PathList::RootCandidate.new(full_path, nil, directory, content)
16
+ @rule_groups.allowed_recursive?(candidate)
17
+ rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
18
+ false
19
+ end
20
+
21
+ def each(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength
22
+ children = ::Dir.children(parent_full_path)
23
+
24
+ children.each do |filename|
25
+ begin
26
+ full_path = parent_full_path + filename
27
+ relative_path = parent_relative_path + filename
28
+ dir = ::File.lstat(full_path).directory?
29
+ candidate = ::PathList::RootCandidate.new(full_path, filename, dir, nil)
30
+
31
+ next unless @rule_groups.allowed_unrecursive?(candidate)
32
+
33
+ if dir
34
+ each(full_path + '/', relative_path + '/', &block)
35
+ else
36
+ yield relative_path
37
+ end
38
+ rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
39
+ nil
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,47 @@
1
+ # frozen-string-literal: true
2
+
3
+ class PathList
4
+ module Walkers
5
+ class GitignoreCollectingFileSystem
6
+ def initialize(rule_groups)
7
+ @rule_groups = rule_groups
8
+ end
9
+
10
+ def allowed?(path, directory: nil, content: nil)
11
+ full_path = ::File.expand_path(path)
12
+ return false if directory.nil? ? ::File.lstat(full_path).directory? : directory
13
+
14
+ @rule_groups.add_gitignore_to_root(full_path)
15
+
16
+ candidate = ::PathList::RootCandidate.new(full_path, nil, directory, content)
17
+ @rule_groups.allowed_recursive?(candidate)
18
+ rescue ::Errno::ENOENT, ::Errno::EACCES, ::Errno::ENOTDIR, ::Errno::ELOOP, ::Errno::ENAMETOOLONG
19
+ false
20
+ end
21
+
22
+ def each(parent_full_path, parent_relative_path, &block) # rubocop:disable Metrics/MethodLength
23
+ children = ::Dir.children(parent_full_path)
24
+ @rule_groups.add_gitignore(parent_full_path) if children.include?('.gitignore')
25
+
26
+ children.each do |filename|
27
+ begin
28
+ full_path = parent_full_path + filename
29
+ relative_path = parent_relative_path + filename
30
+ dir = ::File.lstat(full_path).directory?
31
+ candidate = ::PathList::RootCandidate.new(full_path, filename, dir, nil)
32
+
33
+ next unless @rule_groups.allowed_unrecursive?(candidate)
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
47
+ end
metadata ADDED
@@ -0,0 +1,198 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: path_list
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.16'
5
+ platform: ruby
6
+ authors:
7
+ - Dana Sherson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-03-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: leftovers
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.4.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.4.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">"
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">"
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 12.3.3
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 12.3.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 0.93.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 0.93.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 1.44.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 1.44.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.18.5
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.18.5
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov-console
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description:
140
+ email:
141
+ - robot@dana.sh
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - CHANGELOG.md
147
+ - LICENSE.txt
148
+ - README.md
149
+ - lib/path_list.rb
150
+ - lib/path_list/backports.rb
151
+ - lib/path_list/gitignore_include_rule_builder.rb
152
+ - lib/path_list/gitignore_rule_builder.rb
153
+ - lib/path_list/gitignore_rule_group.rb
154
+ - lib/path_list/gitignore_rule_scanner.rb
155
+ - lib/path_list/global_gitignore.rb
156
+ - lib/path_list/matchers/allow_any_dir.rb
157
+ - lib/path_list/matchers/allow_path_regexp.rb
158
+ - lib/path_list/matchers/ignore_path_regexp.rb
159
+ - lib/path_list/matchers/shebang_regexp.rb
160
+ - lib/path_list/matchers/unmatchable.rb
161
+ - lib/path_list/matchers/within_dir.rb
162
+ - lib/path_list/path_regexp_builder.rb
163
+ - lib/path_list/patterns.rb
164
+ - lib/path_list/relative_candidate.rb
165
+ - lib/path_list/root_candidate.rb
166
+ - lib/path_list/rule_builder.rb
167
+ - lib/path_list/rule_group.rb
168
+ - lib/path_list/rule_groups.rb
169
+ - lib/path_list/version.rb
170
+ - lib/path_list/walkers/file_system.rb
171
+ - lib/path_list/walkers/gitignore_collecting_file_system.rb
172
+ homepage: https://github.com/robotdana/path_list
173
+ licenses:
174
+ - MIT
175
+ metadata:
176
+ homepage_uri: https://github.com/robotdana/path_list
177
+ source_code_uri: https://github.com/robotdana/path_list
178
+ changelog_uri: https://github.com/robotdana/path_list/blob/main/CHANGELOG.md
179
+ post_install_message:
180
+ rdoc_options: []
181
+ require_paths:
182
+ - lib
183
+ required_ruby_version: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: 2.4.0
188
+ required_rubygems_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ requirements: []
194
+ rubygems_version: 3.1.2
195
+ signing_key:
196
+ specification_version: 4
197
+ summary: Parse gitignore files, quickly
198
+ test_files: []