ecosystems-bibliothecary 14.3.0 → 15.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +8 -23
- data/bibliothecary.gemspec +5 -9
- data/lib/bibliothecary/analyser.rb +0 -31
- data/lib/bibliothecary/cli.rb +35 -26
- data/lib/bibliothecary/configuration.rb +1 -6
- data/lib/bibliothecary/dependency.rb +1 -4
- data/lib/bibliothecary/parsers/bentoml.rb +0 -2
- data/lib/bibliothecary/parsers/bower.rb +0 -1
- data/lib/bibliothecary/parsers/cargo.rb +12 -10
- data/lib/bibliothecary/parsers/carthage.rb +51 -15
- data/lib/bibliothecary/parsers/clojars.rb +14 -18
- data/lib/bibliothecary/parsers/cocoapods.rb +100 -19
- data/lib/bibliothecary/parsers/cog.rb +0 -2
- data/lib/bibliothecary/parsers/conan.rb +156 -0
- data/lib/bibliothecary/parsers/conda.rb +0 -3
- data/lib/bibliothecary/parsers/cpan.rb +0 -2
- data/lib/bibliothecary/parsers/cran.rb +40 -19
- data/lib/bibliothecary/parsers/docker.rb +0 -2
- data/lib/bibliothecary/parsers/dub.rb +33 -8
- data/lib/bibliothecary/parsers/dvc.rb +0 -2
- data/lib/bibliothecary/parsers/elm.rb +13 -3
- data/lib/bibliothecary/parsers/go.rb +14 -5
- data/lib/bibliothecary/parsers/hackage.rb +132 -24
- data/lib/bibliothecary/parsers/haxelib.rb +14 -4
- data/lib/bibliothecary/parsers/hex.rb +37 -20
- data/lib/bibliothecary/parsers/homebrew.rb +0 -2
- data/lib/bibliothecary/parsers/julia.rb +0 -2
- data/lib/bibliothecary/parsers/maven.rb +35 -25
- data/lib/bibliothecary/parsers/meteor.rb +14 -4
- data/lib/bibliothecary/parsers/mlflow.rb +0 -2
- data/lib/bibliothecary/parsers/npm.rb +47 -59
- data/lib/bibliothecary/parsers/nuget.rb +22 -21
- data/lib/bibliothecary/parsers/ollama.rb +0 -2
- data/lib/bibliothecary/parsers/packagist.rb +0 -3
- data/lib/bibliothecary/parsers/pub.rb +0 -2
- data/lib/bibliothecary/parsers/pypi.rb +54 -35
- data/lib/bibliothecary/parsers/rubygems.rb +92 -27
- data/lib/bibliothecary/parsers/shard.rb +0 -1
- data/lib/bibliothecary/parsers/swift_pm.rb +77 -29
- data/lib/bibliothecary/parsers/vcpkg.rb +68 -17
- data/lib/bibliothecary/runner.rb +2 -15
- data/lib/bibliothecary/version.rb +1 -1
- data/lib/bibliothecary.rb +0 -4
- metadata +2 -110
- data/.codeclimate.yml +0 -25
- data/.github/CONTRIBUTING.md +0 -195
- data/.github/workflows/ci.yml +0 -25
- data/.gitignore +0 -10
- data/.rspec +0 -2
- data/.rubocop.yml +0 -69
- data/.ruby-version +0 -1
- data/.tidelift +0 -1
- data/CODE_OF_CONDUCT.md +0 -74
- data/Gemfile +0 -35
- data/Rakefile +0 -18
- data/bin/benchmark +0 -386
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/lib/bibliothecary/multi_parsers/bundler_like_manifest.rb +0 -26
- data/lib/bibliothecary/multi_parsers/cyclonedx.rb +0 -170
- data/lib/bibliothecary/multi_parsers/dependencies_csv.rb +0 -155
- data/lib/bibliothecary/multi_parsers/json_runtime.rb +0 -22
- data/lib/bibliothecary/multi_parsers/spdx.rb +0 -149
- data/lib/bibliothecary/purl_util.rb +0 -37
- data/lib/bibliothecary/runner/multi_manifest_filter.rb +0 -92
- data/lib/sdl_parser.rb +0 -30
|
@@ -7,13 +7,12 @@ module Bibliothecary
|
|
|
7
7
|
module Parsers
|
|
8
8
|
class Nuget
|
|
9
9
|
include Bibliothecary::Analyser
|
|
10
|
-
extend Bibliothecary::MultiParsers::JSONRuntime
|
|
11
10
|
|
|
12
11
|
def self.mapping
|
|
13
12
|
{
|
|
14
13
|
match_filename("Project.json") => {
|
|
15
14
|
kind: "manifest",
|
|
16
|
-
parser: :
|
|
15
|
+
parser: :parse_project_json,
|
|
17
16
|
},
|
|
18
17
|
match_filename("Project.lock.json") => {
|
|
19
18
|
kind: "lockfile",
|
|
@@ -46,9 +45,19 @@ module Bibliothecary
|
|
|
46
45
|
}
|
|
47
46
|
end
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
def self.parse_project_json(file_contents, options: {})
|
|
49
|
+
manifest = JSON.parse(file_contents)
|
|
50
|
+
dependencies = manifest.fetch("dependencies", {}).map do |name, requirement|
|
|
51
|
+
Dependency.new(
|
|
52
|
+
name: name,
|
|
53
|
+
requirement: requirement,
|
|
54
|
+
type: "runtime",
|
|
55
|
+
source: options.fetch(:filename, nil),
|
|
56
|
+
platform: platform_name
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
ParserResult.new(dependencies: dependencies)
|
|
60
|
+
end
|
|
52
61
|
|
|
53
62
|
def self.parse_project_lock_json(file_contents, options: {})
|
|
54
63
|
manifest = JSON.parse file_contents
|
|
@@ -68,32 +77,24 @@ module Bibliothecary
|
|
|
68
77
|
def self.parse_packages_lock_json(file_contents, options: {})
|
|
69
78
|
manifest = JSON.parse file_contents
|
|
70
79
|
|
|
71
|
-
frameworks
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
# Merge dependencies from all target frameworks, deduping by name.
|
|
81
|
+
# Different frameworks may have different versions of the same package,
|
|
82
|
+
# but we can only report one version per package.
|
|
83
|
+
dependencies = manifest.fetch("dependencies", {}).flat_map do |_framework, deps|
|
|
84
|
+
deps
|
|
85
|
+
.reject { |_name, details| details["type"] == "Project" }
|
|
75
86
|
.map do |name, details|
|
|
76
87
|
Dependency.new(
|
|
77
88
|
name: name,
|
|
78
|
-
# 'resolved' has been set in all examples so far
|
|
79
|
-
# so fallback to requested is pure paranoia
|
|
80
89
|
requirement: details.fetch("resolved", details.fetch("requested", "*")),
|
|
81
90
|
type: "runtime",
|
|
82
91
|
source: options.fetch(:filename, nil),
|
|
83
92
|
platform: platform_name
|
|
84
93
|
)
|
|
85
94
|
end
|
|
86
|
-
end
|
|
95
|
+
end.uniq(&:name)
|
|
87
96
|
|
|
88
|
-
|
|
89
|
-
# we should really return multiple manifests, but bibliothecary doesn't
|
|
90
|
-
# do that yet so at least pick deterministically.
|
|
91
|
-
|
|
92
|
-
# Note, frameworks can be empty, so remove empty ones and then return the last sorted item if any
|
|
93
|
-
frameworks.delete_if { |_k, v| v.empty? }
|
|
94
|
-
return ParserResult.new(dependencies: frameworks[frameworks.keys.max]) unless frameworks.empty?
|
|
95
|
-
end
|
|
96
|
-
ParserResult.new(dependencies: [])
|
|
97
|
+
ParserResult.new(dependencies: dependencies)
|
|
97
98
|
end
|
|
98
99
|
|
|
99
100
|
def self.parse_packages_config(file_contents, options: {})
|
|
@@ -15,8 +15,6 @@ module Bibliothecary
|
|
|
15
15
|
}
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
19
|
-
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
20
18
|
|
|
21
19
|
def self.parse_modelfile(file_contents, options: {})
|
|
22
20
|
source = options.fetch(:filename, 'Modelfile')
|
|
@@ -20,9 +20,6 @@ module Bibliothecary
|
|
|
20
20
|
}
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
24
|
-
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
25
|
-
add_multi_parser(Bibliothecary::MultiParsers::Spdx)
|
|
26
23
|
|
|
27
24
|
def self.parse_lockfile(file_contents, options: {})
|
|
28
25
|
manifest = JSON.parse file_contents
|
|
@@ -20,8 +20,6 @@ module Bibliothecary
|
|
|
20
20
|
}
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
24
|
-
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
25
23
|
|
|
26
24
|
def self.parse_yaml_manifest(file_contents, options: {})
|
|
27
25
|
manifest = YAML.load file_contents
|
|
@@ -88,34 +88,40 @@ module Bibliothecary
|
|
|
88
88
|
}
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
92
|
-
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
93
|
-
add_multi_parser(Bibliothecary::MultiParsers::Spdx)
|
|
94
91
|
|
|
95
92
|
def self.parser_pylock(file_contents, options: {})
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
dependencies = []
|
|
94
|
+
# Split into [[packages]] blocks and extract fields from each
|
|
95
|
+
file_contents.split(/\[\[packages\]\]/).drop(1).each do |block|
|
|
96
|
+
name = block[/^name\s*=\s*"([^"]+)"/m, 1]
|
|
97
|
+
version = block[/^version\s*=\s*"([^"]+)"/m, 1]
|
|
98
|
+
# Local packages have [packages.archive] or [packages.directory] sections
|
|
99
|
+
is_local = block.include?("[packages.archive]") || block.include?("[packages.directory]")
|
|
100
|
+
|
|
101
|
+
dependencies << Dependency.new(
|
|
100
102
|
platform: platform_name,
|
|
101
|
-
name:
|
|
103
|
+
name: name,
|
|
102
104
|
type: "runtime",
|
|
103
105
|
source: options.fetch(:filename, nil),
|
|
104
|
-
requirement:
|
|
105
|
-
local: is_local
|
|
106
|
+
requirement: version || "*",
|
|
107
|
+
local: is_local || nil
|
|
106
108
|
)
|
|
107
109
|
end
|
|
108
110
|
ParserResult.new(dependencies: dependencies)
|
|
109
111
|
end
|
|
110
112
|
|
|
111
113
|
def self.parse_uv_lock(file_contents, options: {})
|
|
112
|
-
manifest = Tomlrb.parse(file_contents)
|
|
113
114
|
source = options.fetch(:filename, nil)
|
|
114
|
-
dependencies =
|
|
115
|
-
|
|
115
|
+
dependencies = []
|
|
116
|
+
# Split into [[package]] blocks and extract fields from each
|
|
117
|
+
file_contents.split(/\[\[package\]\]/).drop(1).each do |block|
|
|
118
|
+
name = block[/name\s*=\s*"([^"]+)"/, 1]
|
|
119
|
+
version = block[/version\s*=\s*"([^"]+)"/, 1]
|
|
120
|
+
|
|
121
|
+
dependencies << Dependency.new(
|
|
116
122
|
platform: platform_name,
|
|
117
|
-
name:
|
|
118
|
-
requirement:
|
|
123
|
+
name: name,
|
|
124
|
+
requirement: version,
|
|
119
125
|
type: "runtime", # All dependencies are considered runtime
|
|
120
126
|
source: source
|
|
121
127
|
)
|
|
@@ -230,32 +236,43 @@ module Bibliothecary
|
|
|
230
236
|
end
|
|
231
237
|
|
|
232
238
|
def self.parse_poetry_lock(file_contents, options: {})
|
|
233
|
-
manifest = Tomlrb.parse(file_contents)
|
|
234
239
|
deps = []
|
|
235
|
-
|
|
236
|
-
|
|
240
|
+
# Split into [[package]] blocks and extract fields from each
|
|
241
|
+
file_contents.split(/\[\[package\]\]/).drop(1).each do |block|
|
|
242
|
+
name = block[/^name\s*=\s*"([^"]+)"/m, 1]
|
|
243
|
+
version = block[/^version\s*=\s*"([^"]+)"/m, 1]
|
|
237
244
|
|
|
238
245
|
# Poetry <1.2.0 used singular "category" for kind
|
|
239
246
|
# Poetry >=1.2.0 uses plural "groups" field for kind(s)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
+
# Use ^ anchor to avoid matching commented lines
|
|
248
|
+
category = block[/^category\s*=\s*"([^"]+)"/m, 1]
|
|
249
|
+
groups_match = block[/^groups\s*=\s*\[([^\]]+)\]/m, 1]
|
|
250
|
+
groups = if groups_match
|
|
251
|
+
groups_match.scan(/"([^"]+)"/).flatten
|
|
252
|
+
elsif category
|
|
253
|
+
[category]
|
|
254
|
+
else
|
|
255
|
+
[]
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
groups = groups.map do |g|
|
|
259
|
+
if g == "dev"
|
|
260
|
+
"develop"
|
|
261
|
+
else
|
|
262
|
+
(g == "main" ? "runtime" : g)
|
|
247
263
|
end
|
|
264
|
+
end
|
|
248
265
|
|
|
249
266
|
groups = ["runtime"] if groups.empty?
|
|
250
267
|
|
|
251
268
|
groups.each do |group|
|
|
252
|
-
# Poetry lockfiles should already contain
|
|
269
|
+
# Poetry lockfiles should already contain normalized names, but we'll
|
|
253
270
|
# apply it here as well just to be consistent with pyproject.toml parsing.
|
|
254
|
-
normalized_name = normalize_name(
|
|
271
|
+
normalized_name = normalize_name(name)
|
|
255
272
|
deps << Dependency.new(
|
|
256
273
|
name: normalized_name,
|
|
257
|
-
original_name: normalized_name ==
|
|
258
|
-
requirement:
|
|
274
|
+
original_name: normalized_name == name ? nil : name,
|
|
275
|
+
requirement: version,
|
|
259
276
|
type: group,
|
|
260
277
|
source: options.fetch(:filename, nil),
|
|
261
278
|
platform: platform_name
|
|
@@ -314,7 +331,8 @@ module Bibliothecary
|
|
|
314
331
|
# Invalid lines in requirements.txt are skipped.
|
|
315
332
|
def self.parse_requirements_txt(file_contents, options: {})
|
|
316
333
|
deps = []
|
|
317
|
-
|
|
334
|
+
source = options.fetch(:filename, nil)
|
|
335
|
+
type = case source
|
|
318
336
|
when /dev/ || /docs/ || /tools/
|
|
319
337
|
"development"
|
|
320
338
|
when /test/
|
|
@@ -323,21 +341,22 @@ module Bibliothecary
|
|
|
323
341
|
"runtime"
|
|
324
342
|
end
|
|
325
343
|
|
|
326
|
-
file_contents.
|
|
327
|
-
|
|
344
|
+
file_contents.each_line do |line|
|
|
345
|
+
line = line.chomp
|
|
346
|
+
if line.include?("://")
|
|
328
347
|
begin
|
|
329
|
-
result = parse_requirements_txt_url(line, type,
|
|
348
|
+
result = parse_requirements_txt_url(line, type, source)
|
|
330
349
|
rescue URI::Error, NoEggSpecified
|
|
331
350
|
next
|
|
332
351
|
end
|
|
333
352
|
|
|
334
353
|
deps << result
|
|
335
|
-
elsif (match = line.
|
|
354
|
+
elsif (match = line.tr(" ", "").match(REQUIREMENTS_REGEXP))
|
|
336
355
|
deps << Dependency.new(
|
|
337
356
|
name: match[1],
|
|
338
357
|
requirement: match[-1],
|
|
339
358
|
type: type,
|
|
340
|
-
source:
|
|
359
|
+
source: source,
|
|
341
360
|
platform: platform_name
|
|
342
361
|
)
|
|
343
362
|
end
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "bundler"
|
|
4
|
-
require "gemnasium/parser"
|
|
5
4
|
|
|
6
5
|
module Bibliothecary
|
|
7
6
|
module Parsers
|
|
8
7
|
class Rubygems
|
|
9
8
|
include Bibliothecary::Analyser
|
|
10
|
-
extend Bibliothecary::MultiParsers::BundlerLikeManifest
|
|
11
9
|
|
|
12
10
|
NAME_VERSION = '(?! )(.*?)(?: \(([^-]*)(?:-(.*))?\))?'
|
|
13
11
|
NAME_VERSION_4 = /^ {4}#{NAME_VERSION}$/
|
|
14
12
|
BUNDLED_WITH = /BUNDLED WITH/
|
|
15
13
|
|
|
14
|
+
# Gemfile patterns
|
|
15
|
+
GEM_REGEXP = /^\s*gem\s+['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"])?/
|
|
16
|
+
GROUP_START = /^\s*group\s+(.+?)\s+do/
|
|
17
|
+
BLOCK_END = /^\s*end\s*$/
|
|
18
|
+
|
|
19
|
+
# Gemspec pattern - captures type in first group
|
|
20
|
+
GEMSPEC_DEPENDENCY = /\.add_(development_|runtime_)?dependency\s*\(?\s*['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"])?(?:\s*,\s*['"]([^'"]+)['"])?\s*\)?/
|
|
21
|
+
|
|
16
22
|
def self.mapping
|
|
17
23
|
{
|
|
18
24
|
match_filenames("Gemfile", "gems.rb") => {
|
|
@@ -33,55 +39,114 @@ module Bibliothecary
|
|
|
33
39
|
}
|
|
34
40
|
end
|
|
35
41
|
|
|
36
|
-
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
37
|
-
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
38
|
-
add_multi_parser(Bibliothecary::MultiParsers::Spdx)
|
|
39
42
|
|
|
40
43
|
def self.parse_gemfile_lock(file_contents, options: {})
|
|
41
|
-
lockfile = Bundler::LockfileParser.new(file_contents)
|
|
42
44
|
source = options.fetch(:filename, nil)
|
|
45
|
+
dependencies = []
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
source: source
|
|
51
|
-
)
|
|
52
|
-
end
|
|
47
|
+
file_contents.each_line do |line|
|
|
48
|
+
line = line.chomp.gsub(/\r$/, "")
|
|
49
|
+
next unless (match = line.match(NAME_VERSION_4))
|
|
50
|
+
|
|
51
|
+
name, version, _platform = match.captures
|
|
52
|
+
next if name.nil? || name.empty?
|
|
53
53
|
|
|
54
|
-
bundler_version = lockfile.bundler_version
|
|
55
|
-
if bundler_version
|
|
56
54
|
dependencies << Dependency.new(
|
|
57
55
|
platform: platform_name,
|
|
58
|
-
name:
|
|
59
|
-
requirement:
|
|
56
|
+
name: name,
|
|
57
|
+
requirement: version,
|
|
60
58
|
type: "runtime",
|
|
61
59
|
source: source
|
|
62
60
|
)
|
|
63
61
|
end
|
|
64
62
|
|
|
63
|
+
if (bundler_dep = parse_bundler(file_contents, source))
|
|
64
|
+
dependencies << bundler_dep
|
|
65
|
+
end
|
|
66
|
+
|
|
65
67
|
ParserResult.new(dependencies: dependencies)
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
def self.parse_gemfile(file_contents, options: {})
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
source = options.fetch(:filename, nil)
|
|
72
|
+
deps = []
|
|
73
|
+
current_type = "runtime"
|
|
74
|
+
block_depth = 0
|
|
75
|
+
|
|
76
|
+
file_contents.each_line do |line|
|
|
77
|
+
# Track group blocks
|
|
78
|
+
if (group_match = line.match(GROUP_START))
|
|
79
|
+
block_depth += 1
|
|
80
|
+
groups = group_match[1]
|
|
81
|
+
current_type = groups.include?(":development") ? "development" : "runtime"
|
|
82
|
+
next
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if line.match?(BLOCK_END) && block_depth > 0
|
|
86
|
+
block_depth -= 1
|
|
87
|
+
current_type = "runtime" if block_depth == 0
|
|
88
|
+
next
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Match gem declarations
|
|
92
|
+
if (match = line.match(GEM_REGEXP))
|
|
93
|
+
name = match[1]
|
|
94
|
+
version = match[2]
|
|
95
|
+
requirement = version ? "= #{version}" : ">= 0"
|
|
96
|
+
|
|
97
|
+
deps << Dependency.new(
|
|
98
|
+
platform: platform_name,
|
|
99
|
+
name: name,
|
|
100
|
+
requirement: requirement,
|
|
101
|
+
type: current_type,
|
|
102
|
+
source: source
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
ParserResult.new(dependencies: deps)
|
|
72
108
|
end
|
|
73
109
|
|
|
74
110
|
def self.parse_gemspec(file_contents, options: {})
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
111
|
+
source = options.fetch(:filename, nil)
|
|
112
|
+
deps = []
|
|
113
|
+
|
|
114
|
+
file_contents.each_line do |line|
|
|
115
|
+
match = line.match(GEMSPEC_DEPENDENCY)
|
|
116
|
+
next unless match
|
|
117
|
+
|
|
118
|
+
type_prefix, name, ver1, ver2 = match.captures
|
|
119
|
+
type = type_prefix == "development_" ? "development" : "runtime"
|
|
120
|
+
requirement = build_requirement(ver1, ver2)
|
|
121
|
+
|
|
122
|
+
deps << Dependency.new(
|
|
123
|
+
platform: platform_name,
|
|
124
|
+
name: name,
|
|
125
|
+
requirement: requirement,
|
|
126
|
+
type: type,
|
|
127
|
+
source: source
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
ParserResult.new(dependencies: deps)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def self.build_requirement(ver1, ver2)
|
|
135
|
+
if ver1 && ver2
|
|
136
|
+
"#{ver1}, #{ver2}"
|
|
137
|
+
elsif ver1
|
|
138
|
+
ver1
|
|
139
|
+
else
|
|
140
|
+
">= 0"
|
|
141
|
+
end
|
|
78
142
|
end
|
|
79
143
|
|
|
80
144
|
def self.parse_bundler(file_contents, source = nil)
|
|
81
145
|
bundled_with_index = file_contents.lines(chomp: true).find_index { |line| line.match(BUNDLED_WITH) }
|
|
82
|
-
|
|
146
|
+
return nil unless bundled_with_index
|
|
83
147
|
|
|
84
|
-
|
|
148
|
+
version = file_contents.lines(chomp: true).fetch(bundled_with_index + 1, nil)&.strip
|
|
149
|
+
return nil unless version && !version.empty?
|
|
85
150
|
|
|
86
151
|
Dependency.new(
|
|
87
152
|
name: "bundler",
|
|
@@ -1,75 +1,123 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
1
3
|
module Bibliothecary
|
|
2
4
|
module Parsers
|
|
3
5
|
class SwiftPM
|
|
4
6
|
include Bibliothecary::Analyser
|
|
5
7
|
|
|
8
|
+
# Matches .Package(url: "...", majorVersion: X, minor: Y)
|
|
9
|
+
# Also matches .package(url: "...", from: "X.Y.Z") (Swift 4+)
|
|
10
|
+
PACKAGE_REGEXP_LEGACY = /\.Package\s*\(\s*url:\s*"([^"]+)"[^)]*majorVersion:\s*(\d+)[^)]*minor:\s*(\d+)/
|
|
11
|
+
PACKAGE_REGEXP_FROM = /\.package\s*\(\s*(?:name:\s*"[^"]+",\s*)?url:\s*"([^"]+)"[^)]*from:\s*"([^"]+)"/i
|
|
12
|
+
PACKAGE_REGEXP_EXACT = /\.package\s*\(\s*(?:name:\s*"[^"]+",\s*)?url:\s*"([^"]+)"[^)]*(?:\.exact|exact)\s*\(\s*"([^"]+)"\s*\)/i
|
|
13
|
+
PACKAGE_REGEXP_RANGE = /\.package\s*\(\s*(?:name:\s*"[^"]+",\s*)?url:\s*"([^"]+)"[^)]*"([^"]+)"\s*(?:\.\.|\.\.\.)\s*"([^"]+)"/i
|
|
14
|
+
|
|
6
15
|
def self.mapping
|
|
7
16
|
{
|
|
8
17
|
match_filename("Package.swift", case_insensitive: true) => {
|
|
9
|
-
kind:
|
|
18
|
+
kind: "manifest",
|
|
10
19
|
parser: :parse_package_swift,
|
|
11
|
-
related_to: [
|
|
20
|
+
related_to: ["lockfile"],
|
|
12
21
|
},
|
|
13
22
|
match_filename("Package.resolved", case_insensitive: true) => {
|
|
14
|
-
kind:
|
|
23
|
+
kind: "lockfile",
|
|
15
24
|
parser: :parse_package_resolved,
|
|
16
|
-
related_to: [
|
|
17
|
-
}
|
|
25
|
+
related_to: ["manifest"],
|
|
26
|
+
},
|
|
18
27
|
}
|
|
19
28
|
end
|
|
20
29
|
|
|
21
|
-
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
22
|
-
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
23
|
-
add_multi_parser(Bibliothecary::MultiParsers::Spdx)
|
|
24
30
|
|
|
25
31
|
def self.parse_package_swift(file_contents, options: {})
|
|
26
|
-
source = options.fetch(:filename,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
source = options.fetch(:filename, "Package.swift")
|
|
33
|
+
deps = []
|
|
34
|
+
|
|
35
|
+
# Remove comments (but not :// in URLs)
|
|
36
|
+
content = file_contents.gsub(%r{(?<!:)//.*$}, "")
|
|
37
|
+
|
|
38
|
+
# Legacy format: .Package(url: "...", majorVersion: X, minor: Y)
|
|
39
|
+
content.scan(PACKAGE_REGEXP_LEGACY) do |url, major, minor|
|
|
40
|
+
name = url.gsub(%r{^https?://}, "").gsub(/\.git$/, "")
|
|
41
|
+
# Match the remote parser format: lowerBound - upperBound
|
|
42
|
+
lower = "#{major}.#{minor}.0"
|
|
43
|
+
upper = "#{major}.#{minor}.9223372036854775807"
|
|
44
|
+
deps << Dependency.new(
|
|
45
|
+
platform: platform_name,
|
|
46
|
+
name: name,
|
|
47
|
+
requirement: "#{lower} - #{upper}",
|
|
48
|
+
type: "runtime",
|
|
49
|
+
source: source
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Swift 4+ format: .package(url: "...", from: "X.Y.Z")
|
|
54
|
+
content.scan(PACKAGE_REGEXP_FROM) do |url, version|
|
|
55
|
+
name = url.gsub(%r{^https?://}, "").gsub(/\.git$/, "")
|
|
56
|
+
deps << Dependency.new(
|
|
34
57
|
platform: platform_name,
|
|
35
58
|
name: name,
|
|
36
|
-
requirement: version,
|
|
59
|
+
requirement: ">= #{version}",
|
|
37
60
|
type: "runtime",
|
|
38
61
|
source: source
|
|
39
62
|
)
|
|
40
63
|
end
|
|
41
|
-
|
|
64
|
+
|
|
65
|
+
# Swift 4+ exact version: .package(url: "...", .exact("X.Y.Z"))
|
|
66
|
+
content.scan(PACKAGE_REGEXP_EXACT) do |url, version|
|
|
67
|
+
name = url.gsub(%r{^https?://}, "").gsub(/\.git$/, "")
|
|
68
|
+
deps << Dependency.new(
|
|
69
|
+
platform: platform_name,
|
|
70
|
+
name: name,
|
|
71
|
+
requirement: "= #{version}",
|
|
72
|
+
type: "runtime",
|
|
73
|
+
source: source
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Swift 4+ range: .package(url: "...", "1.0.0"..<"2.0.0")
|
|
78
|
+
content.scan(PACKAGE_REGEXP_RANGE) do |url, lower, upper|
|
|
79
|
+
name = url.gsub(%r{^https?://}, "").gsub(/\.git$/, "")
|
|
80
|
+
deps << Dependency.new(
|
|
81
|
+
platform: platform_name,
|
|
82
|
+
name: name,
|
|
83
|
+
requirement: "#{lower} - #{upper}",
|
|
84
|
+
type: "runtime",
|
|
85
|
+
source: source
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
ParserResult.new(dependencies: deps)
|
|
42
90
|
end
|
|
43
91
|
|
|
44
92
|
def self.parse_package_resolved(file_contents, options: {})
|
|
45
|
-
source = options.fetch(:filename,
|
|
93
|
+
source = options.fetch(:filename, "Package.resolved")
|
|
46
94
|
json = JSON.parse(file_contents)
|
|
47
95
|
deps = if json["version"] == 1
|
|
48
96
|
json["object"]["pins"].map do |dependency|
|
|
49
|
-
name = dependency[
|
|
50
|
-
version = dependency[
|
|
51
|
-
|
|
97
|
+
name = dependency["repositoryURL"].gsub(%r{^https?://}, "").gsub(/\.git$/, "")
|
|
98
|
+
version = dependency["state"]["version"]
|
|
99
|
+
Dependency.new(
|
|
52
100
|
platform: platform_name,
|
|
53
101
|
name: name,
|
|
54
102
|
requirement: version,
|
|
55
|
-
type:
|
|
103
|
+
type: "runtime",
|
|
56
104
|
source: source
|
|
57
105
|
)
|
|
58
106
|
end
|
|
59
|
-
else # version 2
|
|
107
|
+
else # version 2+
|
|
60
108
|
json["pins"].map do |dependency|
|
|
61
|
-
name = dependency[
|
|
62
|
-
version = dependency[
|
|
63
|
-
|
|
109
|
+
name = dependency["location"].gsub(%r{^https?://}, "").gsub(/\.git$/, "")
|
|
110
|
+
version = dependency["state"]["version"]
|
|
111
|
+
Dependency.new(
|
|
64
112
|
platform: platform_name,
|
|
65
113
|
name: name,
|
|
66
114
|
requirement: version,
|
|
67
|
-
type:
|
|
115
|
+
type: "runtime",
|
|
68
116
|
source: source
|
|
69
117
|
)
|
|
70
118
|
end
|
|
71
119
|
end
|
|
72
|
-
|
|
120
|
+
ParserResult.new(dependencies: deps)
|
|
73
121
|
end
|
|
74
122
|
end
|
|
75
123
|
end
|