path_list 0.16

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []