bibliothecary 9.1.0 → 10.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/README.md +7 -2
  4. data/lib/bibliothecary/analyser.rb +2 -2
  5. data/lib/bibliothecary/dependency.rb +83 -0
  6. data/lib/bibliothecary/multi_parsers/bundler_like_manifest.rb +3 -3
  7. data/lib/bibliothecary/multi_parsers/cyclonedx.rb +2 -2
  8. data/lib/bibliothecary/multi_parsers/dependencies_csv.rb +4 -3
  9. data/lib/bibliothecary/multi_parsers/json_runtime.rb +2 -2
  10. data/lib/bibliothecary/multi_parsers/spdx.rb +2 -2
  11. data/lib/bibliothecary/parsers/cargo.rb +4 -4
  12. data/lib/bibliothecary/parsers/carthage.rb +2 -2
  13. data/lib/bibliothecary/parsers/clojars.rb +2 -2
  14. data/lib/bibliothecary/parsers/cocoapods.rb +4 -4
  15. data/lib/bibliothecary/parsers/conda.rb +1 -1
  16. data/lib/bibliothecary/parsers/cran.rb +2 -2
  17. data/lib/bibliothecary/parsers/elm.rb +2 -2
  18. data/lib/bibliothecary/parsers/go.rb +21 -17
  19. data/lib/bibliothecary/parsers/hackage.rb +5 -3
  20. data/lib/bibliothecary/parsers/hex.rb +4 -4
  21. data/lib/bibliothecary/parsers/julia.rb +2 -2
  22. data/lib/bibliothecary/parsers/maven.rb +25 -25
  23. data/lib/bibliothecary/parsers/npm.rb +42 -22
  24. data/lib/bibliothecary/parsers/nuget.rb +16 -16
  25. data/lib/bibliothecary/parsers/packagist.rb +18 -12
  26. data/lib/bibliothecary/parsers/pub.rb +2 -2
  27. data/lib/bibliothecary/parsers/pypi.rb +16 -18
  28. data/lib/bibliothecary/parsers/rubygems.rb +4 -4
  29. data/lib/bibliothecary/parsers/shard.rb +2 -2
  30. data/lib/bibliothecary/parsers/swift_pm.rb +2 -2
  31. data/lib/bibliothecary/version.rb +1 -1
  32. data/lib/bibliothecary.rb +1 -0
  33. data/lib/sdl_parser.rb +2 -2
  34. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 663746258869345c2ab91235203d083b8c20862ceacc435726e0e903a5924a2c
4
- data.tar.gz: d675d548e6c34bb985895087948753f43a132c09c28608a56d3cfce5cc14b0fa
3
+ metadata.gz: a5819573e0a53cf1f6d12f289f16a89318da9da6124cfbfcf2d5bf43f2a2c60a
4
+ data.tar.gz: 0521f708de33c56c3b9c271c68d61f630f88e4ba73923f781c71f6b117c71401
5
5
  SHA512:
6
- metadata.gz: 65b0c2930fd92537908a604c94e2a12852e5cc5167838f3fb7c86325a16fc930479b746096a063506f01174c9eb40a792e7f88e1aacefce0f657059b630297a6
7
- data.tar.gz: 1b29d13ee41d4a6aabe54854ebe316f1f17cea1be88ba9a73a290c645f3cad4853e2cc6497f419dff474809e1905c1ad851d1d214a58373eee013774830a23e6
6
+ metadata.gz: 9c1c1d84cfdf88a8b8d0b9b4692c4ce453c4c759a067e3ea99f5c0b0a4a3bf9136af9d600e0bb8a81cbb5d0ff2a3a24cdc7b9264b4455482dde0e12ef359e20c
7
+ data.tar.gz: 7628bb022b1daad35a3a826dfda54a46faa5437065ab36abf80de1fc9d64abf7e2b7636157eb00665b127a58792659a4b7dd3e74a35014bc731e973216c3df2d
data/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ ### Changed
13
+
14
+ ### Removed
15
+
16
+ ## [10.1.0] - 2024-07-23
17
+
18
+ ### Changed
19
+
20
+ - Skip self referencing package entries in yarn v4+ lockfiles.
21
+
22
+ ## [10.0.0] - 2024-07-08
23
+
24
+ ### Added
25
+
26
+ - Added `CHANGELOG.md`, based on https://keepachangelog.com/en/1.1.0/.
27
+ - New `Bibliothecary::Dependency` class.
28
+
29
+ ### Changed
30
+
31
+ - **Breaking**: `Bibliothecary::Parsers` classes now return lists of `Bibliothecary::Dependency`
32
+ instances instead of `Hash` instances.
data/README.md CHANGED
@@ -162,9 +162,14 @@ All available config options are in: https://github.com/librariesio/bibliothecar
162
162
 
163
163
  ## Development
164
164
 
165
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
165
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
166
166
 
167
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, bump and commit the version number in `version.rb` in the `main` branch, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
167
+ To install this gem onto your local machine, run `bundle exec rake install`.
168
+
169
+ To release a new version:
170
+ * in `CHANGELOG.md`, move the changes under `"Unreleased"` into a new section with your version number
171
+ * bump and commit the version number in `version.rb` in the `main` branch
172
+ * and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
168
173
 
169
174
  ## Contributing
170
175
 
@@ -58,11 +58,11 @@ module Bibliothecary
58
58
 
59
59
  def map_dependencies(hash, key, type)
60
60
  hash.fetch(key,[]).map do |name, requirement|
61
- {
61
+ Dependency.new(
62
62
  name: name,
63
63
  requirement: requirement,
64
64
  type: type,
65
- }
65
+ )
66
66
  end
67
67
  end
68
68
 
