bibliothecary 12.0.0 → 12.1.0

Sign up to get free protection for your applications and to get access to all the features.
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 +12 -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 +18 -101
@@ -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
-