bibliothecary 7.3.5 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +1 -1
  3. data/README.md +7 -0
  4. data/bibliothecary.gemspec +1 -0
  5. data/lib/bibliothecary/analyser/analysis.rb +110 -0
  6. data/lib/bibliothecary/analyser/determinations.rb +27 -0
  7. data/lib/bibliothecary/analyser/matchers.rb +64 -0
  8. data/lib/bibliothecary/analyser.rb +32 -188
  9. data/lib/bibliothecary/cli.rb +3 -3
  10. data/lib/bibliothecary/file_info.rb +2 -0
  11. data/lib/bibliothecary/multi_parsers/bundler_like_manifest.rb +22 -0
  12. data/lib/bibliothecary/multi_parsers/cyclonedx.rb +156 -0
  13. data/lib/bibliothecary/multi_parsers/json_runtime.rb +16 -0
  14. data/lib/bibliothecary/parsers/bower.rb +2 -2
  15. data/lib/bibliothecary/parsers/cargo.rb +4 -2
  16. data/lib/bibliothecary/parsers/carthage.rb +6 -6
  17. data/lib/bibliothecary/parsers/clojars.rb +2 -2
  18. data/lib/bibliothecary/parsers/cocoapods.rb +5 -4
  19. data/lib/bibliothecary/parsers/conda.rb +11 -5
  20. data/lib/bibliothecary/parsers/cpan.rb +2 -2
  21. data/lib/bibliothecary/parsers/cran.rb +3 -1
  22. data/lib/bibliothecary/parsers/dub.rb +3 -2
  23. data/lib/bibliothecary/parsers/elm.rb +2 -1
  24. data/lib/bibliothecary/parsers/generic.rb +3 -3
  25. data/lib/bibliothecary/parsers/go.rb +13 -11
  26. data/lib/bibliothecary/parsers/hackage.rb +4 -2
  27. data/lib/bibliothecary/parsers/haxelib.rb +1 -0
  28. data/lib/bibliothecary/parsers/hex.rb +6 -4
  29. data/lib/bibliothecary/parsers/julia.rb +2 -2
  30. data/lib/bibliothecary/parsers/maven.rb +19 -11
  31. data/lib/bibliothecary/parsers/meteor.rb +1 -0
  32. data/lib/bibliothecary/parsers/npm.rb +7 -5
  33. data/lib/bibliothecary/parsers/nuget.rb +10 -7
  34. data/lib/bibliothecary/parsers/packagist.rb +4 -2
  35. data/lib/bibliothecary/parsers/pub.rb +2 -2
  36. data/lib/bibliothecary/parsers/pypi.rb +11 -9
  37. data/lib/bibliothecary/parsers/rubygems.rb +7 -4
  38. data/lib/bibliothecary/parsers/shard.rb +2 -2
  39. data/lib/bibliothecary/parsers/swift_pm.rb +4 -2
  40. data/lib/bibliothecary/runner.rb +8 -3
  41. data/lib/bibliothecary/version.rb +1 -1
  42. data/lib/bibliothecary.rb +3 -0
  43. metadata +22 -2
@@ -0,0 +1,156 @@
1
+ require 'json'
2
+ require 'ox'
3
+
4
+ # packageurl-ruby uses pattern-matching (https://docs.ruby-lang.org/en/2.7.0/NEWS.html#label-Pattern+matching)
5
+ # which warns a whole bunch in Ruby 2.7 as being an experimental feature, but has
6
+ # been accepted in Ruby 3.0 (https://rubyreferences.github.io/rubychanges/3.0.html#pattern-matching).
7
+ Warning[:experimental] = false
8
+ require 'package_url'
9
+ Warning[:experimental] = true
10
+
11
+ module Bibliothecary
12
+ module MultiParsers
13
+ module CycloneDX
14
+ include Bibliothecary::Analyser
15
+ include Bibliothecary::Analyser::TryCache
16
+
17
+ NoComponents = Class.new(StandardError)
18
+
19
+ class ManifestEntries
20
+ # If a purl type (key) exists, it will be used in a manifest for
21
+ # the key's value. If not, it's ignored.
22
+ #
23
+ # https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst
24
+ PURL_TYPE_MAPPING = {
25
+ "golang" => :go,
26
+ "maven" => :maven,
27
+ "npm" => :npm,
28
+ "cargo" => :cargo,
29
+ "composer" => :packagist,
30
+ "conda" => :conda,
31
+ "cran" => :cran,
32
+ "gem" => :rubygems,
33
+ "hackage" => :hackage,
34
+ "hex" => :hex,
35
+ "nuget" => :nuget,
36
+ "pypi" => :pypi,
37
+ "swift" => :swift_pm
38
+ }
39
+
40
+ attr_reader :manifests
41
+
42
+ def initialize(parse_queue:)
43
+ @manifests = {}
44
+
45
+ # Instead of recursing, we'll work through a queue of components
46
+ # to process, letting the different parser add components to the
47
+ # queue however they need to pull them from the source document.
48
+ @parse_queue = parse_queue
49
+ end
50
+
51
+ def <<(purl)
52
+ mapping = PURL_TYPE_MAPPING[purl.type]
53
+ return unless mapping
54
+
55
+ @manifests[mapping] ||= Set.new
56
+ @manifests[mapping] << {
57
+ name: self.class.full_name_for_purl(purl),
58
+ requirement: purl.version,
59
+ type: 'lockfile'
60
+ }
61
+ end
62
+
63
+ # Iterates over each manifest entry in the parse_queue, and accepts a block which will
64
+ # be called on each component. The block has two jobs: 1) add more sub-components
65
+ # to parse (if they exist), and 2) return the components purl.
66
+ def parse!(&block)
67
+ while @parse_queue.length > 0
68
+ component = @parse_queue.shift
69
+
70
+ purl_text = block.call(component, @parse_queue)
71
+
72
+ next unless purl_text
73
+
74
+ purl = PackageURL.parse(purl_text)
75
+
76
+ self << purl
77
+ end
78
+ end
79
+
80
+ def [](key)
81
+ @manifests[key]&.to_a
82
+ end
83
+
84
+ # @return [String] The properly namespaced package name
85
+ def self.full_name_for_purl(purl)
86
+ parts = [purl.namespace, purl.name].compact
87
+
88
+ case purl.type
89
+ when "maven"
90
+ parts.join(':')
91
+ else
92
+ parts.join('/')
93
+ end
94
+ end
95
+ end
96
+
97
+ def self.mapping
98
+ {
99
+ match_filename('cyclonedx.json') => {
100
+ kind: 'lockfile',
101
+ parser: :parse_cyclonedx_json
102
+ },
103
+ match_filename('cyclonedx.xml') => {
104
+ kind: 'lockfile',
105
+ parser: :parse_cyclonedx_xml
106
+ }
107
+ }
108
+ end
109
+
110
+ def parse_cyclonedx_json(file_contents, options: {})
111
+ manifest = nil
112
+
113
+ manifest = try_cache(options, options[:filename]) do
114
+ JSON.parse(file_contents)
115
+ end
116
+
117
+ raise NoComponents unless manifest["components"]
118
+
119
+ entries = ManifestEntries.new(parse_queue: manifest["components"])
120
+
121
+ entries.parse! do |component, parse_queue|
122
+ parse_queue.concat(component["components"]) if component["components"]
123
+
124
+ component["purl"]
125
+ end
126
+
127
+ entries[platform_name.to_sym]
128
+ end
129
+
130
+ def parse_cyclonedx_xml(file_contents, options: {})
131
+ manifest = try_cache(options, options[:filename]) do
132
+ Ox.parse(file_contents)
133
+ end
134
+
135
+ root = manifest
136
+ if root.respond_to?(:bom)
137
+ root = root.bom
138
+ end
139
+
140
+ raise NoComponents unless root.locate('components').first
141
+
142
+ entries = ManifestEntries.new(parse_queue: root.locate('components/*'))
143
+
144
+ entries.parse! do |component, parse_queue|
145
+ # #locate returns an empty array if nothing is found, so we can
146
+ # always safely concatenate it to the parse queue.
147
+ parse_queue.concat(component.locate('components/*'))
148
+
149
+ component.locate("purl").first&.text
150
+ end
151
+
152
+ entries[platform_name.to_sym]
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,16 @@
1
+ module Bibliothecary
2
+ module MultiParsers
3
+ # Provide JSON Runtime Manifest parsing
4
+ module JSONRuntime
5
+ def parse_json_runtime_manifest(file_contents, options: {})
6
+ JSON.parse(file_contents).fetch('dependencies',[]).map do |name, requirement|
7
+ {
8
+ name: name,
9
+ requirement: requirement,
10
+ type: 'runtime'
11
+ }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -14,8 +14,8 @@ module Bibliothecary
14
14
  }
15
15
  end
16
16
 
17
- def self.parse_manifest(manifest)
18
- json = JSON.parse(manifest)
17
+ def self.parse_manifest(file_contents, options: {})
18
+ json = JSON.parse(file_contents)
19
19
  map_dependencies(json, 'dependencies', 'runtime') +
20
20
  map_dependencies(json, 'devDependencies', 'development')
21
21
  end
@@ -16,7 +16,9 @@ module Bibliothecary
16
16
  }
17
17
  end
18
18
 
19
- def self.parse_manifest(file_contents)
19
+ add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
20
+
21
+ def self.parse_manifest(file_contents, options: {})
20
22
  manifest = Tomlrb.parse(file_contents)
21
23
  manifest.fetch('dependencies', []).map do |name, requirement|
22
24
  if requirement.respond_to?(:fetch)
@@ -31,7 +33,7 @@ module Bibliothecary
31
33
  .compact
32
34
  end
33
35
 
34
- def self.parse_lockfile(file_contents)
36
+ def self.parse_lockfile(file_contents, options: {})
35
37
  manifest = Tomlrb.parse(file_contents)
36
38
  manifest.fetch('package',[]).map do |dependency|
37
39
  next if not dependency['source'] or not dependency['source'].start_with?('registry+')
@@ -20,16 +20,16 @@ module Bibliothecary
20
20
  }
21
21
  end
22
22
 
23
- def self.parse_cartfile(manifest)
24
- map_dependencies(manifest, 'cartfile')
23
+ def self.parse_cartfile(file_contents, options: {})
24
+ map_dependencies(file_contents, 'cartfile')
25
25
  end
26
26
 
27
- def self.parse_cartfile_private(manifest)
28
- map_dependencies(manifest, 'cartfile.private')
27
+ def self.parse_cartfile_private(file_contents, options: {})
28
+ map_dependencies(file_contents, 'cartfile.private')
29
29
  end
30
30
 
31
- def self.parse_cartfile_resolved(manifest)
32
- map_dependencies(manifest, 'cartfile.resolved')
31
+ def self.parse_cartfile_resolved(file_contents, options: {})
32
+ map_dependencies(file_contents, 'cartfile.resolved')
33
33
  end
34
34
 
35
35
  def self.map_dependencies(manifest, path)
@@ -15,8 +15,8 @@ module Bibliothecary
15
15
  }
16
16
  end
17
17
 
18
- def self.parse_manifest(manifest)
19
- response = Typhoeus.post("#{Bibliothecary.configuration.clojars_parser_host}/project.clj", body: manifest)
18
+ def self.parse_manifest(file_contents, options: {})
19
+ response = Typhoeus.post("#{Bibliothecary.configuration.clojars_parser_host}/project.clj", body: file_contents)
20
20
  raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.clojars_parser_host}/project.clj", response.response_code) unless response.success?
21
21
  json = JSON.parse response.body
22
22
  index = json.index("dependencies")
@@ -5,6 +5,7 @@ module Bibliothecary
5
5
  module Parsers
6
6
  class CocoaPods
7
7
  include Bibliothecary::Analyser
8
+ extend Bibliothecary::MultiParsers::BundlerLikeManifest
8
9
 
9
10
  NAME_VERSION = '(?! )(.*?)(?: \(([^-]*)(?:-(.*))?\))?'.freeze
10
11
  NAME_VERSION_4 = /^ {4}#{NAME_VERSION}$/
@@ -32,7 +33,7 @@ module Bibliothecary
32
33
  }
33
34
  end
34
35
 
35
- def self.parse_podfile_lock(file_contents)
36
+ def self.parse_podfile_lock(file_contents, options: {})
36
37
  manifest = YAML.load file_contents
37
38
  manifest['PODS'].map do |row|
38
39
  pod = row.is_a?(String) ? row : row.keys.first
@@ -45,17 +46,17 @@ module Bibliothecary
45
46
  end.compact
46
47
  end
47
48
 
48
- def self.parse_podspec(file_contents)
49
+ def self.parse_podspec(file_contents, options: {})
49
50
  manifest = Gemnasium::Parser.send(:podspec, file_contents)