@@ -0,0 +1,83 @@
1
+ module Bibliothecary
2
+ # Dependency represents a single unique dependency that was parsed out of a manifest.
3
+ #
4
+ # @attr_reader [String] name The name of the package, e.g. "ansi-string-colors"
5
+ # @attr_reader [String] requirement The version requirement of the release, e.g. "1.0.0" or "^1.0.0"
6
+ # @attr_reader [String] platform The platform of the package, e.g. "maven". This is optional because
7
+ # it's implicit in most parser results, and the analyzer returns the platform name itself. One
8
+ # exception are multi-parsers like DependenciesCSV, because they may return deps from multiple platforms.
9
+ # Bibliothecary could start returning this field for *all* deps in future, and make it required. (default: nil)
10
+ # @attr_reader [String] type The type of dependency, e.g. "runtime" or "test"
11
+ # @attr_reader [Boolean] direct Is this dependency a direct dependency (vs transitive dependency)? (default: nil)
12
+ # @attr_reader [Boolean] deprecated Is this dependency deprecated? (default: nil)
13
+ # @attr_reader [Boolean] local Is this dependency local? (default: nil)
14
+ # @attr_reader [Boolean] optional Is this dependency optional? (default: nil)
15
+ # @attr_reader [String] original_name The original name used to require the dependency, for cases
16
+ # where it did not match the resolved name. This can be used for features like aliasing.
17
+ # @attr_reader [String] original_requirement The original requirement used to require the dependency,
18
+ # for cases where it did not match the resolved name. This can be used for features like aliasing.
19
+ # @attr_reader [String] lockfile_requirement The requirement found in the lockfile, e.g. "1.0.0" or "^1.0.0". This is
20
+ # only returned from the yarn.lock parser and may not be used by downstream users. TODO: should this be deprecated?
21
+ # @source [String] source An optional string to store the location of the manifest that contained this
22
+ # dependency, e.g. "src/package.json".
23
+ class Dependency
24
+ FIELDS = [
25
+ :name,
26
+ :requirement,
27
+ :original_requirement,
28
+ :lockfile_requirement,
29
+ :platform,
30
+ :type,
31
+ :direct,
32
+ :deprecated,
33
+ :local,
34
+ :optional,
35
+ :original_name,
36
+ :source,
37
+ ]
38
+
39
+ attr_reader *FIELDS
40
+
41
+ def initialize(
42
+ name:,
43
+ requirement:,
44
+ original_requirement: nil,
45
+ lockfile_requirement: nil,
46
+ platform: nil,
47
+ type: nil,
48
+ direct: nil,
49
+ deprecated: nil,
50
+ local: nil,
51
+ optional: nil,
52
+ original_name: nil,
53
+ source: nil
54
+ )
55
+ @name = name
56
+ @platform = platform
57
+ @requirement = requirement
58
+ @original_requirement = original_requirement
59
+ # TODO: maybe deprecate this field? Is it possible to replace it with original_requirement?
60
+ @lockfile_requirement = lockfile_requirement
61
+ @type = type
62
+ @direct = direct
63
+ @deprecated = deprecated
64
+ @local = local
65
+ @optional = optional
66
+ @original_name = original_name
67
+ @source = source
68
+ end
69
+
70
+ def eql?(other)
71
+ FIELDS.all? { |f| public_send(f) == other.public_send(f) }
72
+ end
73
+ alias :== :eql?
74
+
75
+ def to_h
76
+ FIELDS.to_h { |f| [f, public_send(f)] }
77
+ end
78
+
79
+ def hash
80
+ FIELDS.map { |f| public_send(f) }.hash
81
+ end
82
+ end
83
+ end
@@ -5,7 +5,7 @@ module Bibliothecary
5
5
  # manifests and turns them into a list of dependencies.
6
6
  def parse_ruby_manifest(manifest)
7
7
  manifest.dependencies.inject([]) do |deps, dep|
8
- deps.push({
8
+ deps.push(Dependency.new(
9
9
  name: dep.name,
10
10
  requirement: dep
11
11
  .requirement
@@ -13,8 +13,8 @@ module Bibliothecary
13
13
  .sort_by(&:last)
14
14
  .map { |op, version| "#{op} #{version}" }
15
15
  .join(", "),
16
- type: dep.type,
17
- })
16
+ type: dep.type.to_s,
17
+ ))
18
18
  end.uniq
19
19
  end
20
20
  end
@@ -33,11 +33,11 @@ module Bibliothecary
33
33
  return unless mapping
34
34
 
35
35
  @manifests[mapping] ||= Set.new
36
- @manifests[mapping] << {
36
+ @manifests[mapping] << Dependency.new(
37
37
  name: self.class.full_name_for_purl(purl),
38
38
  requirement: purl.version,
39
39
  type: "lockfile",
40
- }
40
+ )
41
41
  end
42
42
 
43
43
  # Iterates over each manifest entry in the parse_queue, and accepts a block which will
@@ -143,9 +143,10 @@ module Bibliothecary
143
143
  raw_csv_file
144
144
  end
145
145
 
146
- csv_file.result.find_all do |dependency|
147
- dependency[:platform] == platform_name.to_s
148
- end
146
+ csv_file
147
+ .result
148
+ .find_all { |dependency| dependency[:platform] == platform_name.to_s }
149
+ .map { |dep_kvs| Dependency.new(**dep_kvs) }
149
150
  end
150
151
  end
151
152
  end
@@ -4,11 +4,11 @@ module Bibliothecary
4
4
  module JSONRuntime
5
5
  def parse_json_runtime_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
6
6
  JSON.parse(file_contents).fetch("dependencies",[]).map do |name, requirement|
7
- {
7
+ Dependency.new(
8
8
  name: name,
9
9
  requirement: requirement,
10
10
  type: "runtime",
11
- }
11
+ )
12
12
  end
13
13
  end
14
14
  end
@@ -76,11 +76,11 @@ module Bibliothecary
76
76
 
77
77
  unless package_name.nil? || package_version.nil? || platform.nil?
78
78
  entries[platform.to_sym] ||= []
