bibliothecary 9.1.0 → 10.0.0

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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +6 -1
  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 +30 -15
  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: f04e398aaa7fd5f464025615120f69886e8f908b842205276a20f1b8c1d86f0c
4
+ data.tar.gz: f053d3dcba23b8f90541ff82b060b7252264c2048fa6f9ee296947d8d915113d
5
5
  SHA512:
6
- metadata.gz: 65b0c2930fd92537908a604c94e2a12852e5cc5167838f3fb7c86325a16fc930479b746096a063506f01174c9eb40a792e7f88e1aacefce0f657059b630297a6
7
- data.tar.gz: 1b29d13ee41d4a6aabe54854ebe316f1f17cea1be88ba9a73a290c645f3cad4853e2cc6497f419dff474809e1905c1ad851d1d214a58373eee013774830a23e6
6
+ metadata.gz: 3f064aa980b63f797b057b857c6d687fd3cff93081276040fff1cf6e15f57cf4faa454ef78cae8f749ccf26326701f8f3c7c2e6a96afbf05a5b49dc8feed9ff8
7
+ data.tar.gz: 83189af6e0f441e3bb21d19344a3bdbee0052fe2457cde85ec831be7a1bb2a63bc622c811c80a1e93076fcc6470d67a980846c3028ab23b674bb2fc238735598
data/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
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.0.0] - 2024-07-08
17
+
18
+ ### Added
19
+
20
+ - Added `CHANGELOG.md`, based on https://keepachangelog.com/en/1.1.0/.
21
+ - New `Bibliothecary::Dependency` class.
22
+
23
+ ### Changed
24
+
25
+ - **Breaking**: `Bibliothecary::Parsers` classes now return lists of `Bibliothecary::Dependency`
26
+ instances instead of `Hash` instances.
data/README.md CHANGED
@@ -164,7 +164,12 @@ All available config options are in: https://github.com/librariesio/bibliothecar
164
164
 
165
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.
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
@@ -119,13 +134,13 @@ module Bibliothecary
119
134
 
120
135
  json = JSON.parse(response.body, symbolize_names: true)
121
136
  json.uniq.map do |dep|
122
- {
137
+ Dependency.new(
123
138
  name: dep[:name],
124
139
  requirement: dep[:version],
125
140
  lockfile_requirement: dep[:requirement],
126
141
  type: dep[:type],
127
142
  local: dep[:requirement]&.start_with?("file:"),
128
- }
143
+ )
129
144
  end
130
145
  end
131
146
 
@@ -150,12 +165,12 @@ module Bibliothecary
150
165
  private_class_method def self.transform_tree_to_array(deps_by_name)
151
166
  deps_by_name.map do |name, metadata|
152
167
  [
153
- {
168
+ Dependency.new(
154
169
  name: name,
155
170
  requirement: metadata["version"],
156
171
  lockfile_requirement: metadata.fetch("from", "").split("@").last,
157
172
  type: "runtime",
158
- },
173
+ ),
159
174
  ] + transform_tree_to_array(metadata.fetch("dependencies", {}))
160
175
  end.flatten(1)
161
176
  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.0.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.0.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-12 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