50
51
  parse_ruby_manifest(manifest)
51
52
  end
52
53
 
53
- def self.parse_podfile(file_contents)
54
+ def self.parse_podfile(file_contents, options: {})
54
55
  manifest = Gemnasium::Parser.send(:podfile, file_contents)
55
56
  parse_ruby_manifest(manifest)
56
57
  end
57
58
 
58
- def self.parse_json_manifest(file_contents)
59
+ def self.parse_json_manifest(file_contents, options: {})
59
60
  manifest = JSON.parse(file_contents)
60
61
  manifest['dependencies'].inject([]) do |deps, dep|
61
62
  deps.push({
@@ -26,13 +26,19 @@ module Bibliothecary
26
26
  }
27
27
  end
28
28
 
29
- def self.parse_conda(info, kind = "manifest")
30
- dependencies = call_conda_parser_web(info, kind)[kind.to_sym]
31
- dependencies.map { |dep| dep.merge(type: "runtime") }
29
+ add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
30
+
31
+ def self.parse_conda(file_contents, options: {})
32
+ parse_conda_with_kind(file_contents, "manifest")
32
33
  end
33
34
 
34
- def self.parse_conda_lockfile(info)
35
- parse_conda(info, "lockfile")
35
+ def self.parse_conda_lockfile(file_contents, options: {})
36
+ parse_conda_with_kind(file_contents, "lockfile")
37
+ end
38
+
39
+ def self.parse_conda_with_kind(info, kind)
40
+ dependencies = call_conda_parser_web(info, kind)[kind.to_sym]
41
+ dependencies.map { |dep| dep.merge(type: "runtime") }
36
42
  end
37
43
 
38
44
  private_class_method def self.call_conda_parser_web(file_contents, kind)
@@ -19,14 +19,14 @@ module Bibliothecary
19
19
  }
20
20
  end
21
21
 
22
- def self.parse_json_manifest(file_contents)
22
+ def self.parse_json_manifest(file_contents, options: {})
23
23
  manifest = JSON.parse file_contents
24
24
  manifest['prereqs'].map do |_group, deps|
25
25
  map_dependencies(deps, 'requires', 'runtime')
26
26
  end.flatten
27
27
  end
28
28
 
29
- def self.parse_yaml_manifest(file_contents)
29
+ def self.parse_yaml_manifest(file_contents, options: {})
30
30
  manifest = YAML.load file_contents
31
31
  map_dependencies(manifest, 'requires', 'runtime')
32
32
  end
@@ -16,7 +16,9 @@ module Bibliothecary
16
16
  }
17
17
  end
18
18
 
19
- def self.parse_description(file_contents)
19
+ add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
20
+
21
+ def self.parse_description(file_contents, options: {})
20
22
  manifest = DebControl::ControlFileBase.parse(file_contents)
21
23
  parse_section(manifest, 'Depends') +
22
24
  parse_section(manifest, 'Imports') +
@@ -5,6 +5,7 @@ module Bibliothecary
5
5
  module Parsers
6
6
  class Dub
7
7
  include Bibliothecary::Analyser
8
+ extend Bibliothecary::MultiParsers::JSONRuntime
8
9
 
9
10
  def self.mapping
10
11
  {
@@ -19,8 +20,8 @@ module Bibliothecary
19
20
  }
20
21
  end
21
22
 
22
- def self.parse_sdl_manifest(manifest)
23
- SdlParser.new(:runtime, manifest).dependencies
23
+ def self.parse_sdl_manifest(file_contents, options: {})
24
+ SdlParser.new(:runtime, file_contents).dependencies
24
25
  end
25
26
  end
26
27
  end
@@ -4,6 +4,7 @@ module Bibliothecary
4
4
  module Parsers
5
5
  class Elm
6
6
  include Bibliothecary::Analyser
7
+ extend Bibliothecary::MultiParsers::JSONRuntime
7
8
 
8
9
  def self.mapping
9
10
  {
@@ -18,7 +19,7 @@ module Bibliothecary
18
19
  }
19
20
  end
20
21
 
21
- def self.parse_json_lock(file_contents)
22
+ def self.parse_json_lock(file_contents, options: {})
22
23
  manifest = JSON.parse file_contents
23
24
  manifest.map do |name, requirement|