79
- entries[platform.to_sym] << {
79
+ entries[platform.to_sym] << Dependency.new(
80
80
  name: package_name,
81
81
  requirement: package_version,
82
82
  type: "lockfile",
83
- }
83
+ )
84
84
 
85
85
  package_name = package_version = platform = nil
86
86
  end
@@ -30,11 +30,11 @@ module Bibliothecary
30
30
  if requirement.respond_to?(:fetch)
31
31
  requirement = requirement["version"] or next
32
32
  end
33
- {
33
+ Dependency.new(
34
34
  name: name,
35
35
  requirement: requirement,
36
36
  type: index.zero? ? "runtime" : "development",
37
- }
37
+ )
38
38
  end
39
39
  end
40
40
 
@@ -45,11 +45,11 @@ module Bibliothecary
45
45
  manifest = Tomlrb.parse(file_contents)
46
46
  manifest.fetch("package",[]).map do |dependency|
47
47
  next if not dependency["source"] or not dependency["source"].start_with?("registry+")
48
- {
48
+ Dependency.new(
49
49
  name: dependency["name"],
50
50
  requirement: dependency["version"],
51
51
  type: "runtime",
52
- }
52
+ )
53
53
  end
54
54
  .compact
55
55
  end
@@ -40,11 +40,11 @@ module Bibliothecary
40
40
  json = JSON.parse(response.body)
41
41
 
42
42
  json.map do |dependency|
43
- {
43
+ Dependency.new(
44
44
  name: dependency["name"],
45
45
  requirement: dependency["version"],
46
46
  type: dependency["type"],
47
- }
47
+ )
48
48
  end
49
49
  end
50
50
  end
@@ -26,11 +26,11 @@ module Bibliothecary
26
26
  return [] unless index;
27
27
  dependencies = json[index + 1]
28
28
  dependencies.map do |dependency|
29
- {
29
+ Dependency.new(
30
30
  name: dependency[0],
31
31
  requirement: dependency[1],
32
32
  type: "runtime",
33
- }
33
+ )
34
34
  end
35
35
  end
36
36
  end
@@ -40,11 +40,11 @@ module Bibliothecary
40
40
  manifest["PODS"].map do |row|
41
41
  pod = row.is_a?(String) ? row : row.keys.first
42
42
  match = pod.match(/(.+?)\s\((.+?)\)/i)
43
- {
43
+ Dependency.new(
44
44
  name: match[1].split("/").first,
45
45
  requirement: match[2],
46
46
  type: "runtime",
47
- }
47
+ )
48
48
  end.compact
49
49
  end
50
50
 
@@ -61,11 +61,11 @@ module Bibliothecary
61
61
  def self.parse_json_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
62
62
  manifest = JSON.parse(file_contents)
63
63
  manifest["dependencies"].inject([]) do |deps, dep|
64
- deps.push({
64
+ deps.push(Dependency.new(
65
65
  name: dep[0],
66
66
  requirement: dep[1],
67
67
  type: "runtime",
68
- })
68
+ ))
69
69
  end.uniq
70
70
  end
71
71
  end
@@ -40,7 +40,7 @@ module Bibliothecary
40
40
 
41
41
  def self.parse_conda_with_kind(info, kind)
42
42
  dependencies = call_conda_parser_web(info, kind)[kind.to_sym]
43
- dependencies.map { |dep| dep.merge(type: "runtime") }
43
+ dependencies.map { |dep_kv| Dependency.new(**dep_kv.merge(type: "runtime")) }
44
44
  end
45
45
 
46
46
  private_class_method def self.call_conda_parser_web(file_contents, kind)
@@ -33,11 +33,11 @@ module Bibliothecary
33
33
  deps = manifest.first[name].delete("\n").split(",").map(&:strip)
34
34
  deps.map do |dependency|
35
35
  dep = dependency.match(REQUIRE_REGEXP)
36
- {
36
+ Dependency.new(
37
37
  name: dep[1],
38
38
  requirement: dep[2] || "*",
39
39
  type: name.downcase,
40
- }
40
+ )
41
41
  end
42
42
  end
43
43
  end
