bibliothecary 8.3.5 → 8.3.8

Sign up to get free protection for your applications and to get access to all the features.
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
 
@@ -167,6 +167,15 @@ module Bibliothecary
167
167
  requirement: dep[-1],
168
168
  type: type
169
169
  }
170
+ elsif dep.count == 5
171
+ # get name from renamed package resolution "org:name -> renamed_org:name:version"
172
+ {
173
+ original_name: dep[0,2].join(":"),
174
+ original_requirement: "*",
175
+ name: dep[-3..-2].join(":"),
176
+ requirement: dep[-1],
177
+ type: type
178
+ }
170
179
  else
171
180
  # get name from version conflict resolution ("org:name:version -> version") and no-resolution ("org:name:version")
172
181
  {
@@ -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.5"
2
+ VERSION = "8.3.8"
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.5
4
+ version: 8.3.8
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 00:00:00.000000000 Z
11
+ date: 2022-08-05 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