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.
data/Rakefile CHANGED
@@ -3,4 +3,4 @@ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
@@ -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
- manifest.fetch('dependencies', []).map do |name, requirement|
25
- if requirement.respond_to?(:fetch)
26
- requirement = requirement['version'] or next
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
- .compact
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?
@@ -61,7 +61,7 @@ module Bibliothecary
61
61
  match_filename("go-resolved-dependencies.json") => {
62
62
  kind: 'lockfile',
63
63
  parser: :parse_go_resolved
64
- },
64
+ }
65
65
  }
66
66
  end
67
67
 
@@ -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
- dep_name = if dep.count == 6
163
- # get name from renamed package resolution "org:name:version -> renamed_org:name:version"
164
- dep[-3..-2]
165
- else
166
- # get name from version conflict resolution ("org:name:version -> version") and no-resolution ("org:name:version")
167
- dep[0..1]
168
- end
169
-
170
- version = dep[-1]
171
- {
172
- name: dep_name.join(":"),
173
- requirement: version,
174
- type: type
175
- }
176
- end.compact.uniq {|item| [item[:name], item[:requirement], item[:type]]}
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
- end
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
- map_dependencies(manifest, 'dependencies', 'runtime') +
78
- map_dependencies(manifest, 'devDependencies', 'development')
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 { |k, v| v.empty? }
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: 'runtime'
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 { |name, details| details["type"] == "package" }
156
- .map do |name, details|
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 { |k, v| v.empty? }
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: 'runtime'
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: 'runtime'
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
- rfis_for_file = @related_files_info_entries.find_all { |rfi| rfi.lockfiles.include?(file) }
57
+ each_analysis_and_rfis do |analysis, rfis_for_file|
45
58
  rfis_for_file.each do |rfi|
46
- file_analysis = analysis.find { |a| a[:platform] == rfi.platform }
59
+ file_analysis = FileAnalysis.new(
60
+ analysis.find { |a| a[:platform] == rfi.platform }
61
+ )
47
62
 
48
- next unless file_analysis
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 { |file, count| count == 1 }
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)
@@ -1,3 +1,3 @@
1
1
  module Bibliothecary
2
- VERSION = "8.3.4"
2
+ VERSION = "8.3.7"
3
3
  end
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
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-05 00:00:00.000000000 Z
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