24
25
  {
@@ -14,7 +14,7 @@ module Bibliothecary
14
14
  }
15
15
  end
16
16
 
17
- def self.parse_lockfile(file_contents)
17
+ def self.parse_lockfile(file_contents, options: {})
18
18
  table = CSV.parse(file_contents, headers: true)
19
19
 
20
20
  required_headers = ["platform", "name", "requirement"]
@@ -22,9 +22,9 @@ module Bibliothecary
22
22
  raise "Missing headers #{missing_headers} in CSV" unless missing_headers.empty?
23
23
 
24
24
  table.map.with_index do |row, idx|
25
- line = idx + 1
25
+ line = idx + 2 # use 1-based index just like the 'csv' std lib, and count the headers as first row.
26
26
  required_headers.each do |h|
27
- raise "missing field '#{h}' on line #{line}" if row[h].empty?
27
+ raise "missing field '#{h}' on line #{line}" if row[h].nil? || row[h].empty?
28
28
  end
29
29
  {
30
30
  platform: row['platform'],
@@ -65,12 +65,14 @@ module Bibliothecary
65
65
  }
66
66
  end
67
67
 
68
- def self.parse_godep_json(file_contents)
68
+ add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
69
+
70
+ def self.parse_godep_json(file_contents, options: {})
69
71
  manifest = JSON.parse file_contents
70
72
  map_dependencies(manifest, 'Deps', 'ImportPath', 'Rev', 'runtime')
71
73
  end
72
74
 
73
- def self.parse_gpm(file_contents)
75
+ def self.parse_gpm(file_contents, options: {})
74
76
  deps = []
75
77
  file_contents.split("\n").each do |line|
76
78
  match = line.gsub(/(\#(.*))/, '').match(GPM_REGEXP)
@@ -84,38 +86,38 @@ module Bibliothecary
84
86
  deps
85
87
  end
86
88
 
87
- def self.parse_govendor(file_contents)
89
+ def self.parse_govendor(file_contents, options: {})
88
90
  manifest = JSON.load file_contents
89
91
  map_dependencies(manifest, 'package', 'path', 'revision', 'runtime')
90
92
  end
91
93
 
92
- def self.parse_glide_yaml(file_contents)
94
+ def self.parse_glide_yaml(file_contents, options: {})
93
95
  manifest = YAML.load file_contents
94
96
  map_dependencies(manifest, 'import', 'package', 'version', 'runtime') +
95
97
  map_dependencies(manifest, 'devImports', 'package', 'version', 'development')
96
98
  end
97
99
 
98
- def self.parse_glide_lockfile(file_contents)
100
+ def self.parse_glide_lockfile(file_contents, options: {})
99
101
  manifest = YAML.load file_contents
100
102
  map_dependencies(manifest, 'imports', 'name', 'version', 'runtime')
101
103
  end
102
104
 
103
- def self.parse_gb_manifest(file_contents)
105
+ def self.parse_gb_manifest(file_contents, options: {})
104
106
  manifest = JSON.parse file_contents
105
107
  map_dependencies(manifest, 'dependencies', 'importpath', 'revision', 'runtime')
106
108
  end
107
109
 
108
- def self.parse_dep_toml(file_contents)
110
+ def self.parse_dep_toml(file_contents, options: {})
109
111
  manifest = Tomlrb.parse file_contents
110
112
  map_dependencies(manifest, 'constraint', 'name', 'version', 'runtime')
111
113
  end
112
114
 
113
- def self.parse_dep_lockfile(file_contents)
115
+ def self.parse_dep_lockfile(file_contents, options: {})
114
116
  manifest = Tomlrb.parse file_contents
115
117
  map_dependencies(manifest, 'projects', 'name', 'revision', 'runtime')
116
118
  end
117
119
 
118
- def self.parse_go_mod(file_contents)
120
+ def self.parse_go_mod(file_contents, options: {})
119
121
  deps = []
120
122
  file_contents.lines.map(&:strip).each do |line|
121
123
  next if line.match(GOMOD_IGNORABLE_REGEX)
@@ -130,7 +132,7 @@ module Bibliothecary
130
132
  deps
131
133
  end
132
134
 
133
- def self.parse_go_sum(file_contents)
135
+ def self.parse_go_sum(file_contents, options: {})
134
136
  deps = []
135
137
  file_contents.lines.map(&:strip).each do |line|
136
138
  if match = line.match(GOSUM_REGEX)
@@ -144,7 +146,7 @@ module Bibliothecary
144
146
  deps.uniq
145
147
  end
146
148
 
147
- def self.parse_go_resolved(file_contents)
149
+ def self.parse_go_resolved(file_contents, options: {})
148
150
  JSON.parse(file_contents)
149
151
  .select { |dep| dep["Main"] != "true" }
150
152
  .map { |dep| { name: dep["Path"], requirement: dep["Version"], type: 'runtime' } }
@@ -19,7 +19,9 @@ module Bibliothecary
19
19
  }
20
20
  end
21
21
 
22
- def self.parse_cabal(file_contents)
22
+ add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
23
+
24
+ def self.parse_cabal(file_contents, options: {})
23
25
  headers = {
24
26
  'Content-Type' => "text/plain;charset=utf-8"
25
27
  }
@@ -30,7 +32,7 @@ module Bibliothecary
30
32
  JSON.parse(response.body, symbolize_names: true)
31
33
  end
32
34
 
33
- def self.parse_cabal_config(file_contents)
35
+ def self.parse_cabal_config(file_contents, options: {})
34
36
  manifest = DebControl::ControlFileBase.parse(file_contents)
35
37
  deps = manifest.first['constraints'].delete("\n").split(',').map(&:strip)
36
38
  deps.map do |dependency|
@@ -4,6 +4,7 @@ module Bibliothecary
4
4
  module Parsers
5
5
  class Haxelib
6
6
  include Bibliothecary::Analyser
7
+ extend Bibliothecary::MultiParsers::JSONRuntime
7
8
 
8
9
  def self.mapping
9
10
  {
@@ -18,8 +18,10 @@ module Bibliothecary
18
18
  }
19
19
  end
20
20
 
21
- def self.parse_mix(manifest)
22
- response = Typhoeus.post("#{Bibliothecary.configuration.mix_parser_host}/", body: manifest)
21
+ add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
22
+
23
+ def self.parse_mix(file_contents, options: {})
24
+ response = Typhoeus.post("#{Bibliothecary.configuration.mix_parser_host}/", body: file_contents)
23
25
  raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.mix_parser_host}/", response.response_code) unless response.success?
