bibliothecary 12.0.0 → 12.1.1

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +2 -1
  3. data/.rubocop.yml +10 -2
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +13 -0
  6. data/Gemfile +16 -1
  7. data/Rakefile +2 -0
  8. data/bibliothecary.gemspec +11 -13
  9. data/bin/bibliothecary +2 -1
  10. data/bin/console +1 -0
  11. data/lib/bibliothecary/analyser/analysis.rb +13 -8
  12. data/lib/bibliothecary/analyser/determinations.rb +2 -0
  13. data/lib/bibliothecary/analyser/matchers.rb +17 -17
  14. data/lib/bibliothecary/analyser.rb +11 -8
  15. data/lib/bibliothecary/cli.rb +3 -1
  16. data/lib/bibliothecary/configuration.rb +3 -8
  17. data/lib/bibliothecary/dependency.rb +17 -15
  18. data/lib/bibliothecary/exceptions.rb +6 -2
  19. data/lib/bibliothecary/file_info.rb +9 -11
  20. data/lib/bibliothecary/multi_parsers/bundler_like_manifest.rb +13 -10
  21. data/lib/bibliothecary/multi_parsers/cyclonedx.rb +10 -8
  22. data/lib/bibliothecary/multi_parsers/dependencies_csv.rb +11 -4
  23. data/lib/bibliothecary/multi_parsers/json_runtime.rb +5 -2
  24. data/lib/bibliothecary/multi_parsers/spdx.rb +24 -19
  25. data/lib/bibliothecary/parsers/bower.rb +5 -3
  26. data/lib/bibliothecary/parsers/cargo.rb +10 -4
  27. data/lib/bibliothecary/parsers/cocoapods.rb +15 -11
  28. data/lib/bibliothecary/parsers/conda.rb +20 -18
  29. data/lib/bibliothecary/parsers/cpan.rb +6 -4
  30. data/lib/bibliothecary/parsers/cran.rb +10 -6
  31. data/lib/bibliothecary/parsers/dub.rb +4 -2
  32. data/lib/bibliothecary/parsers/elm.rb +4 -1
  33. data/lib/bibliothecary/parsers/go.rb +51 -43
  34. data/lib/bibliothecary/parsers/haxelib.rb +2 -1
  35. data/lib/bibliothecary/parsers/julia.rb +5 -1
  36. data/lib/bibliothecary/parsers/maven.rb +93 -77
  37. data/lib/bibliothecary/parsers/meteor.rb +2 -0
  38. data/lib/bibliothecary/parsers/npm.rb +89 -75
  39. data/lib/bibliothecary/parsers/nuget.rb +37 -28
  40. data/lib/bibliothecary/parsers/packagist.rb +21 -11
  41. data/lib/bibliothecary/parsers/pub.rb +4 -2
  42. data/lib/bibliothecary/parsers/pypi.rb +48 -29
  43. data/lib/bibliothecary/parsers/rubygems.rb +16 -12
  44. data/lib/bibliothecary/parsers/shard.rb +10 -7
  45. data/lib/bibliothecary/purl_util.rb +2 -1
  46. data/lib/bibliothecary/related_files_info.rb +7 -8
  47. data/lib/bibliothecary/runner/multi_manifest_filter.rb +5 -4
  48. data/lib/bibliothecary/runner.rb +13 -10
  49. data/lib/bibliothecary/version.rb +3 -1
  50. data/lib/bibliothecary.rb +7 -4
  51. data/lib/sdl_parser.rb +11 -6
  52. metadata +19 -106
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # packageurl-ruby uses pattern-matching (https://docs.ruby-lang.org/en/2.7.0/NEWS.html#label-Pattern+matching)
2
4
  # which warns a whole bunch in Ruby 2.7 as being an experimental feature, but has
3
5
  # been accepted in Ruby 3.0 (https://rubyreferences.github.io/rubychanges/3.0.html#pattern-matching).
@@ -43,7 +45,7 @@ module Bibliothecary
43
45
 
44
46
  def parse_spdx_tag_value(file_contents, options: {})
45
47
  entries = try_cache(options, options[:filename]) do
