bibliothecary 8.3.4 → 8.3.7
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 +4 -4
- data/.rubocop.yml +565 -683
- data/Rakefile +1 -1
- data/bibliothecary.gemspec +2 -0
- data/lib/bibliothecary/multi_parsers/dependencies_csv.rb +4 -4
- data/lib/bibliothecary/parsers/cargo.rb +15 -9
- data/lib/bibliothecary/parsers/carthage.rb +1 -1
- data/lib/bibliothecary/parsers/conda.rb +7 -7
- data/lib/bibliothecary/parsers/go.rb +1 -1
- data/lib/bibliothecary/parsers/maven.rb +22 -16
- data/lib/bibliothecary/parsers/npm.rb +12 -14
- data/lib/bibliothecary/parsers/nuget.rb +15 -9
- data/lib/bibliothecary/parsers/pypi.rb +25 -9
- data/lib/bibliothecary/runner/multi_manifest_filter.rb +30 -7
- data/lib/bibliothecary/version.rb +1 -1
- metadata +30 -2
data/Rakefile
CHANGED
data/bibliothecary.gemspec
CHANGED
|
@@ -34,4 +34,6 @@ Gem::Specification.new do |spec|
|
|
|
34
34
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
35
35
|
spec.add_development_dependency "webmock"
|
|
36
36
|
spec.add_development_dependency "vcr"
|
|
37
|
+
spec.add_development_dependency "rubocop"
|
|
38
|
+
spec.add_development_dependency "rubocop-rails"
|
|
37
39
|
end
|
|
@@ -41,8 +41,8 @@ module Bibliothecary
|
|
|
41
41
|
"lockfile_requirement" => {
|
|
42
42
|
match: [
|
|
43
43
|
/^(lockfile |)requirement$/i,
|
|
44
|
-
/^version$/i
|
|
45
|
-
]
|
|
44
|
+
/^version$/i
|
|
45
|
+
]
|
|
46
46
|
},
|
|
47
47
|
# Manifests have versions that can have operators.
|
|
48
48
|
# However, since Bibliothecary only currently supports analyzing a
|
|
@@ -52,8 +52,8 @@ module Bibliothecary
|
|
|
52
52
|
"requirement" => {
|
|
53
53
|
match: [
|
|
54
54
|
/^(lockfile |)requirement$/i,
|
|
55
|
-
/^version$/i
|
|
56
|
-
]
|
|
55
|
+
/^version$/i
|
|
56
|
+
]
|
|
57
57
|
},
|
|
58
58
|
"type" => {
|
|
59
59
|
default: "runtime",
|
|
@@ -21,17 +21,23 @@ module Bibliothecary
|
|
|
21
21
|
|
|
22
22
|
def self.parse_manifest(file_contents, options: {})
|
|
23
23
|
manifest = Tomlrb.parse(file_contents)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
|
|
25
|
+
parsed_dependencies = []
|
|
26
|
+
|
|
27
|
+
manifest.fetch_values('dependencies', 'dev-dependencies').each_with_index do |deps, index|
|
|
28
|
+
parsed_dependencies << deps.map do |name, requirement|
|
|
29
|
+
if requirement.respond_to?(:fetch)
|
|
30
|
+
requirement = requirement['version'] or next
|
|
31
|
+
end
|
|
32
|
+
{
|
|
33
|
+
name: name,
|
|
34
|
+
requirement: requirement,
|
|
35
|
+
type: index.zero? ? 'runtime' : 'development'
|
|
36
|
+
}
|
|
27
37
|
end
|
|
28
|
-
{
|
|
29
|
-
name: name,
|
|
30
|
-
requirement: requirement,
|
|
31
|
-
type: 'runtime'
|
|
32
|
-
}
|
|
33
38
|
end
|
|
34
|
-
|
|
39
|
+
|
|
40
|
+
parsed_dependencies.flatten.compact
|
|
35
41
|
end
|
|
36
42
|
|
|
37
43
|
def self.parse_lockfile(file_contents, options: {})
|
|
@@ -35,7 +35,7 @@ module Bibliothecary
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def self.map_dependencies(manifest, path)
|
|
38
|
-
response = Typhoeus.post("#{Bibliothecary.configuration.carthage_parser_host}/#{path}", params: {body: manifest})
|
|
38
|
+
response = Typhoeus.post("#{Bibliothecary.configuration.carthage_parser_host}/#{path}", params: { body: manifest })
|
|
39
39
|
raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.carthage_parser_host}/#{path}", response.response_code) unless response.success?
|
|
40
40
|
json = JSON.parse(response.body)
|
|
41
41
|
|
|
@@ -9,20 +9,20 @@ module Bibliothecary
|
|
|
9
9
|
{
|
|
10
10
|
match_filename("environment.yml") => {
|
|
11
11
|
parser: :parse_conda,
|
|
12
|
-
kind: "manifest"
|
|
12
|
+
kind: "manifest"
|
|
13
13
|
},
|
|
14
14
|
match_filename("environment.yaml") => {
|
|
15
15
|
parser: :parse_conda,
|
|
16
|
-
kind: "manifest"
|
|
16
|
+
kind: "manifest"
|
|
17
17
|
},
|
|
18
18
|
match_filename("environment.yml.lock") => {
|
|
19
19
|
parser: :parse_conda_lockfile,
|
|
20
|
-
kind: "lockfile"
|
|
20
|
+
kind: "lockfile"
|
|
21
21
|
},
|
|
22
22
|
match_filename("environment.yaml.lock") => {
|
|
23
23
|
parser: :parse_conda_lockfile,
|
|
24
|
-
kind: "lockfile"
|
|
25
|
-
}
|
|
24
|
+
kind: "lockfile"
|
|
25
|
+
}
|
|
26
26
|
}
|
|
27
27
|
end
|
|
28
28
|
|
|
@@ -47,12 +47,12 @@ module Bibliothecary
|
|
|
47
47
|
response = Typhoeus.post(
|
|
48
48
|
"#{host}/parse",
|
|
49
49
|
headers: {
|
|
50
|
-
ContentType: "multipart/form-data"
|
|
50
|
+
ContentType: "multipart/form-data"
|
|
51
51
|
},
|
|
52
52
|
body: {
|
|
53
53
|
file: file_contents,
|
|
54
54
|
# Unfortunately we do not get the filename in the mapping parsers, so hardcoding the file name depending on the kind
|
|
55
|
-
filename: kind == "manifest" ? "environment.yml" : "environment.yml.lock"
|
|
55
|
+
filename: kind == "manifest" ? "environment.yml" : "environment.yml.lock"
|
|
56
56
|
}
|
|
57
57
|
)
|
|
58
58
|
raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{host}/parse", response.response_code) unless response.success?
|
|
@@ -148,7 +148,6 @@ module Bibliothecary
|
|
|
148
148
|
|
|
149
149
|
split = gradle_dep_match.captures[0]
|
|
150
150
|
|
|
151
|
-
|
|
152
151
|
dep = line
|
|
153
152
|
.split(split)[1].sub(/(\((c|n|\*)\))$/, "") # line ending legend: (c) means a dependency constraint, (n) means not resolved, or (*) means resolved previously, e.g. org.springframework.boot:spring-boot-starter-web:2.1.0.M3 (*)
|
|
154
153
|
.sub(/ FAILED$/, "") # dependency could not be resolved (but still may have a version)
|
|
@@ -159,21 +158,28 @@ module Bibliothecary
|
|
|
159
158
|
# \--- org.springframework.security:spring-security-test (n)
|
|
160
159
|
next unless dep.length >= 3
|
|
161
160
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
name:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
161
|
+
if dep.count == 6
|
|
162
|
+
# get name from renamed package resolution "org:name:version -> renamed_org:name:version"
|
|
163
|
+
{
|
|
164
|
+
original_name: dep[0,2].join(":"),
|
|
165
|
+
original_requirement: dep[2],
|
|
166
|
+
name: dep[-3..-2].join(":"),
|
|
167
|
+
requirement: dep[-1],
|
|
168
|
+
type: type
|
|
169
|
+
}
|
|
170
|
+
else
|
|
171
|
+
# get name from version conflict resolution ("org:name:version -> version") and no-resolution ("org:name:version")
|
|
172
|
+
{
|
|
173
|
+
name: dep[0..1].join(":"),
|
|
174
|
+
requirement: dep[-1],
|
|
175
|
+
type: type
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
.compact
|
|
180
|
+
# Prefer duplicate deps with the aliased ones first, so we don't lose the aliases in the next uniq step.
|
|
181
|
+
.sort_by { |dep| dep.key?(:original_name) || dep.key?(:original_requirement) ? 0 : 1 }
|
|
182
|
+
.uniq { |item| [item[:name], item[:requirement], item[:type]] }
|
|
177
183
|
end
|
|
178
184
|
|
|
179
185
|
def self.parse_maven_resolved(file_contents, options: {})
|
|
@@ -36,22 +36,16 @@ module Bibliothecary
|
|
|
36
36
|
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
37
37
|
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
38
38
|
|
|
39
|
-
def self.parse_shrinkwrap(file_contents, options: {})
|
|
40
|
-
manifest = JSON.parse(file_contents)
|
|
41
|
-
manifest.fetch('dependencies',[]).map do |name, requirement|
|
|
42
|
-
{
|
|
43
|
-
name: name,
|
|
44
|
-
requirement: requirement["version"],
|
|
45
|
-
type: 'runtime'
|
|
46
|
-
}
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
39
|
def self.parse_package_lock(file_contents, options: {})
|
|
51
40
|
manifest = JSON.parse(file_contents)
|
|
52
41
|
parse_package_lock_deps_recursively(manifest.fetch('dependencies', []))
|
|
53
42
|
end
|
|
54
43
|
|
|
44
|
+
class << self
|
|
45
|
+
# "package-lock.json" and "npm-shrinkwrap.json" have same format, so use same parsing logic
|
|
46
|
+
alias_method :parse_shrinkwrap, :parse_package_lock
|
|
47
|
+
end
|
|
48
|
+
|
|
55
49
|
def self.parse_package_lock_deps_recursively(dependencies, depth=1)
|
|
56
50
|
dependencies.flat_map do |name, requirement|
|
|
57
51
|
type = requirement.fetch("dev", false) ? 'development' : 'runtime'
|
|
@@ -61,7 +55,7 @@ module Bibliothecary
|
|
|
61
55
|
[]
|
|
62
56
|
else
|
|
63
57
|
parse_package_lock_deps_recursively(requirement.fetch('dependencies', []), depth + 1)
|
|
64
|
-
|
|
58
|
+
end
|
|
65
59
|
|
|
66
60
|
[{
|
|
67
61
|
name: name,
|
|
@@ -74,8 +68,12 @@ module Bibliothecary
|
|
|
74
68
|
def self.parse_manifest(file_contents, options: {})
|
|
75
69
|
manifest = JSON.parse(file_contents)
|
|
76
70
|
raise "appears to be a lockfile rather than manifest format" if manifest.key?('lockfileVersion')
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
|
|
72
|
+
(
|
|
73
|
+
map_dependencies(manifest, 'dependencies', 'runtime') +
|
|
74
|
+
map_dependencies(manifest, 'devDependencies', 'development')
|
|
75
|
+
)
|
|
76
|
+
.reject { |dep| dep[:name].start_with?("//") } # Omit comment keys. They are valid in package.json: https://groups.google.com/g/nodejs/c/NmL7jdeuw0M/m/yTqI05DRQrIJ
|
|
79
77
|
end
|
|
80
78
|
|
|
81
79
|
def self.parse_yarn_lock(file_contents, options: {})
|
|
@@ -40,7 +40,7 @@ module Bibliothecary
|
|
|
40
40
|
match_filename("project.assets.json") => {
|
|
41
41
|
kind: 'lockfile',
|
|
42
42
|
parser: :parse_project_assets_json
|
|
43
|
-
}
|
|
43
|
+
}
|
|
44
44
|
}
|
|
45
45
|
end
|
|
46
46
|
|
|
@@ -80,7 +80,7 @@ module Bibliothecary
|
|
|
80
80
|
# do that yet so at least pick deterministically.
|
|
81
81
|
|
|
82
82
|
# Note, frameworks can be empty, so remove empty ones and then return the last sorted item if any
|
|
83
|
-
frameworks = frameworks.delete_if { |
|
|
83
|
+
frameworks = frameworks.delete_if { |_k, v| v.empty? }
|
|
84
84
|
return frameworks[frameworks.keys.sort.last] unless frameworks.empty?
|
|
85
85
|
end
|
|
86
86
|
[]
|
|
@@ -92,7 +92,7 @@ module Bibliothecary
|
|
|
92
92
|
{
|
|
93
93
|
name: dependency.id,
|
|
94
94
|
requirement: (dependency.version if dependency.respond_to? "version") || "*",
|
|
95
|
-
type: 'runtime'
|
|
95
|
+
type: dependency.respond_to?("developmentDependency") && dependency.developmentDependency == "true" ? 'development' : 'runtime'
|
|
96
96
|
}
|
|
97
97
|
end
|
|
98
98
|
rescue
|
|
@@ -102,16 +102,22 @@ module Bibliothecary
|
|
|
102
102
|
def self.parse_csproj(file_contents, options: {})
|
|
103
103
|
manifest = Ox.parse file_contents
|
|
104
104
|
|
|
105
|
-
packages = manifest.locate('ItemGroup/PackageReference').map do |dependency|
|
|
105
|
+
packages = manifest.locate('ItemGroup/PackageReference').select{ |dep| dep.respond_to? "Include" }.map do |dependency|
|
|
106
106
|
requirement = (dependency.Version if dependency.respond_to? "Version") || "*"
|
|
107
107
|
if requirement.is_a?(Ox::Element)
|
|
108
108
|
requirement = dependency.nodes.detect{ |n| n.value == "Version" }&.text
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
+
type = if dependency.nodes.first && dependency.nodes.first.nodes.include?("all") && dependency.nodes.first.value.include?("PrivateAssets") || dependency.attributes[:PrivateAssets] == "All"
|
|
112
|
+
"development"
|
|
113
|
+
else
|
|
114
|
+
"runtime"
|
|
115
|
+
end
|
|
116
|
+
|
|
111
117
|
{
|
|
112
118
|
name: dependency.Include,
|
|
113
119
|
requirement: requirement,
|
|
114
|
-
type:
|
|
120
|
+
type: type
|
|
115
121
|
}
|
|
116
122
|
end
|
|
117
123
|
packages.uniq {|package| package[:name] }
|
|
@@ -125,7 +131,7 @@ module Bibliothecary
|
|
|
125
131
|
{
|
|
126
132
|
name: dependency.id,
|
|
127
133
|
requirement: dependency.attributes[:version] || '*',
|
|
128
|
-
type: 'runtime'
|
|
134
|
+
type: dependency.respond_to?("developmentDependency") && dependency.developmentDependency == "true" ? 'development' : 'runtime'
|
|
129
135
|
}
|
|
130
136
|
end
|
|
131
137
|
rescue
|
|
@@ -152,8 +158,8 @@ module Bibliothecary
|
|
|
152
158
|
frameworks = {}
|
|
153
159
|
manifest.fetch("targets",[]).each do |framework, deps|
|
|
154
160
|
frameworks[framework] = deps
|
|
155
|
-
.select { |
|
|
156
|
-
.map do |name,
|
|
161
|
+
.select { |_name, details| details["type"] == "package" }
|
|
162
|
+
.map do |name, _details|
|
|
157
163
|
name_split = name.split("/")
|
|
158
164
|
{
|
|
159
165
|
name: name_split[0],
|
|
@@ -168,7 +174,7 @@ module Bibliothecary
|
|
|
168
174
|
# do that yet so at least pick deterministically.
|
|
169
175
|
|
|
170
176
|
# Note, frameworks can be empty, so remove empty ones and then return the last sorted item if any
|
|
171
|
-
frameworks = frameworks.delete_if { |
|
|
177
|
+
frameworks = frameworks.delete_if { |_k, v| v.empty? }
|
|
172
178
|
return frameworks[frameworks.keys.sort.last] unless frameworks.empty?
|
|
173
179
|
end
|
|
174
180
|
[]
|
|
@@ -16,6 +16,13 @@ module Bibliothecary
|
|
|
16
16
|
|
|
17
17
|
def self.mapping
|
|
18
18
|
{
|
|
19
|
+
match_filenames('requirements-dev.txt', 'requirements/dev.txt',
|
|
20
|
+
'requirements-docs.txt', 'requirements/docs.txt',
|
|
21
|
+
'requirements-test.txt', 'requirements/test.txt',
|
|
22
|
+
'requirements-tools.txt', 'requirements/tools.txt') => {
|
|
23
|
+
kind: 'manifest',
|
|
24
|
+
parser: :parse_requirements_txt
|
|
25
|
+
},
|
|
19
26
|
lambda { |p| PIP_COMPILE_REGEXP.match(p) } => {
|
|
20
27
|
content_matcher: :pip_compile?,
|
|
21
28
|
kind: 'lockfile',
|
|
@@ -28,11 +35,11 @@ module Bibliothecary
|
|
|
28
35
|
},
|
|
29
36
|
match_filename('requirements.frozen') => { # pattern exists to store frozen deps in requirements.frozen
|
|
30
37
|
parser: :parse_requirements_txt,
|
|
31
|
-
kind: 'lockfile'
|
|
38
|
+
kind: 'lockfile'
|
|
32
39
|
},
|
|
33
40
|
match_filename('pip-resolved-dependencies.txt') => { # Inferred from pip
|
|
34
41
|
kind: 'lockfile',
|
|
35
|
-
parser: :parse_requirements_txt
|
|
42
|
+
parser: :parse_requirements_txt
|
|
36
43
|
},
|
|
37
44
|
match_filename("setup.py") => {
|
|
38
45
|
kind: 'manifest',
|
|
@@ -58,20 +65,20 @@ module Bibliothecary
|
|
|
58
65
|
# Pip dependencies can be embedded in conda environment files
|
|
59
66
|
match_filename("environment.yml") => {
|
|
60
67
|
parser: :parse_conda,
|
|
61
|
-
kind: "manifest"
|
|
68
|
+
kind: "manifest"
|
|
62
69
|
},
|
|
63
70
|
match_filename("environment.yaml") => {
|
|
64
71
|
parser: :parse_conda,
|
|
65
|
-
kind: "manifest"
|
|
72
|
+
kind: "manifest"
|
|
66
73
|
},
|
|
67
74
|
match_filename("environment.yml.lock") => {
|
|
68
75
|
parser: :parse_conda,
|
|
69
|
-
kind: "lockfile"
|
|
76
|
+
kind: "lockfile"
|
|
70
77
|
},
|
|
71
78
|
match_filename("environment.yaml.lock") => {
|
|
72
79
|
parser: :parse_conda,
|
|
73
|
-
kind: "lockfile"
|
|
74
|
-
}
|
|
80
|
+
kind: "lockfile"
|
|
81
|
+
}
|
|
75
82
|
}
|
|
76
83
|
end
|
|
77
84
|
|
|
@@ -191,6 +198,15 @@ module Bibliothecary
|
|
|
191
198
|
# Invalid lines in requirements.txt are skipped.
|
|
192
199
|
def self.parse_requirements_txt(file_contents, options: {})
|
|
193
200
|
deps = []
|
|
201
|
+
type = case options[:filename]
|
|
202
|
+
when /dev/ || /docs/ || /tools/
|
|
203
|
+
'development'
|
|
204
|
+
when /test/
|
|
205
|
+
'test'
|
|
206
|
+
else
|
|
207
|
+
'runtime'
|
|
208
|
+
end
|
|
209
|
+
|
|
194
210
|
file_contents.split("\n").each do |line|
|
|
195
211
|
if line['://']
|
|
196
212
|
begin
|
|
@@ -200,7 +216,7 @@ module Bibliothecary
|
|
|
200
216
|
end
|
|
201
217
|
|
|
202
218
|
deps << result.merge(
|
|
203
|
-
type:
|
|
219
|
+
type: type
|
|
204
220
|
)
|
|
205
221
|
else
|
|
206
222
|
match = line.delete(' ').match(REQUIREMENTS_REGEXP)
|
|
@@ -209,7 +225,7 @@ module Bibliothecary
|
|
|
209
225
|
deps << {
|
|
210
226
|
name: match[1],
|
|
211
227
|
requirement: match[-1] || '*',
|
|
212
|
-
type:
|
|
228
|
+
type: type
|
|
213
229
|
}
|
|
214
230
|
end
|
|
215
231
|
end
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
module Bibliothecary
|
|
2
2
|
class Runner
|
|
3
3
|
class MultiManifestFilter
|
|
4
|
+
# Wrap up a file analysis for easier validity testing
|
|
5
|
+
class FileAnalysis
|
|
6
|
+
def initialize(file_analysis)
|
|
7
|
+
@file_analysis = file_analysis
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Determine if we should skip this file analysis when processing
|
|
11
|
+
# @return [Boolean] True if we should skip processing
|
|
12
|
+
def skip?
|
|
13
|
+
!@file_analysis ||
|
|
14
|
+
!@file_analysis[:dependencies] ||
|
|
15
|
+
@file_analysis[:dependencies].empty?
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
4
19
|
def initialize(path:, related_files_info_entries:, runner:)
|
|
5
20
|
@path = path
|
|
6
21
|
@related_files_info_entries = related_files_info_entries
|
|
@@ -38,15 +53,14 @@ module Bibliothecary
|
|
|
38
53
|
return @multiple_file_results if @multiple_file_results
|
|
39
54
|
|
|
40
55
|
@multiple_file_results = []
|
|
41
|
-
@multiple_file_entries.each do |file|
|
|
42
|
-
analysis = @runner.analyse_file(file, File.read(File.join(@path, file)))
|
|
43
56
|
|
|
44
|
-
|
|
57
|
+
each_analysis_and_rfis do |analysis, rfis_for_file|
|
|
45
58
|
rfis_for_file.each do |rfi|
|
|
46
|
-
file_analysis =
|
|
59
|
+
file_analysis = FileAnalysis.new(
|
|
60
|
+
analysis.find { |a| a[:platform] == rfi.platform }
|
|
61
|
+
)
|
|
47
62
|
|
|
48
|
-
next
|
|
49
|
-
next if file_analysis[:dependencies].empty?
|
|
63
|
+
next if file_analysis.skip?
|
|
50
64
|
|
|
51
65
|
@multiple_file_results << rfi
|
|
52
66
|
end
|
|
@@ -55,8 +69,17 @@ module Bibliothecary
|
|
|
55
69
|
@multiple_file_results
|
|
56
70
|
end
|
|
57
71
|
|
|
72
|
+
def each_analysis_and_rfis
|
|
73
|
+
@multiple_file_entries.each do |file|
|
|
74
|
+
analysis = @runner.analyse_file(file, File.read(File.join(@path, file)))
|
|
75
|
+
rfis_for_file = @related_files_info_entries.find_all { |rfi| rfi.lockfiles.include?(file) }
|
|
76
|
+
|
|
77
|
+
yield analysis, rfis_for_file
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
58
81
|
def partition_file_entries!
|
|
59
|
-
@single_file_entries, @multiple_file_entries = files_to_check.partition { |
|
|
82
|
+
@single_file_entries, @multiple_file_entries = files_to_check.partition { |_file, count| count == 1 }
|
|
60
83
|
|
|
61
84
|
@single_file_entries = @single_file_entries.map(&:first)
|
|
62
85
|
@multiple_file_entries = @multiple_file_entries.map(&:first)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bibliothecary
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.3.
|
|
4
|
+
version: 8.3.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Nesbitt
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-07-
|
|
11
|
+
date: 2022-07-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: tomlrb
|
|
@@ -220,6 +220,34 @@ dependencies:
|
|
|
220
220
|
- - ">="
|
|
221
221
|
- !ruby/object:Gem::Version
|
|
222
222
|
version: '0'
|
|
223
|
+
- !ruby/object:Gem::Dependency
|
|
224
|
+
name: rubocop
|
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
|
226
|
+
requirements:
|
|
227
|
+
- - ">="
|
|
228
|
+
- !ruby/object:Gem::Version
|
|
229
|
+
version: '0'
|
|
230
|
+
type: :development
|
|
231
|
+
prerelease: false
|
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
233
|
+
requirements:
|
|
234
|
+
- - ">="
|
|
235
|
+
- !ruby/object:Gem::Version
|
|
236
|
+
version: '0'
|
|
237
|
+
- !ruby/object:Gem::Dependency
|
|
238
|
+
name: rubocop-rails
|
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
|
240
|
+
requirements:
|
|
241
|
+
- - ">="
|
|
242
|
+
- !ruby/object:Gem::Version
|
|
243
|
+
version: '0'
|
|
244
|
+
type: :development
|
|
245
|
+
prerelease: false
|
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
247
|
+
requirements:
|
|
248
|
+
- - ">="
|
|
249
|
+
- !ruby/object:Gem::Version
|
|
250
|
+
version: '0'
|
|
223
251
|
description:
|
|
224
252
|
email:
|
|
225
253
|
- andrewnez@gmail.com
|