24
26
  json = JSON.parse response.body
25
27
 
@@ -32,8 +34,8 @@ module Bibliothecary
32
34
  end
33
35
  end
34
36
 
35
- def self.parse_mix_lock(manifest)
36
- response = Typhoeus.post("#{Bibliothecary.configuration.mix_parser_host}/lock", body: manifest)
37
+ def self.parse_mix_lock(file_contents, options: {})
38
+ response = Typhoeus.post("#{Bibliothecary.configuration.mix_parser_host}/lock", body: file_contents)
37
39
  raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.mix_parser_host}/", response.response_code) unless response.success?
38
40
  json = JSON.parse response.body
39
41
 
@@ -12,9 +12,9 @@ module Bibliothecary
12
12
  }
13
13
  end
14
14
 
15
- def self.parse_require(manifest)
15
+ def self.parse_require(file_contents, options: {})
16
16
  deps = []
17
- manifest.split("\n").each do |line|
17
+ file_contents.split("\n").each do |line|
18
18
  next if line.match(/^#/) || line.empty?
19
19
  split = line.split(/\s/)
20
20
  if line.match(/^@/)
@@ -38,7 +38,7 @@ module Bibliothecary
38
38
  },
39
39
  match_filename("pom.xml", case_insensitive: true) => {
40
40
  kind: 'manifest',
41
- parser: :parse_pom_manifest
41
+ parser: :parse_standalone_pom_manifest
42
42
  },
43
43
  match_filename("build.gradle", case_insensitive: true) => {
44
44
  kind: 'manifest',
@@ -72,7 +72,9 @@ module Bibliothecary
72
72
  }
73
73
  end
74
74
 
75
- def self.parse_ivy_manifest(file_contents)
75
+ add_multi_parser Bibliothecary::MultiParsers::CycloneDX
76
+
77
+ def self.parse_ivy_manifest(file_contents, options: {})
76
78
  manifest = Ox.parse file_contents
77
79
  manifest.dependencies.locate('dependency').map do |dependency|
78
80
  attrs = dependency.attributes