46
- parse_spdx_tag_value_file_contents(file_contents)
48
+ parse_spdx_tag_value_file_contents(file_contents, options.fetch(:filename, nil))
47
49
  end
48
50
 
49
51
  raise NoEntries if entries.empty?
@@ -51,7 +53,7 @@ module Bibliothecary
51
53
  entries[platform_name.to_sym]
52
54
  end
53
55
 
54
- def parse_spdx_tag_value_file_contents(file_contents)
56
+ def parse_spdx_tag_value_file_contents(file_contents, source = nil)
55
57
  entries = {}
56
58
  spdx_name = spdx_version = platform = purl_name = purl_version = nil
57
59
 
@@ -65,13 +67,14 @@ module Bibliothecary
65
67
  # Per the spec:
66
68
  # > A new package Information section is denoted by the package name (7.1) field.
67
69
  add_entry(entries: entries, platform: platform, purl_name: purl_name,
68
- spdx_name: spdx_name, purl_version: purl_version, spdx_version: spdx_version)
70
+ spdx_name: spdx_name, purl_version: purl_version, spdx_version: spdx_version,
71
+ source: source)
69
72
 
70
73
  # reset for this new package
71
74
  spdx_name = spdx_version = platform = purl_name = purl_version = nil
72
75
 
73
76
  # capture the new package's name
74
- spdx_package_name = match[1]
77
+ spdx_name = match[1]
75
78
  elsif (match = stripped_line.match(PACKAGE_VERSION_REGEXP))
76
79
  spdx_version = match[1]
77
80
  elsif (match = stripped_line.match(PURL_REGEXP))
@@ -83,7 +86,8 @@ module Bibliothecary
83
86
  end
84
87
 
85
88
  add_entry(entries: entries, platform: platform, purl_name: purl_name,
86
- spdx_name: spdx_name, purl_version: purl_version, spdx_version: spdx_version)
89
+ spdx_name: spdx_name, purl_version: purl_version, spdx_version: spdx_version,
90
+ source: source)
87
91
 
88
92
  entries
89
93
  end
@@ -95,7 +99,7 @@ module Bibliothecary
95
99
 
96
100
  def parse_spdx_json(file_contents, options: {})
97
101
  entries = try_cache(options, options[:filename]) do
98
- parse_spdx_json_file_contents(file_contents)
102
+ parse_spdx_json_file_contents(file_contents, options.fetch(:filename, nil))
99
103
  end
100
104
 
101
105
  raise NoEntries if entries.empty?
@@ -103,7 +107,7 @@ module Bibliothecary
103
107
  entries[platform_name.to_sym]
104
108
  end
105
109
 
106
- def parse_spdx_json_file_contents(file_contents)
110
+ def parse_spdx_json_file_contents(file_contents, source = nil)
107
111
  entries = {}
108
112
  manifest = JSON.parse(file_contents)
109
113
 
@@ -111,33 +115,34 @@ module Bibliothecary
111
115
  spdx_name = package["name"]
112
116
  spdx_version = package["versionInfo"]
113
117
 
114
- first_purl_string = package.dig("externalRefs")&.find { |ref| ref["referenceType"] == "purl" }&.dig("referenceLocator")
118
+ first_purl_string = package["externalRefs"]&.find { |ref| ref["referenceType"] == "purl" }&.dig("referenceLocator")
115
119
  purl = first_purl_string && PackageURL.parse(first_purl_string)
116
120
  platform = PurlUtil::PURL_TYPE_MAPPING[purl&.type]
117
121
  purl_name = PurlUtil.full_name(purl)
118
122
  purl_version = purl&.version
119
123
 
120
124
  add_entry(entries: entries, platform: platform, purl_name: purl_name,
121
- spdx_name: spdx_name, purl_version: purl_version, spdx_version: spdx_version)
125
+ spdx_name: spdx_name, purl_version: purl_version, spdx_version: spdx_version,
126
+ source: source)
122
127
  end
123
128
 
124
129
  entries
125
130
  end
126
131
 