@@ -24,11 +24,11 @@ module Bibliothecary
24
24
  def self.parse_json_lock(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
25
25
  manifest = JSON.parse file_contents
26
26
  manifest.map do |name, requirement|
27
- {
27
+ Dependency.new(
28
28
  name: name,
29
29
  requirement: requirement,
30
30
  type: "runtime",
31
- }
31
+ )
32
32
  end
33
33
  end
34
34
  end
@@ -84,11 +84,11 @@ module Bibliothecary
84
84
  file_contents.split("\n").each do |line|
85
85
  match = line.gsub(/(\#(.*))/, "").match(GPM_REGEXP)
86
86
  next unless match
87
- deps << {
87
+ deps << Dependency.new(
88
88
  name: match[1].strip,
89
89
  requirement: match[2].strip || "*",
90
90
  type: "runtime",
91
- }
91
+ )
92
92
  end
93
93
  deps
94
94
  end
@@ -136,9 +136,9 @@ module Bibliothecary
136
136
  # the *source code*. So by replacing the deps here, we're giving more honest results than you'd get when asking go
137
137
  # about the versions used.
138
138
  replaced_dep = categorized_deps["replace"]
139
- .find do |replacement_dep|
140
- replacement_dep[:original_name] == dep[:name] &&
141
- (replacement_dep[:original_requirement] == "*" || replacement_dep[:original_requirement] == dep[:requirement])
139
+ .find do |replacement_dep|
140
+ replacement_dep.original_name == dep.name &&
141
+ (replacement_dep.original_requirement == "*" || replacement_dep.original_requirement == dep.requirement)
142
142
  end
143
143
 
144
144
  replaced_dep || dep
@@ -178,11 +178,11 @@ module Bibliothecary
178
178
  deps = []
179
179
  file_contents.lines.map(&:strip).each do |line|
180
180
  if (match = line.match(GOSUM_REGEXP))
181
- deps << {
181
+ deps << Dependency.new(
182
182
  name: match[1].strip,
183
183
  requirement: match[2].strip.split("/").first || "*",
184
184
  type: "runtime",
185
- }
185
+ )
186
186
  end
187
187
  end
188
188
  deps.uniq
@@ -198,20 +198,24 @@ module Bibliothecary
198
198
  # about the versions used.
199
199
  name, requirement = dep["Replace"].split(" ", 2)
200
200
  requirement = "*" if requirement.to_s.strip == ""
201
- { name: name, requirement: requirement, original_name: dep["Path"], original_requirement: dep["Version"], type: dep.fetch("Scope") { "runtime" } }
201
+ Dependency.new(
202
+ name: name, requirement: requirement, original_name: dep["Path"], original_requirement: dep["Version"], type: dep.fetch("Scope") { "runtime" }
203
+ )
202
204
  else
203
- { name: dep["Path"], requirement: dep["Version"], type: dep.fetch("Scope") { "runtime" } }
205
+ Dependency.new(
206
+ name: dep["Path"], requirement: dep["Version"], type: dep.fetch("Scope") { "runtime" }
207
+ )
204
208
  end
205
209
  end
206
210
  end
207
211
 
208
212
  def self.map_dependencies(manifest, attr_name, dep_attr_name, version_attr_name, type)
209
213
  manifest.fetch(attr_name,[]).map do |dependency|
210
- {
214
+ Dependency.new(
211
215
  name: dependency[dep_attr_name],
212
216
  requirement: dependency[version_attr_name] || "*",
213
217
  type: type,
214
- }
218
+ )
215
219
  end
216
220
  end
217
221
 
@@ -221,29 +225,29 @@ module Bibliothecary
221
225
  when "replace"
222
226
  replacement_dep = line.split(GOMOD_REPLACEMENT_SEPARATOR_REGEXP, 2).last
223
227
  replacement_match = replacement_dep.match(GOMOD_DEP_REGEXP)
224
- {
228
+ Dependency.new(
225
229
  original_name: match[:name],
226
230
  original_requirement: match[:requirement] || "*",
227
231
  name: replacement_match[:name],
228
232
  requirement: replacement_match[:requirement] || "*",
229
233
  type: "runtime",
230
234
  direct: !match[:indirect],
231
- }
235
+ )
232
236
  when "retract"
233
- {
237
+ Dependency.new(
234
238
  name: match[:name],
235
239
  requirement: match[:requirement] || "*",
236
240
  type: "runtime",
237
241
  deprecated: true,
238
242
  direct: !match[:indirect],
239
- }
243
+ )
240
244
  else
241
- {
245
+ Dependency.new(
242
246
  name: match[:name],
243
247
  requirement: match[:requirement] || "*",
244
248
  type: "runtime",
245
249
  direct: !match[:indirect],
246
- }
250
+ )
247
251
  end
248
252
  end
249
253
  end
@@ -31,7 +31,9 @@ module Bibliothecary
31
31
  response = Typhoeus.post("#{Bibliothecary.configuration.cabal_parser_host}/parse", headers: headers, body: file_contents)
32
32
 
33
33
  raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.cabal_parser_host}/parse", response.response_code) unless response.success?
34
- JSON.parse(response.body, symbolize_names: true)
34
+ JSON
35
+ .parse(response.body, symbolize_names: true)
36
+ .map { |dep_kvs| Dependency.new(**dep_kvs) }
35
37
  end
36
38
 
37
39
  def self.parse_cabal_config(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
@@ -39,11 +41,11 @@ module Bibliothecary
39
41
  deps = manifest.first["constraints"].delete("\n").split(",").map(&:strip)
40
42
  deps.map do |dependency|
41
43
  dep = dependency.delete("==").split(" ")
42
- {
44
+ Dependency.new(
43
45
  name: dep[0],
44
46
  requirement: dep[1] || "*",
45
47
  type: "runtime",
46
- }
48
+ )
47
49
  end
48
50
  end
49
51
  end
@@ -28,11 +28,11 @@ module Bibliothecary
28
28
  json = JSON.parse response.body
29
29
 
30
30
  json.map do |name, version|
31
- {
31
+ Dependency.new(
32
32
  name: name,
33
33
  requirement: version,
34
34
  type: "runtime",
35
- }
35
+ )
36
36
  end
37
37
  end
38
38
 
@@ -42,11 +42,11 @@ module Bibliothecary
42
42
  json = JSON.parse response.body
43
43
 
44
44
  json.map do |name, info|
45
- {
45
+ Dependency.new(
46
46
  name: name,
47
47
  requirement: info["version"],
48
48
  type: "runtime",
49
- }
49
+ )
50
50
  end
51
51
  end
52
52
  end
@@ -29,11 +29,11 @@ module Bibliothecary
29
29
  reqs = "*" if reqs.empty?
30
30
  next if name.empty?
31
31
 
32
- deps << {
32
+ deps << Dependency.new(
33
33
  name: name,
34
34
  requirement: reqs,
35
35
  type: "runtime",
36
- }
36
+ )
37
37
  end
38
38
  deps
39
39
  end
@@ -107,11 +107,11 @@ module Bibliothecary
107
107
  manifest = Ox.parse file_contents
108
108
  manifest.dependencies.locate("dependency").map do |dependency|
109
109
  attrs = dependency.attributes
110
- {
110
+ Dependency.new(
111
111
  name: "#{attrs[:org]}:#{attrs[:name]}",
112
112
  requirement: attrs[:rev],
113
113
  type: "runtime",
114
- }
114
+ )
115
115
  end
116
116
  end
117
117
 
@@ -143,11 +143,11 @@ module Bibliothecary
143
143
 
144
144
  next nil if org.nil? or name.nil? or version.nil?
145
145
 
146
- {
146
+ Dependency.new(
147
147
  name: "#{org}:#{name}",
148
148
  requirement: version,
149
149
  type: type,
150
- }
150
+ )
151
151
  end.compact
152
152
  end
153
153
 
@@ -188,33 +188,33 @@ module Bibliothecary
188
188
 
189
189
  if dep.count == 6
190
190
  # get name from renamed package resolution "org:name:version -> renamed_org:name:version"
191
- {
191
+ Dependency.new(
192
192
  original_name: dep[0,2].join(":"),
193
193
  original_requirement: dep[2],
194
194
  name: dep[-3..-2].join(":"),
195
195
  requirement: dep[-1],
196
196
  type: current_type,
197
- }
197
+ )
198
198
  elsif dep.count == 5
199
199
  # get name from renamed package resolution "org:name -> renamed_org:name:version"
200
- {
200
+ Dependency.new(
201
201
  original_name: dep[0,2].join(":"),
202
202
  original_requirement: "*",
203
203
  name: dep[-3..-2].join(":"),
204
204
  requirement: dep[-1],
205
205
  type: current_type,
206
- }
206
+ )
207
207
  else
208
208
  # get name from version conflict resolution ("org:name:version -> version") and no-resolution ("org:name:version")
209
- {
209
+ Dependency.new(
210
210
  name: dep[0..1].join(":"),
211
211
  requirement: dep[-1],
212
212
  type: current_type,
213
- }
213
+ )
214
214
  end
215
215
  end
216
216
  .compact
217
- .uniq { |item| item.values_at(:name, :requirement, :type, :original_name, :original_requirement) }
217
+ .uniq { |item| [item.name, item.requirement, item.type, item.original_name, item.original_requirement] }
218
218
  end
219
219
 
220
220
  def self.parse_maven_resolved(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
@@ -241,16 +241,16 @@ module Bibliothecary
241
241
  when 5..6
242
242
  version, type = parts[-2..]
243
243
  end
244
- {
244
+ Dependency.new(
245
245
  name: parts[0..1].join(":"),
246
246
  requirement: version,
247
247
  type: type,
248
- }
248
+ )
249
249
  end
250
250
 
251
251
  # First dep line will be the package itself (unless we're only analyzing a single line)
252
252
  package = deps[0]
253
- deps.size < 2 ? deps : deps[1..-1].reject { |d| d[:name] == package[:name] && d[:requirement] == package[:requirement] }
253
+ deps.size < 2 ? deps : deps[1..-1].reject { |d| d.name == package.name && d.requirement == package.requirement }
254
254
  end
255
255
 
256
256
  def self.parse_resolved_dep_line(line)
@@ -261,11 +261,11 @@ module Bibliothecary
261
261
  dep_parts = line.strip.split(":")
262
262
  return unless dep_parts.length == 5
263
263
  # org.springframework.boot:spring-boot-starter-web:jar:2.0.3.RELEASE:compile -- module spring.boot.starter.web [auto]
264
- {
264
+ Dependency.new(
265
265
  name: dep_parts[0, 2].join(":"),
266
266
  requirement: dep_parts[3],
267
267
  type: dep_parts[4].split("--").first.strip,
268
- }
268
+ )
269
269
  end
270
270
 
271
271
  def self.parse_standalone_pom_manifest(file_contents, options: {})
@@ -311,7 +311,7 @@ module Bibliothecary
311
311
  # optional field is, itself, optional, and will be either "true" or "false"
312
312
  optional = extract_pom_dep_info(xml, dep, "optional", parent_properties)
313
313
  dep_hash[:optional] = optional == "true" unless optional.nil?
314
- deps.push(dep_hash)
314
+ deps.push(Dependency.new(**dep_hash))
315
315
  end
316
316
  end
317
317
  end
@@ -321,11 +321,11 @@ module Bibliothecary
321
321
  .scan(GRADLE_GROOVY_SIMPLE_REGEXP) # match 'implementation "group:artifactId:version"'
322
322
  .reject { |(_type, group, artifactId, _version)| group.nil? || artifactId.nil? } # remove any matches with missing group/artifactId
323
323
  .map { |(type, group, artifactId, version)|
324
- {
325
- name: [group, artifactId].join(":"),
326
- requirement: version || "*",
327
- type: type,
328
- }
324
+ Dependency.new(
325
+ name: [group, artifactId].join(":"),
326
+ requirement: version || "*",
327
+ type: type,
328
+ )
329
329
  }
330
330
  end
331
331
 
@@ -334,11 +334,11 @@ module Bibliothecary
334
334
  .scan(GRADLE_KOTLIN_SIMPLE_REGEXP) # match 'implementation("group:artifactId:version")'
335
335
  .reject { |(_type, group, artifactId, _version)| group.nil? || artifactId.nil? } # remove any matches with missing group/artifactId
336
336
  .map { |(type, group, artifactId, version)|
337
- {
337
+ Dependency.new(
338
338
  name: [group, artifactId].join(":"),
339
339
  requirement: version || "*",
340
340
  type: type,
341
- }
341
+ )
342
342
  }
343
343
  end
344
344
 
@@ -458,7 +458,7 @@ module Bibliothecary
458
458
  dep.delete(:fields)
459
459
  end
460
460
 
461
- return squished
461
+ return squished.map { |dep_kvs| Dependency.new(**dep_kvs) }
462
462
  end
463
463
 
464
464
  def self.parse_sbt_deps(type, lines)
@@ -70,12 +70,12 @@ module Bibliothecary
70
70
  # * The other occurrence's name is the path to the local dependency (which has less information, and is duplicative, so we discard)
71
71
  .select { |name, _dep| name.start_with?("node_modules") }
72
72
  .map do |name, dep|
73
- {
73
+ Dependency.new(
74
74
  name: name.split("node_modules/").last,
75
75
  requirement: dep["version"] || "*",
76
76
  type: dep.fetch("dev", false) || dep.fetch("devOptional", false) ? "development" : "runtime",
77
77
  local: dep.fetch("link", false),
78
- }
78
+ )
79
79
  end
80
80
  end
81
81
 
@@ -90,11 +90,11 @@ module Bibliothecary
90
90
  parse_package_lock_deps_recursively(requirement.fetch("dependencies", []), depth + 1)
91
91
  end
92
92
 
93
- [{
93
+ [Dependency.new(
94
94
  name: name,
95
95
  requirement: version,
96
96
  type: type,
97
- }] + child_dependencies
97
+ )] + child_dependencies
98
98
  end
99
99
  end
100
100
 
@@ -102,14 +102,29 @@ module Bibliothecary
102
102
  manifest = JSON.parse(file_contents)
103
103
  raise "appears to be a lockfile rather than manifest format" if manifest.key?("lockfileVersion")
104
104
 
105
- (
106
- map_dependencies(manifest, "dependencies", "runtime") +
107
- map_dependencies(manifest, "devDependencies", "development")
108
- )
109
- .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
110
- .each do |dep|
111
- dep[:local] = dep[:requirement].start_with?("file:")
105
+ dependencies = manifest.fetch("dependencies", [])
106
+ .reject { |name, _requirement| name.start_with?("//") } # Omit comment keys. They are valid in package.json: https://groups.google.com/g/nodejs/c/NmL7jdeuw0M/m/yTqI05DRQrIJ
107
+ .map do |name, requirement|
108
+ Dependency.new(
109
+ name: name,
110
+ requirement: requirement,
111
+ type: "runtime",
112
+ local: requirement.start_with?("file:")
113
+ )
114
+ end
115
+
116
+ dependencies += manifest.fetch("devDependencies", [])
117
+ .reject { |name, _requirement| name.start_with?("//") } # Omit comment keys. They are valid in package.json: https://groups.google.com/g/nodejs/c/NmL7jdeuw0M/m/yTqI05DRQrIJ
118
+ .map do |name, requirement|
119
+ Dependency.new(
120
+ name: name,
121
+ requirement: requirement,
122
+ type: "development",
123
+ local: requirement.start_with?("file:")
124
+ )
112
125
  end
126
+
127
+ dependencies
113
128
  end
114
129
 
115
130
  def self.parse_yarn_lock(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
@@ -118,15 +133,20 @@ module Bibliothecary
118
133
  raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.yarn_parser_host}/parse", response.response_code) unless response.success?
119
134
 
120
135
  json = JSON.parse(response.body, symbolize_names: true)
121
- json.uniq.map do |dep|
122
- {
123
- name: dep[:name],
124
- requirement: dep[:version],
125
- lockfile_requirement: dep[:requirement],
126
- type: dep[:type],
127
- local: dep[:requirement]&.start_with?("file:"),
128
- }
129
- end
136
+ json
137
+ .uniq
138
+ .reject do |dep|
139
+ dep[:requirement]&.include?("workspace") && dep[:version].include?("use.local")
140
+ end
141
+ .map do |dep|
142
+ Dependency.new(
143
+ name: dep[:name],
144
+ requirement: dep[:version],
145
+ lockfile_requirement: dep[:requirement],
146
+ type: dep[:type],
147
+ local: dep[:requirement]&.start_with?("file:"),
148
+ )
149
+ end
130
150
  end
131
151
 
132
152
  def self.parse_ls(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
@@ -150,12 +170,12 @@ module Bibliothecary
150
170
  private_class_method def self.transform_tree_to_array(deps_by_name)
151
171
  deps_by_name.map do |name, metadata|
152
172
  [
153
- {
173
+ Dependency.new(
154
174
  name: name,
155
175
  requirement: metadata["version"],
156
176
  lockfile_requirement: metadata.fetch("from", "").split("@").last,
157
177
  type: "runtime",
158
- },
178
+ ),
159
179
  ] + transform_tree_to_array(metadata.fetch("dependencies", {}))
160
180
  end.flatten(1)
161
181
  end
@@ -52,11 +52,11 @@ module Bibliothecary
52
52
  manifest = JSON.parse file_contents
53
53
  manifest.fetch("libraries",[]).map do |name, _requirement|
54
54
  dep = name.split("/")
55
- {
55
+ Dependency.new(
56
56
  name: dep[0],
57
57
  requirement: dep[1],
58
58
  type: "runtime",
59
- }
59
+ )
60
60
  end
61
61
  end
62
62
 
@@ -68,13 +68,13 @@ module Bibliothecary
68
68
  frameworks[framework] = deps
69
69
  .reject { |_name, details| details["type"] == "Project" } # Projects do not have versions
70
70
  .map do |name, details|
71
- {
71
+ Dependency.new(
72
72
  name: name,
73
73
  # 'resolved' has been set in all examples so far
74
74
  # so fallback to requested is pure paranoia
75
75
  requirement: details.fetch("resolved", details.fetch("requested", "*")),
76
76
  type: "runtime",
77
- }
77
+ )
78
78
  end
79
79
  end
80
80
 
@@ -92,11 +92,11 @@ module Bibliothecary
92
92
  def self.parse_packages_config(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
93
93
  manifest = Ox.parse file_contents
94
94
  manifest.packages.locate("package").map do |dependency|
95
- {
95
+ Dependency.new(
96
96
  name: dependency.id,
97
97
  requirement: (dependency.version if dependency.respond_to? "version") || "*",
98
98
  type: dependency.respond_to?("developmentDependency") && dependency.developmentDependency == "true" ? "development" : "runtime",
99
- }
99
+ )
100
100
  end
101
101
  rescue
102
102
  []
@@ -117,13 +117,13 @@ module Bibliothecary
117
117
  "runtime"
118
118
  end
119
119
 
120
- {
120
+ Dependency.new(
121
121
  name: dependency.Include,
122
122
  requirement: requirement,
123
123
  type: type,
124
- }
124
+ )
125
125
  end
126
- packages.uniq {|package| package[:name] }
126
+ packages.uniq {|package| package.name }
127
127
  rescue
128
128
  []
129
129
  end
@@ -131,11 +131,11 @@ module Bibliothecary
131
131
  def self.parse_nuspec(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
132
132
  manifest = Ox.parse file_contents
133
133
  manifest.package.metadata.dependencies.locate("dependency").map do |dependency|
134
- {
134
+ Dependency.new(
135
135
  name: dependency.id,
136
136
  requirement: dependency.attributes[:version] || "*",
137
137
  type: dependency.respond_to?("developmentDependency") && dependency.developmentDependency == "true" ? "development" : "runtime",
138
- }
138
+ )
139
139
  end
140
140
  rescue
141
141
  []
@@ -145,14 +145,14 @@ module Bibliothecary
145
145
  lines = file_contents.split("\n")
146
146
  package_version_re = /\s+(?<name>\S+)\s\((?<version>\d+\.\d+[\.\d+[\.\d+]*]*)\)/
147
147
  packages = lines.select { |line| package_version_re.match(line) }.map { |line| package_version_re.match(line) }.map do |match|
148
- {
148
+ Dependency.new(
149
149
  name: match[:name].strip,
150
150
  requirement: match[:version],
151
151
  type: "runtime",
152
- }
152
+ )
153
153
  end
154
154
  # we only have to enforce uniqueness by name because paket ensures that there is only the single version globally in the project
155
- packages.uniq {|package| package[:name] }
155
+ packages.uniq {|package| package.name }
156
156
  end
157
157
 
158
158
  def self.parse_project_assets_json(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
@@ -164,11 +164,11 @@ module Bibliothecary
164
164
  .select { |_name, details| details["type"] == "package" }
165
165
  .map do |name, _details|
166
166
  name_split = name.split("/")
167
- {
167
+ Dependency.new(
168
168
  name: name_split[0],
169
169
  requirement: name_split[1],
170
170
  type: "runtime",
171
- }
171
+ )
172
172
  end
173
173
  end
174
174
 
@@ -25,23 +25,29 @@ module Bibliothecary
25
25
  def self.parse_lockfile(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
26
26
  manifest = JSON.parse file_contents
27
27
  manifest.fetch("packages",[]).map do |dependency|
28
- {
28
+ requirement = dependency["version"]
29
+
30
+ # Store Drupal version if Drupal, but include the original manifest version for reference
31
+ original_requirement, requirement = requirement, dependency.dig("source", "reference") if is_drupal_module(dependency)
32
+
33
+ Dependency.new(
29
34
  name: dependency["name"],
30
- requirement: dependency["version"],
35
+ requirement: requirement,
31
36
  type: "runtime",
32
- }.tap do |result|
33
- # Store Drupal version if Drupal, but include the original manifest version for reference
34
- result[:original_requirement], result[:requirement] = result[:requirement], dependency.dig("source", "reference") if is_drupal_module(dependency)
35
- end
37
+ original_requirement: original_requirement
38
+ )
36
39
  end + manifest.fetch("packages-dev",[]).map do |dependency|
37
- {
40
+ requirement = dependency["version"]
41
+
42
+ # Store Drupal version if Drupal, but include the original manifest version for reference
43
+ original_requirement, requirement = requirement, dependency.dig("source", "reference") if is_drupal_module(dependency)
44
+
45
+ Dependency.new(
38
46
  name: dependency["name"],
39
- requirement: dependency["version"],
47
+ requirement: requirement,
40
48
  type: "development",
41
- }.tap do |result|
42
- # Store Drupal version if Drupal, but include the original manifest version for reference
43
- result[:original_requirement], result[:requirement] = result[:requirement], dependency.dig("source", "reference") if is_drupal_module(dependency)
44
- end
49
+ original_requirement: original_requirement
50
+ )
45
51
  end
46
52
  end
47
53
 
@@ -29,11 +29,11 @@ module Bibliothecary
29
29
  def self.parse_yaml_lockfile(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
30
30
  manifest = YAML.load file_contents
31
31
  manifest.fetch("packages", []).map do |name, dep|
32
- {
32
+ Dependency.new(
33
33
  name: name,
34
34
  requirement: dep["version"],
35
35
  type: "runtime",
36
- }
36
+ )
37
37
  end
38
38
  end
39
39
  end
@@ -147,11 +147,11 @@ module Bibliothecary
147
147
  def self.map_dependencies(packages, type)
148
148
  return [] unless packages
149
149
  packages.map do |name, info|
150
- {
150
+ Dependency.new(
151
151
  name: name,
152
152
  requirement: map_requirements(info),
153
153
  type: type,
154
- }
154
+ )
155
155
  end
156
156
  end
157
157
 
@@ -176,11 +176,11 @@ module Bibliothecary
176
176
  next if group == "_meta"
177
177
  group = "runtime" if group == "default"
178
178
  dependencies.each do |name, info|
179
- deps << {
179
+ deps << Dependency.new(
180
180
  name: name,
181
181
  requirement: map_requirements(info),
182
182
  type: group,
183
- }
183
+ )
184
184
  end
185
185
  end
186
186
  deps
@@ -198,11 +198,11 @@ module Bibliothecary
198
198
  "runtime"
199
199
  end
200
200
 
201
- deps << {
201
+ deps << Dependency.new(
202
202
  name: package["name"],
203
203
  requirement: map_requirements(package),
204
204
  type: group,
205
- }
205
+ )
206
206
  end
207
207
  deps
208
208
  end
@@ -215,11 +215,11 @@ module Bibliothecary
215
215
  next if line.match(/^#/)
216
216
  match = line.match(REQUIRE_REGEXP)
217
217
  next unless match
218
- deps << {
218
+ deps << Dependency.new(
219
219
  name: match[1],
220
220
  requirement: match[-1] || "*",
221
221
  type: "runtime",
222
- }
222
+ )
223
223
  end
224
224
  deps
225
225
  end
@@ -233,11 +233,11 @@ module Bibliothecary
233
233
  def self.parse_dependency_tree_json(file_contents, options: {})
234
234
  JSON.parse(file_contents)
235
235
  .map do |pkg|
236
- {
236
+ Dependency.new(
237
237
  name: pkg.dig("package", "package_name"),
238
238
  requirement: pkg.dig("package", "installed_version"),
239
239
  type: "runtime",
240
- }
240
+ )
241
241
  end
242
242
  .uniq
243
243
  end
@@ -260,27 +260,25 @@ module Bibliothecary
260
260
  file_contents.split("\n").each do |line|
261
261
  if line["://"]
262
262
  begin
263
- result = parse_requirements_txt_url(line)
263
+ result = parse_requirements_txt_url(line, type)
264
264
  rescue URI::Error, NoEggSpecified
265
265
  next
266
266
  end
267
267
 
268
- deps << result.merge(
269
- type: type
270
- )
268
+ deps << result
271
269
  elsif (match = line.delete(" ").match(REQUIREMENTS_REGEXP))
272
- deps << {
270
+ deps << Dependency.new(
273
271
  name: match[1],
274
272
  requirement: match[-1] || "*",
275
273
  type: type,
276
- }
274
+ )
277
275
  end
278
276
  end
279
277
 
280
278
  deps.uniq
281
279
  end
282
280
 
283
- def self.parse_requirements_txt_url(url)
281
+ def self.parse_requirements_txt_url(url, type=nil)
284
282
  uri = URI.parse(url)
285
283
  raise NoEggSpecified, "No egg specified in #{url}" unless uri.fragment
286
284
 
@@ -289,7 +287,7 @@ module Bibliothecary
289
287
 
290
288
  requirement = uri.path[/@(.+)$/, 1]
291
289
 
292
- { name: name, requirement: requirement || "*" }
290
+ Dependency.new(name: name, requirement: requirement || "*", type: type)
293
291
  end
294
292
 
295
293
  def self.pip_compile?(file_contents)
@@ -43,11 +43,11 @@ module Bibliothecary
43
43
  if match
44
44
  name = match[1]
45
45
  version = match[2].gsub(/\(|\)/,"")
46
- {
46
+ Dependency.new(
47
47
  name: name,
48
48
  requirement: version,
49
49
  type: "runtime",
50
- }
50
+ )
51
51
  else
52
52
  parse_bundler(file_contents)
53
53
  end
@@ -70,11 +70,11 @@ module Bibliothecary
70
70
 
71
71
  return nil unless version
72
72
 
73
- {
73
+ Dependency.new(
74
74
  name: "bundler",
75
75
  requirement: version,
76
76
  type: "runtime",
77
- }
77
+ )
78
78
  end
79
79
  end
80
80
  end
@@ -33,11 +33,11 @@ module Bibliothecary
33
33
 
34
34
  def self.map_dependencies(hash, key, type)
35
35
  hash.fetch(key,[]).map do |name, requirement|
36
- {
36
+ Dependency.new(
37
37
  name: name,
38
38
  requirement: requirement["version"] || "*",
39
39
  type: type,
40
- }
40
+ )
41
41
  end
42
42
  end
43
43
  end
@@ -23,11 +23,11 @@ module Bibliothecary
23
23
  json["dependencies"].map do |dependency|
24
24
  name = dependency["url"].gsub(/^https?:\/\//, "").gsub(/\.git$/,"")
25
25
  version = "#{dependency['version']['lowerBound']} - #{dependency['version']['upperBound']}"
26
- {
26
+ Dependency.new(
27
27
  name: name,
28
28
  requirement: version,
29
29
  type: "runtime",
30
- }
30
+ )
31
31
  end
32
32
  end
33
33
  end
@@ -1,3 +1,3 @@
1
1
  module Bibliothecary
2
- VERSION = "9.1.0"
2
+ VERSION = "10.1.0"
3
3
  end
data/lib/bibliothecary.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "bibliothecary/version"
2
+ require "bibliothecary/dependency"
2
3
  require "bibliothecary/analyser"
3
4
  require "bibliothecary/configuration"
4
5
  require "bibliothecary/runner"
data/lib/sdl_parser.rb CHANGED
@@ -9,11 +9,11 @@ class SdlParser
9
9
 
10
10
  def dependencies
11
11
  parse.children("dependency").inject([]) do |deps, dep|
12
- deps.push({
12
+ deps.push(Bibliothecary::Dependency.new(
13
13
  name: dep.value,
14
14
  requirement: dep.attribute("version") || ">= 0",
15
15
  type: type,
16
- })
16
+ ))
17
17
  end.uniq
18
18
  end
19
19
 
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: 9.1.0
4
+ version: 10.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Nesbitt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-18 00:00:00.000000000 Z
11
+ date: 2024-07-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tomlrb
@@ -267,6 +267,7 @@ files:
267
267
  - ".ruby-version"
268
268
  - ".tidelift.yml"
269
269
  - ".travis.yml"
270
+ - CHANGELOG.md
270
271
  - CODE_OF_CONDUCT.md
271
272
  - Gemfile
272
273
  - LICENSE.txt
@@ -284,6 +285,7 @@ files:
284
285
  - lib/bibliothecary/analyser/matchers.rb
285
286
  - lib/bibliothecary/cli.rb
286
287
  - lib/bibliothecary/configuration.rb
288
+ - lib/bibliothecary/dependency.rb
287
289
  - lib/bibliothecary/exceptions.rb
288
290
  - lib/bibliothecary/file_info.rb
289
291
  - lib/bibliothecary/multi_parsers/bundler_like_manifest.rb