@@ -95,7 +97,7 @@ module Bibliothecary
95
97
  false
96
98
  end
97
99
 
98
- def self.parse_ivy_report(file_contents)
100
+ def self.parse_ivy_report(file_contents, options: {})
99
101
  doc = Ox.parse file_contents
100
102
  root = doc.locate("ivy-report").first
101
103
  raise "ivy-report document does not have ivy-report at the root" if root.nil?
@@ -120,7 +122,7 @@ module Bibliothecary
120
122
  end.compact
121
123
  end
122
124
 
123
- def self.parse_gradle_resolved(file_contents)
125
+ def self.parse_gradle_resolved(file_contents, options: {})
124
126
  type = nil
125
127
  file_contents.split("\n").map do |line|
126
128
  type_match = GRADLE_TYPE_REGEX.match(line)
@@ -151,7 +153,7 @@ module Bibliothecary
151
153
  end.compact.uniq {|item| [item[:name], item[:requirement], item[:type]]}
152
154
  end
153
155
 
154
- def self.parse_maven_resolved(file_contents)
156
+ def self.parse_maven_resolved(file_contents, options: {})
155
157
  Strings::ANSI.sanitize(file_contents)
156
158
  .split("\n")
157
159
  .map(&method(:parse_resolved_dep_line))
@@ -159,7 +161,7 @@ module Bibliothecary
159
161
  .uniq
160
162
  end
161
163
 
162
- def self.parse_maven_tree(file_contents)
164
+ def self.parse_maven_tree(file_contents, options: {})
163
165
  file_contents = file_contents.gsub(/\r\n?/, "\n")
164
166
  captures = file_contents.scan(/^\[INFO\](?:(?:\+-)|\||(?:\\-)|\s)+((?:[\w\.-]+:)+[\w\.\-${}]+)/).flatten.uniq
165
167
 
@@ -195,7 +197,13 @@ module Bibliothecary
195
197
  }
196
198
  end
197
199
 
198
- def self.parse_pom_manifest(file_contents, parent_properties = {})
200
+ def self.parse_standalone_pom_manifest(file_contents, options: {})
201
+ parse_pom_manifest(file_contents, {}, options: options)
202
+ end
203
+
204
+ # parent_properties is used by Libraries:
205
+ # https://github.com/librariesio/libraries.io/blob/e970925aade2596a03268b6e1be785eba8502c62/app/models/package_manager/maven.rb#L129
206
+ def self.parse_pom_manifest(file_contents, parent_properties = {}, options: {})
199
207
  manifest = Ox.parse file_contents
200
208
  xml = manifest.respond_to?('project') ? manifest.project : manifest
201
209
  [].tap do |deps|
@@ -211,8 +219,8 @@ module Bibliothecary
211
219
  end
212
220
  end
213
221
 
214
- def self.parse_gradle(manifest)
215
- response = Typhoeus.post("#{Bibliothecary.configuration.gradle_parser_host}/parse", body: manifest)
222
+ def self.parse_gradle(file_contents, options: {})
223
+ response = Typhoeus.post("#{Bibliothecary.configuration.gradle_parser_host}/parse", body: file_contents)
216
224
  raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.gradle_parser_host}/parse", response.response_code) unless response.success?
217
225
  json = JSON.parse(response.body)
218
226
  return [] unless json['dependencies']
@@ -227,7 +235,7 @@ module Bibliothecary
227
235
  end.compact
228
236
  end
229
237
 
230
- def self.parse_gradle_kts(manifest)
238
+ def self.parse_gradle_kts(file_contents, options: {})
231
239
  # TODO: the gradle-parser side needs to be implemented for this, coming soon.
232
240
  []
233
241
  end
@@ -307,7 +315,7 @@ module Bibliothecary
307
315
  end
308
316
  end
309
317
 
310
- def self.parse_sbt_update_full(file_contents)
318
+ def self.parse_sbt_update_full(file_contents, options: {})
311
319
  all_deps = []
312
320
  type = nil
313
321
  lines = file_contents.split("\n")
@@ -4,6 +4,7 @@ module Bibliothecary
4
4
  module Parsers
5
5
  class Meteor
6
6
  include Bibliothecary::Analyser
7
+ extend Bibliothecary::MultiParsers::JSONRuntime
7
8
 
8
9
  def self.mapping
9
10
  {