127
- def add_entry(entries:, platform:, purl_name:, spdx_name:, purl_version:, spdx_version:)
132
+ def add_entry(entries:, platform:, purl_name:, spdx_name:, purl_version:, spdx_version:, source: nil)
128
133
  package_name = purl_name || spdx_name
129
134
  package_version = purl_version || spdx_version
130
135
 
131
- if platform && package_name && package_version
132
- entries[platform.to_sym] ||= []
133
- entries[platform.to_sym] << Dependency.new(
134
- name: package_name,
135
- requirement: package_version,
136
- type: "lockfile"
137
- )
138
- end
139
- end
136
+ return unless platform && package_name && package_version
140
137
 
138
+ entries[platform.to_sym] ||= []
139
+ entries[platform.to_sym] << Dependency.new(
140
+ name: package_name,
141
+ requirement: package_version,
142
+ type: "lockfile",
143
+ source: source
144
+ )
145
+ end
141
146
  end
142
147
  end
143
148
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
 
3
5
  module Bibliothecary
@@ -16,10 +18,10 @@ module Bibliothecary
16
18
 
17
19
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
18
20
 
19
- def self.parse_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
21
+ def self.parse_manifest(file_contents, options: {})
20
22
  json = JSON.parse(file_contents)
21
- map_dependencies(json, "dependencies", "runtime") +
22
- map_dependencies(json, "devDependencies", "development")
23
+ map_dependencies(json, "dependencies", "runtime", options.fetch(:filename, nil)) +
24
+ map_dependencies(json, "devDependencies", "development", options.fetch(:filename, nil))
23
25
  end
24
26
  end
25
27
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bibliothecary
2
4
  module Parsers
3
5
  class Cargo
@@ -20,7 +22,7 @@ module Bibliothecary
20
22
  add_multi_parser(Bibliothecary::MultiParsers::Spdx)
21
23
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
22
24
 
23
- def self.parse_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
25
+ def self.parse_manifest(file_contents, options: {})
24
26
  manifest = Tomlrb.parse(file_contents)
25
27
 
26
28
  parsed_dependencies = []
@@ -30,10 +32,12 @@ module Bibliothecary
30
32
  if requirement.respond_to?(:fetch)
31
33
  requirement = requirement["version"] or next
32
34
  end
35
+
33
36
  Dependency.new(
34
37
  name: name,
35
38
  requirement: requirement,
36
39
  type: index.zero? ? "runtime" : "development",
40
+ source: options.fetch(:filename, nil)
37
41
  )
38
42
  end
39
43
  end
@@ -41,14 +45,16 @@ module Bibliothecary
41
45
  parsed_dependencies.flatten.compact
42
46
  end
43
47
 
44
- def self.parse_lockfile(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
48
+ def self.parse_lockfile(file_contents, options: {})
45
49
  manifest = Tomlrb.parse(file_contents)
46
- manifest.fetch("package",[]).map do |dependency|
47
- next if not dependency["source"] or not dependency["source"].start_with?("registry+")
50
+ manifest.fetch("package", []).map do |dependency|
51
+ next if !dependency["source"] || !dependency["source"].start_with?("registry+")
52
+
48
53
  Dependency.new(
49
54
  name: dependency["name"],
50
55
  requirement: dependency["version"],
51
56
  type: "runtime",
57
+ source: options.fetch(:filename, nil)
52
58
  )
53
59
  end
54
60
  .compact
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "gemnasium/parser"
2
4
  require "yaml"
3
5
 
@@ -7,7 +9,7 @@ module Bibliothecary
7
9
  include Bibliothecary::Analyser
8
10
  extend Bibliothecary::MultiParsers::BundlerLikeManifest
9
11
 
10
- NAME_VERSION = '(?! )(.*?)(?: \(([^-]*)(?:-(.*))?\))?'.freeze
12
+ NAME_VERSION = '(?! )(.*?)(?: \(([^-]*)(?:-(.*))?\))?'
11
13
  NAME_VERSION_4 = /^ {4}#{NAME_VERSION}$/
12
14
 
13
15
  def self.mapping
@@ -35,7 +37,7 @@ module Bibliothecary
35
37
 
36
38
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
37
39
 
38
- def self.parse_podfile_lock(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
40
+ def self.parse_podfile_lock(file_contents, options: {})
39
41
  manifest = YAML.load file_contents
40
42
  manifest["PODS"].map do |row|
41
43
  pod = row.is_a?(String) ? row : row.keys.first
@@ -44,28 +46,30 @@ module Bibliothecary
44
46
  name: match[1].split("/").first,
45
47
  requirement: match[2],
46
48
  type: "runtime",
49
+ source: options.fetch(:filename, nil)
47
50
  )
48
51
  end.compact
49
52
  end
50
53
 
51
- def self.parse_podspec(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
54
+ def self.parse_podspec(file_contents, options: {})
52
55
  manifest = Gemnasium::Parser.send(:podspec, file_contents)
53
- parse_ruby_manifest(manifest)
56
+ parse_ruby_manifest(manifest, options.fetch(:filename, nil))
54
57
  end
55
58
 
56
- def self.parse_podfile(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
59
+ def self.parse_podfile(file_contents, options: {})
57
60
  manifest = Gemnasium::Parser.send(:podfile, file_contents)
58
- parse_ruby_manifest(manifest)
61
+ parse_ruby_manifest(manifest, options.fetch(:filename, nil))
59
62
  end
60
63
 
61
- def self.parse_json_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
64
+ def self.parse_json_manifest(file_contents, options: {})
62
65
  manifest = JSON.parse(file_contents)
63
66
  manifest["dependencies"].inject([]) do |deps, dep|
64
67
  deps.push(Dependency.new(
65
- name: dep[0],
66
- requirement: dep[1],
67
- type: "runtime",
68
- ))
68
+ name: dep[0],
69
+ requirement: dep[1],
70
+ type: "runtime",
71
+ source: options.fetch(:filename, nil)
72
+ ))
69
73
  end.uniq
70
74
  end
71
75
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "yaml"
2
4
 
3
5
  module Bibliothecary
@@ -22,39 +24,39 @@ module Bibliothecary
22
24
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
23
25
  add_multi_parser(Bibliothecary::MultiParsers::Spdx)
24
26
 
25
- def self.parse_conda(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
27
+ def self.parse_conda(file_contents, options: {})
26
28
  manifest = YAML.load(file_contents)
27
- deps = manifest.dig("dependencies")
29
+ deps = manifest["dependencies"]
28
30
  deps.map do |dep|
29
31
  next unless dep.is_a? String # only deal with strings to skip parsing pip stuff
30
32
 
31
33
  parsed = parse_name_requirement_from_matchspec(dep)
32
- Dependency.new(**parsed.merge(type: "runtime"))
34
+ Dependency.new(**parsed, type: "runtime", source: options.fetch(:filename, nil))
33
35
  end.compact
34
36
  end
35
37
 
36
- def self.parse_name_requirement_from_matchspec(ms)
38
+ def self.parse_name_requirement_from_matchspec(matchspec)
37
39
  # simplified version of the implementation in conda to handle what we care about
38
40
  # https://github.com/conda/conda/blob/main/conda/models/match_spec.py#L598
39
41
  # (channel(/subdir):(namespace):)name(version(build))[key1=value1,key2=value2]
40
- return if ms.end_with?("@")
42
+ return if matchspec.end_with?("@")
41
43
 
42
44
  # strip off comments and optional features
43
- ms = ms.split(/#/, 2).first
44
- ms = ms.split(/ if /, 2).first
45
+ matchspec = matchspec.split("#", 2).first
46
+ matchspec = matchspec.split(" if ", 2).first
45
47
 
46
48
  # strip off brackets
47
- ms = ms.match(/^(.*)(?:\[(.*)\])?$/)[1]
49
+ matchspec = matchspec.match(/^(.*)(?:\[(.*)\])?$/)[1]
48
50
 
49
51
  # strip off any parens
50
- ms = ms.match(/^(.*)(?:(\(.*\)))?$/)[1]
52
+ matchspec = matchspec.match(/^(.*)(?:(\(.*\)))?$/)[1]
51
53
 
52
54
  # deal with channel and namespace, I wish there was rsplit in ruby
53
- split = ms.reverse.split(":", 2)
54
- ms = split.last.reverse
55
+ split = matchspec.reverse.split(":", 2)
56
+ matchspec = split.last.reverse
55
57
 
56
58
  # split the name from the version/build combo
57
- matches = ms.match(/([^ =<>!~]+)?([><!=~ ].+)?/)
59
+ matches = matchspec.match(/([^ =<>!~]+)?([><!=~ ].+)?/)
58
60
  name = matches[1]
59
61
  version_build = matches[2]
60
62
 
@@ -64,19 +66,19 @@ module Bibliothecary
64
66
  # and now deal with getting the version from version/build
65
67
  matches = version_build.match(/((?:.+?)[^><!,|]?)(?:(?<![=!|,<>~])(?:[ =])([^-=,|<>~]+?))?$/)
66
68
  version = if matches
67
- matches[1].strip
68
- else
69
- version_build.strip
70
- end
69
+ matches[1].strip
70
+ else
71
+ version_build.strip
72
+ end
71
73
  end
72
74
  # if it's an exact requirement, lose the =
73
75
  if version&.start_with?("==")
74
76
  version = version[2..]
75
77
  elsif version&.start_with?("=")
76
- version = version[1..]
78
+ version = version[1..]
77
79
  end
78
80
 
79
- return {
81
+ {
80
82
  name: name,
81
83
  requirement: version || "", # NOTE: this ignores build info
82
84
  }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "yaml"
2
4
  require "json"
3
5
 
@@ -21,16 +23,16 @@ module Bibliothecary
21
23
 
22
24
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
23
25
 
24
- def self.parse_json_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
26
+ def self.parse_json_manifest(file_contents, options: {})
25
27
  manifest = JSON.parse file_contents
26
28
  manifest["prereqs"].map do |_group, deps|
27
- map_dependencies(deps, "requires", "runtime")
29
+ map_dependencies(deps, "requires", "runtime", options.fetch(:filename, nil))
28
30
  end.flatten
29
31
  end
30
32
 
31
- def self.parse_yaml_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
33
+ def self.parse_yaml_manifest(file_contents, options: {})
32
34
  manifest = YAML.load file_contents
33
- map_dependencies(manifest, "requires", "runtime")
35
+ map_dependencies(manifest, "requires", "runtime", options.fetch(:filename, nil))
34
36
  end
35
37
  end
36
38
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "deb_control"
2
4
 
3
5
  module Bibliothecary
@@ -20,16 +22,17 @@ module Bibliothecary
20
22
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
21
23
  add_multi_parser(Bibliothecary::MultiParsers::Spdx)
22
24
 
23
- def self.parse_description(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
25
+ def self.parse_description(file_contents, options: {})
24
26
  manifest = DebControl::ControlFileBase.parse(file_contents)
25
- parse_section(manifest, "Depends") +
26
- parse_section(manifest, "Imports") +
27
- parse_section(manifest, "Suggests") +
28
- parse_section(manifest, "Enhances")
27
+ parse_section(manifest, "Depends", options.fetch(:filename, nil)) +
28
+ parse_section(manifest, "Imports", options.fetch(:filename, nil)) +
29
+ parse_section(manifest, "Suggests", options.fetch(:filename, nil)) +
30
+ parse_section(manifest, "Enhances", options.fetch(:filename, nil))
29
31
  end
30
32
 
31
- def self.parse_section(manifest, name)
33
+ def self.parse_section(manifest, name, source = nil)
32
34
  return [] unless manifest.first[name]
35
+
33
36
  deps = manifest.first[name].delete("\n").split(",").map(&:strip)
34
37
  deps.map do |dependency|
35
38
  dep = dependency.match(REQUIRE_REGEXP)
@@ -37,6 +40,7 @@ module Bibliothecary
37
40
  name: dep[1],
38
41
  requirement: dep[2],
39
42
  type: name.downcase,
43
+ source: source
40
44
  )
41
45
  end
42
46
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
  require "sdl_parser"
3
5
 
@@ -22,8 +24,8 @@ module Bibliothecary
22
24
 
23
25
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
24
26
 
25
- def self.parse_sdl_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
26
- SdlParser.new(:runtime, file_contents).dependencies
27
+ def self.parse_sdl_manifest(file_contents, options: {})
28
+ SdlParser.new(:runtime, file_contents, options.fetch(:filename, nil)).dependencies
27
29
  end
28
30
  end
29
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
 
3
5
  module Bibliothecary
@@ -21,13 +23,14 @@ module Bibliothecary
21
23
 
22
24
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
23
25
 
24
- def self.parse_json_lock(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
26
+ def self.parse_json_lock(file_contents, options: {})
25
27
  manifest = JSON.parse file_contents
26
28
  manifest.map do |name, requirement|
27
29
  Dependency.new(
28
30
  name: name,
29
31
  requirement: requirement,
30
32
  type: "runtime",
33
+ source: options.fetch(:filename, nil)
31
34
  )
32
35
  end
33
36
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "yaml"
2
4
  require "json"
3
5
 
@@ -74,60 +76,63 @@ module Bibliothecary
74
76
  add_multi_parser(Bibliothecary::MultiParsers::Spdx)
75
77
  add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
76
78
 
77
- def self.parse_godep_json(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
79
+ def self.parse_godep_json(file_contents, options: {})
78
80
  manifest = JSON.parse file_contents
79
- map_dependencies(manifest, "Deps", "ImportPath", "Rev", "runtime")
81
+ map_dependencies(manifest, "Deps", "ImportPath", "Rev", "runtime", options.fetch(:filename, nil))
80
82
  end
81
83
 
82
- def self.parse_gpm(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
84
+ def self.parse_gpm(file_contents, options: {})
83
85
  deps = []
84
86
  file_contents.split("\n").each do |line|
85
87
  match = line.gsub(/(\#(.*))/, "").match(GPM_REGEXP)
86
88
  next unless match
89
+
87
90
  deps << Dependency.new(
88
91
  name: match[1].strip,
89
92
  requirement: match[2].strip,
90
93
  type: "runtime",
94
+ source: options.fetch(:filename, nil)
91
95
  )
92
96
  end
93
97
  deps
94
98
  end
95
99
 
96
- def self.parse_govendor(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
97
- manifest = JSON.load file_contents
98
- map_dependencies(manifest, "package", "path", "revision", "runtime")
100
+ def self.parse_govendor(file_contents, options: {})
101
+ manifest = JSON.parse file_contents
102
+ map_dependencies(manifest, "package", "path", "revision", "runtime", options.fetch(:filename, nil))
99
103
  end
100
104
 
101
- def self.parse_glide_yaml(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
105
+ def self.parse_glide_yaml(file_contents, options: {})
102
106
  manifest = YAML.load file_contents
103
- map_dependencies(manifest, "import", "package", "version", "runtime") +
104
- map_dependencies(manifest, "devImports", "package", "version", "development")
107
+ map_dependencies(manifest, "import", "package", "version", "runtime", options.fetch(:filename, nil)) +
108
+ map_dependencies(manifest, "devImports", "package", "version", "development", options.fetch(:filename, nil))
105
109
  end
106
110
 
107
- def self.parse_glide_lockfile(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
108
- manifest = YAML.load(file_contents, permitted_classes: [Time])
109
- map_dependencies(manifest, "imports", "name", "version", "runtime")
111
+ def self.parse_glide_lockfile(file_contents, options: {})
112
+ # glide.lock files contain an "updated" Time field, but Ruby 3.2+ requires us to safelist that class
113
+ manifest = YAML.load file_contents, permitted_classes: [Time]
114
+ map_dependencies(manifest, "imports", "name", "version", "runtime", options.fetch(:filename, nil))
110
115
  end
111
116
 
112
- def self.parse_gb_manifest(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
117
+ def self.parse_gb_manifest(file_contents, options: {})
113
118
  manifest = JSON.parse file_contents
114
- map_dependencies(manifest, "dependencies", "importpath", "revision", "runtime")
119
+ map_dependencies(manifest, "dependencies", "importpath", "revision", "runtime", options.fetch(:filename, nil))
115
120
  end
116
121
 
117
- def self.parse_dep_toml(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
122
+ def self.parse_dep_toml(file_contents, options: {})
118
123
  manifest = Tomlrb.parse file_contents
119
- map_dependencies(manifest, "constraint", "name", "version", "runtime")
124
+ map_dependencies(manifest, "constraint", "name", "version", "runtime", options.fetch(:filename, nil))
120
125
  end
121
126
 
122
- def self.parse_dep_lockfile(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
127
+ def self.parse_dep_lockfile(file_contents, options: {})
123
128
  manifest = Tomlrb.parse file_contents
124
- map_dependencies(manifest, "projects", "name", "revision", "runtime")
129
+ map_dependencies(manifest, "projects", "name", "revision", "runtime", options.fetch(:filename, nil))
125
130
  end
126
131
 
127
- def self.parse_go_mod(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
128
- categorized_deps = parse_go_mod_categorized_deps(file_contents)
132
+ def self.parse_go_mod(file_contents, options: {})
133
+ categorized_deps = parse_go_mod_categorized_deps(file_contents, options.fetch(:filename, nil))
129
134
 
130
- deps = categorized_deps["require"]
135
+ categorized_deps["require"]
131
136
  .map do |dep|
132
137
  # NOTE: A "replace" directive doesn't add the dep to the module graph unless the original dep is also in a "require" directive,
133
138
  # so we need to track down replacements here and use those instead of the originals, if present.
@@ -143,11 +148,9 @@ module Bibliothecary
143
148
 
144
149
  replaced_dep || dep
145
150
  end
146
-
147
- return deps
148
151
  end
149
152
 
150
- def self.parse_go_mod_categorized_deps(file_contents)
153
+ def self.parse_go_mod_categorized_deps(file_contents, source)
151
154
  current_multiline_category = nil
152
155
  # docs: https://go.dev/ref/mod#go-mod-file-require
153
156
  categorized_deps = {
@@ -159,38 +162,39 @@ module Bibliothecary
159
162
  file_contents
160
163
  .lines
161
164
  .map(&:strip)
162
- .reject { |line| line =~ /^#{GOMOD_COMMENT_REGEXP}/ } # ignore comment lines
165
+ .grep_v(/^#{GOMOD_COMMENT_REGEXP}/) # ignore comment lines
163
166
  .each do |line|
164
167
  if line.match(GOMOD_MULTILINE_END_REGEXP) # detect the end of a multiline
165
168
  current_multiline_category = nil
166
169
  elsif (match = line.match(GOMOD_MULTILINE_START_REGEXP)) # or, detect the start of a multiline
167
170
  current_multiline_category = match[1]
168
171
  elsif (match = line.match(GOMOD_SINGLELINE_DEP_REGEXP)) # or, detect a singleline dep
169
- categorized_deps[match[:category]] << go_mod_category_relative_dep(category: match[:category], line: line, match: match)
170
- elsif (current_multiline_category && match = line.match(GOMOD_MULTILINE_DEP_REGEXP)) # otherwise, parse the multiline dep
171
- categorized_deps[current_multiline_category] << go_mod_category_relative_dep(category: current_multiline_category, line: line, match: match)
172
+ categorized_deps[match[:category]] << go_mod_category_relative_dep(category: match[:category], line: line, match: match, source: source)
173
+ elsif current_multiline_category && (match = line.match(GOMOD_MULTILINE_DEP_REGEXP)) # otherwise, parse the multiline dep
174
+ categorized_deps[current_multiline_category] << go_mod_category_relative_dep(category: current_multiline_category, line: line, match: match, source: source)
172
175
  end
173
176
  end
174
177
  categorized_deps
175
178
  end
176
179
 
177
- def self.parse_go_sum(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
180
+ def self.parse_go_sum(file_contents, options: {})
178
181
  deps = []
179
182
  file_contents.lines.map(&:strip).each do |line|
180
- if (match = line.match(GOSUM_REGEXP))
181
- deps << Dependency.new(
182
- name: match[1].strip,
183
- requirement: match[2].strip.split("/").first,
184
- type: "runtime",
185
- )
186
- end
183
+ next unless (match = line.match(GOSUM_REGEXP))
184
+
185
+ deps << Dependency.new(
186
+ name: match[1].strip,
187
+ requirement: match[2].strip.split("/").first,
188
+ type: "runtime",
189
+ source: options.fetch(:filename, nil)
190
+ )
187
191
  end
188
192
  deps.uniq
189
193
  end
190
194
 
191
- def self.parse_go_resolved(file_contents, options: {}) # rubocop:disable Lint/UnusedMethodArgument
195
+ def self.parse_go_resolved(file_contents, options: {})
192
196
  JSON.parse(file_contents)
193
- .select { |dep| dep["Main"] != "true" }
197
+ .reject { |dep| dep["Main"] == "true" }
194
198
  .map do |dep|
195
199
  if dep["Replace"].is_a?(String) && dep["Replace"] != "<nil>" && dep["Replace"] != ""
196
200
  # NOTE: The "replace" directive doesn't actually change the version reported from Go (e.g. "go mod graph"), it only changes
@@ -199,28 +203,29 @@ module Bibliothecary
199
203
  name, requirement = dep["Replace"].split(" ", 2)
200
204
  requirement = "*" if requirement.to_s.strip == ""
201
205
  Dependency.new(
202
- name: name, requirement: requirement, original_name: dep["Path"], original_requirement: dep["Version"], type: dep.fetch("Scope") { "runtime" }
206
+ name: name, requirement: requirement, original_name: dep["Path"], original_requirement: dep["Version"], type: dep.fetch("Scope", "runtime"), source: options.fetch(:filename, nil)
203
207
  )
204
208
  else
205
209
  Dependency.new(
206
- name: dep["Path"], requirement: dep["Version"], type: dep.fetch("Scope") { "runtime" }
210
+ name: dep["Path"], requirement: dep["Version"], type: dep.fetch("Scope", "runtime"), source: options.fetch(:filename, nil)
207
211
  )
208
212
  end
209
213
  end
210
214
  end
211
215
 
212
- def self.map_dependencies(manifest, attr_name, dep_attr_name, version_attr_name, type)
213
- manifest.fetch(attr_name,[]).map do |dependency|
216
+ def self.map_dependencies(manifest, attr_name, dep_attr_name, version_attr_name, type, source = nil)
217
+ manifest.fetch(attr_name, []).map do |dependency|
214
218
  Dependency.new(
215
219
  name: dependency[dep_attr_name],
216
220
  requirement: dependency[version_attr_name],
217
221
  type: type,
222
+ source: source
218
223
  )
219
224
  end
220
225
  end
221
226
 
222
227
  # Returns our standard-ish dep Hash based on the category of dep matched ("require", "replace", etc.)
223
- def self.go_mod_category_relative_dep(category:, line:, match:)
228
+ def self.go_mod_category_relative_dep(category:, line:, match:, source: nil)
224
229
  case category
225
230
  when "replace"
226
231
  replacement_dep = line.split(GOMOD_REPLACEMENT_SEPARATOR_REGEXP, 2).last
@@ -232,6 +237,7 @@ module Bibliothecary
232
237
  requirement: replacement_match[:requirement],
233
238
  type: "runtime",
234
239
  direct: !match[:indirect],
240
+ source: source
235
241
  )
236
242
  when "retract"
237
243
  Dependency.new(
@@ -240,6 +246,7 @@ module Bibliothecary
240
246
  type: "runtime",
241
247
  deprecated: true,
242
248
  direct: !match[:indirect],
249
+ source: source
243
250
  )
244
251
  else
245
252
  Dependency.new(
@@ -247,6 +254,7 @@ module Bibliothecary
247
254
  requirement: match[:requirement],
248
255
  type: "runtime",
249
256
  direct: !match[:indirect],
257
+ source: source
250
258
  )
251
259
  end
252
260
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "json"
2
4
 
3
5
  module Bibliothecary
@@ -19,4 +21,3 @@ module Bibliothecary
19
21
  end
20
22
  end
21
23
  end
22
-