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
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
require "json"
|
|
2
|
-
require "deb_control"
|
|
3
2
|
|
|
4
3
|
module Bibliothecary
|
|
5
4
|
module Parsers
|
|
6
5
|
class Hackage
|
|
7
6
|
include Bibliothecary::Analyser
|
|
8
7
|
|
|
8
|
+
# Matches dependency lines like: aeson == 1.1.* or base >= 4.9 && < 4.11
|
|
9
|
+
# Package names can contain letters, numbers, and hyphens
|
|
10
|
+
DEPENDENCY_REGEXP = /^\s*([a-zA-Z][a-zA-Z0-9-]*)\s*((?:[<>=!]+\s*[\d.*]+(?:\s*&&\s*[<>=!]+\s*[\d.*]+)*)?)/
|
|
11
|
+
|
|
12
|
+
# Matches build-tool-depends format: package:tool == version
|
|
13
|
+
BUILD_TOOL_REGEXP = /^\s*([a-zA-Z][a-zA-Z0-9-]*):[a-zA-Z][a-zA-Z0-9-]*\s*((?:[<>=!]+\s*[\d.*]+(?:\s*&&\s*[<>=!]+\s*[\d.*]+)*)?)/
|
|
14
|
+
|
|
9
15
|
def self.mapping
|
|
10
16
|
{
|
|
11
17
|
match_extension(".cabal") => {
|
|
@@ -19,39 +25,140 @@ module Bibliothecary
|
|
|
19
25
|
}
|
|
20
26
|
end
|
|
21
27
|
|
|
22
|
-
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
23
|
-
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
24
|
-
add_multi_parser(Bibliothecary::MultiParsers::Spdx)
|
|
25
28
|
|
|
26
29
|
def self.parse_cabal(file_contents, options: {})
|
|
27
|
-
source = options.fetch(:filename,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
source = options.fetch(:filename, "package.cabal")
|
|
31
|
+
deps = []
|
|
32
|
+
|
|
33
|
+
# Track current section type
|
|
34
|
+
current_section = nil
|
|
35
|
+
in_build_depends = false
|
|
36
|
+
in_build_tool_depends = false
|
|
37
|
+
current_deps_buffer = []
|
|
38
|
+
|
|
39
|
+
file_contents.each_line do |line|
|
|
40
|
+
# Check for section headers (library, executable, test-suite, benchmark)
|
|
41
|
+
if line =~ /^(library|executable|test-suite|benchmark)\b/i
|
|
42
|
+
current_section = $1.downcase
|
|
43
|
+
in_build_depends = false
|
|
44
|
+
in_build_tool_depends = false
|
|
45
|
+
next
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Check for build-depends: or build-tool-depends: (can be at any indentation level)
|
|
49
|
+
if line =~ /^\s*build-depends\s*:/i
|
|
50
|
+
in_build_depends = true
|
|
51
|
+
in_build_tool_depends = false
|
|
52
|
+
# Extract deps from same line after colon
|
|
53
|
+
deps_part = line.sub(/^\s*build-depends\s*:/i, "")
|
|
54
|
+
parse_deps_line(deps_part, deps, current_section, "build-depends", source)
|
|
55
|
+
next
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if line =~ /^\s*build-tool-depends\s*:/i
|
|
59
|
+
in_build_tool_depends = true
|
|
60
|
+
in_build_depends = false
|
|
61
|
+
# Extract deps from same line after colon
|
|
62
|
+
deps_part = line.sub(/^\s*build-tool-depends\s*:/i, "")
|
|
63
|
+
parse_deps_line(deps_part, deps, current_section, "build-tool-depends", source)
|
|
64
|
+
next
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check for other field headers that end depends section
|
|
68
|
+
# Field headers are like "field-name:" but NOT "package:tool" (build-tool-depends format)
|
|
69
|
+
# Build-tool-depends entries have format: package:tool version-constraint
|
|
70
|
+
if line =~ /^\s*([a-z][a-z0-9-]*)\s*:/i
|
|
71
|
+
field_name = $1
|
|
72
|
+
# If this looks like a field header (not package:tool), end the depends section
|
|
73
|
+
unless line =~ /^\s*[a-z][a-z0-9-]*:[a-z][a-z0-9-]*\s+/i
|
|
74
|
+
in_build_depends = false
|
|
75
|
+
in_build_tool_depends = false
|
|
76
|
+
next
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Continue parsing dependencies if in a depends section and line is indented
|
|
81
|
+
if (in_build_depends || in_build_tool_depends) && line =~ /^\s+/
|
|
82
|
+
dep_type = in_build_tool_depends ? "build-tool-depends" : "build-depends"
|
|
83
|
+
parse_deps_line(line, deps, current_section, dep_type, source)
|
|
84
|
+
elsif line !~ /^\s/
|
|
85
|
+
# Non-indented line that's not a section header ends depends
|
|
86
|
+
in_build_depends = false
|
|
87
|
+
in_build_tool_depends = false
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
ParserResult.new(dependencies: deps)
|
|
92
|
+
end
|
|
31
93
|
|
|
32
|
-
|
|
94
|
+
def self.parse_deps_line(line, deps, section, dep_type, source)
|
|
95
|
+
# Split by comma and parse each dep
|
|
96
|
+
line.split(",").each do |dep_str|
|
|
97
|
+
dep_str = dep_str.strip
|
|
98
|
+
next if dep_str.empty?
|
|
33
99
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
100
|
+
# Use different regex for build-tool-depends (package:tool format)
|
|
101
|
+
regex = dep_type == "build-tool-depends" ? BUILD_TOOL_REGEXP : DEPENDENCY_REGEXP
|
|
102
|
+
match = dep_str.match(regex)
|
|
103
|
+
next unless match
|
|
104
|
+
|
|
105
|
+
name = match[1]
|
|
106
|
+
requirement = match[2]&.strip
|
|
107
|
+
requirement = "*" if requirement.nil? || requirement.empty?
|
|
108
|
+
# Normalize spacing: "== 1.1.*" -> "==1.1.*", ">= 4.9 && < 4.11" -> ">=4.9 && <4.11"
|
|
109
|
+
requirement = requirement.gsub(/([<>=!]+)\s+/, '\1').gsub(/\s+(&&)\s+/, ' \1 ')
|
|
110
|
+
|
|
111
|
+
# Determine type based on section and dep_type
|
|
112
|
+
type = determine_dep_type(section, dep_type)
|
|
113
|
+
|
|
114
|
+
deps << Dependency.new(
|
|
38
115
|
platform: platform_name,
|
|
39
|
-
name:
|
|
40
|
-
requirement:
|
|
41
|
-
type:
|
|
116
|
+
name: name,
|
|
117
|
+
requirement: requirement,
|
|
118
|
+
type: type,
|
|
42
119
|
source: source
|
|
43
120
|
)
|
|
44
121
|
end
|
|
45
|
-
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def self.determine_dep_type(section, dep_type)
|
|
125
|
+
if dep_type == "build-tool-depends"
|
|
126
|
+
"build"
|
|
127
|
+
elsif section == "test-suite"
|
|
128
|
+
"test"
|
|
129
|
+
elsif section == "benchmark"
|
|
130
|
+
"benchmark"
|
|
131
|
+
else
|
|
132
|
+
"runtime"
|
|
133
|
+
end
|
|
46
134
|
end
|
|
47
135
|
|
|
48
136
|
def self.parse_cabal_config(file_contents, options: {})
|
|
49
|
-
source = options.fetch(:filename,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
137
|
+
source = options.fetch(:filename, "cabal.config")
|
|
138
|
+
deps = []
|
|
139
|
+
|
|
140
|
+
# Parse RFC822-style format: constraints: pkg1 ==1.0, pkg2 ==2.0, ...
|
|
141
|
+
# Values can span multiple lines (continuation lines start with whitespace)
|
|
142
|
+
constraints = nil
|
|
143
|
+
file_contents.each_line do |line|
|
|
144
|
+
if line =~ /^constraints:\s*(.*)/i
|
|
145
|
+
constraints = $1.strip
|
|
146
|
+
elsif line =~ /^\s+(.*)/ && constraints
|
|
147
|
+
constraints += " " + $1.strip
|
|
148
|
+
elsif line =~ /^[a-z]/i && constraints
|
|
149
|
+
break
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
return ParserResult.new(dependencies: []) unless constraints
|
|
154
|
+
|
|
155
|
+
constraints.split(",").each do |dep_str|
|
|
156
|
+
dep_str = dep_str.strip
|
|
157
|
+
next if dep_str.empty?
|
|
158
|
+
|
|
159
|
+
# Format: package ==version or package ==version.*
|
|
160
|
+
dep = dep_str.delete("==").split(/\s+/)
|
|
161
|
+
deps << Dependency.new(
|
|
55
162
|
platform: platform_name,
|
|
56
163
|
name: dep[0],
|
|
57
164
|
requirement: dep[1] || "*",
|
|
@@ -59,7 +166,8 @@ module Bibliothecary
|
|
|
59
166
|
source: source
|
|
60
167
|
)
|
|
61
168
|
end
|
|
62
|
-
|
|
169
|
+
|
|
170
|
+
ParserResult.new(dependencies: deps)
|
|
63
171
|
end
|
|
64
172
|
end
|
|
65
173
|
end
|
|
@@ -6,19 +6,29 @@ module Bibliothecary
|
|
|
6
6
|
module Parsers
|
|
7
7
|
class Haxelib
|
|
8
8
|
include Bibliothecary::Analyser
|
|
9
|
-
extend Bibliothecary::MultiParsers::JSONRuntime
|
|
10
9
|
|
|
11
10
|
def self.mapping
|
|
12
11
|
{
|
|
13
12
|
match_filename("haxelib.json") => {
|
|
14
13
|
kind: "manifest",
|
|
15
|
-
parser: :
|
|
14
|
+
parser: :parse_manifest,
|
|
16
15
|
},
|
|
17
16
|
}
|
|
18
17
|
end
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
def self.parse_manifest(file_contents, options: {})
|
|
20
|
+
manifest = JSON.parse(file_contents)
|
|
21
|
+
dependencies = manifest.fetch("dependencies", {}).map do |name, requirement|
|
|
22
|
+
Dependency.new(
|
|
23
|
+
name: name,
|
|
24
|
+
requirement: requirement,
|
|
25
|
+
type: "runtime",
|
|
26
|
+
source: options.fetch(:filename, nil),
|
|
27
|
+
platform: platform_name
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
ParserResult.new(dependencies: dependencies)
|
|
31
|
+
end
|
|
22
32
|
end
|
|
23
33
|
end
|
|
24
34
|
end
|
|
@@ -5,6 +5,11 @@ module Bibliothecary
|
|
|
5
5
|
class Hex
|
|
6
6
|
include Bibliothecary::Analyser
|
|
7
7
|
|
|
8
|
+
# Matches mix.lock entries: "name": {:hex, :name, "version", ...
|
|
9
|
+
# or "name": {:git, "url", "ref", ...
|
|
10
|
+
HEX_LOCK_REGEXP = /"([^"]+)":\s*\{:hex,\s*:[^,]+,\s*"([^"]+)"/
|
|
11
|
+
GIT_LOCK_REGEXP = /"([^"]+)":\s*\{:git,\s*"([^"]+)",\s*"([^"]+)"/
|
|
12
|
+
|
|
8
13
|
def self.mapping
|
|
9
14
|
{
|
|
10
15
|
match_filename("mix.exs") => {
|
|
@@ -18,44 +23,56 @@ module Bibliothecary
|
|
|
18
23
|
}
|
|
19
24
|
end
|
|
20
25
|
|
|
21
|
-
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
22
|
-
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
23
|
-
add_multi_parser(Bibliothecary::MultiParsers::Spdx)
|
|
24
26
|
|
|
25
27
|
def self.parse_mix(file_contents, options: {})
|
|
26
|
-
source = options.fetch(:filename,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
source = options.fetch(:filename, "mix.exs")
|
|
29
|
+
deps = []
|
|
30
|
+
|
|
31
|
+
# Remove comments before parsing
|
|
32
|
+
content = file_contents.gsub(/#.*$/, "")
|
|
30
33
|
|
|
31
|
-
deps
|
|
32
|
-
|
|
34
|
+
# Match deps in the dependencies list: {:name, "~> version"} or {:name, ">= version"}
|
|
35
|
+
# Format: {:dep_name, "requirement"} or {:dep_name, "requirement", opts}
|
|
36
|
+
content.scan(/\{:(\w+),\s*"([^"]+)"/) do |name, requirement|
|
|
37
|
+
deps << Dependency.new(
|
|
33
38
|
platform: platform_name,
|
|
34
|
-
name: name,
|
|
35
|
-
requirement:
|
|
39
|
+
name: name.to_s,
|
|
40
|
+
requirement: requirement,
|
|
36
41
|
type: "runtime",
|
|
37
42
|
source: source
|
|
38
43
|
)
|
|
39
44
|
end
|
|
40
|
-
|
|
45
|
+
|
|
46
|
+
ParserResult.new(dependencies: deps)
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
def self.parse_mix_lock(file_contents, options: {})
|
|
44
|
-
source = options.fetch(:filename,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
source = options.fetch(:filename, "mix.lock")
|
|
51
|
+
deps = []
|
|
52
|
+
|
|
53
|
+
# Match hex packages: "name": {:hex, :name, "version", ...
|
|
54
|
+
file_contents.scan(HEX_LOCK_REGEXP) do |name, version|
|
|
55
|
+
deps << Dependency.new(
|
|
56
|
+
platform: platform_name,
|
|
57
|
+
name: name,
|
|
58
|
+
requirement: version,
|
|
59
|
+
type: "runtime",
|
|
60
|
+
source: source
|
|
61
|
+
)
|
|
62
|
+
end
|
|
48
63
|
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
# Match git packages: "name": {:git, "url", "ref", ...
|
|
65
|
+
file_contents.scan(GIT_LOCK_REGEXP) do |name, _url, ref|
|
|
66
|
+
deps << Dependency.new(
|
|
51
67
|
platform: platform_name,
|
|
52
68
|
name: name,
|
|
53
|
-
requirement:
|
|
69
|
+
requirement: ref,
|
|
54
70
|
type: "runtime",
|
|
55
71
|
source: source
|
|
56
72
|
)
|
|
57
73
|
end
|
|
58
|
-
|
|
74
|
+
|
|
75
|
+
ParserResult.new(dependencies: deps)
|
|
59
76
|
end
|
|
60
77
|
end
|
|
61
78
|
end
|
|
@@ -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_brewfile(file_contents, options: {})
|
|
27
25
|
source = options.fetch(:filename, 'Brewfile')
|
|
@@ -133,9 +133,6 @@ module Bibliothecary
|
|
|
133
133
|
}
|
|
134
134
|
end
|
|
135
135
|
|
|
136
|
-
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
137
|
-
add_multi_parser(Bibliothecary::MultiParsers::Spdx)
|
|
138
|
-
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
139
136
|
|
|
140
137
|
def self.parse_ivy_manifest(file_contents, options: {})
|
|
141
138
|
manifest = Ox.parse file_contents
|
|
@@ -195,15 +192,22 @@ module Bibliothecary
|
|
|
195
192
|
|
|
196
193
|
def self.parse_gradle_resolved(file_contents, options: {})
|
|
197
194
|
current_type = nil
|
|
195
|
+
source = options.fetch(:filename, nil)
|
|
196
|
+
dependencies = []
|
|
198
197
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
current_type = current_type_match.captures[0] if current_type_match
|
|
198
|
+
file_contents.each_line do |line|
|
|
199
|
+
line = line.chomp
|
|
202
200
|
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
# Check if this is a type header line (starts with word character)
|
|
202
|
+
# e.g. "annotationProcessor - Annotation processors..."
|
|
203
|
+
if line[0] =~ /\w/
|
|
204
|
+
current_type = line[GRADLE_TYPE_REGEXP, 1]
|
|
205
|
+
next
|
|
206
|
+
end
|
|
205
207
|
|
|
206
|
-
|
|
208
|
+
# Check for dependency line (contains +--- or \---)
|
|
209
|
+
split_match = line[GRADLE_DEP_REGEXP, 1]
|
|
210
|
+
next unless split_match
|
|
207
211
|
|
|
208
212
|
# gradle can import on-disk projects and deps will be listed under them, e.g. `+--- project :test:integration`,
|
|
209
213
|
# so we treat these projects as "internal" deps with requirement of "1.0.0"
|
|
@@ -217,7 +221,7 @@ module Bibliothecary
|
|
|
217
221
|
end
|
|
218
222
|
|
|
219
223
|
dep = line
|
|
220
|
-
.split(
|
|
224
|
+
.split(split_match)[1]
|
|
221
225
|
.sub(GRADLE_LINE_ENDING_REGEXP, "")
|
|
222
226
|
.sub(/ FAILED$/, "") # dependency could not be resolved (but still may have a version)
|
|
223
227
|
.sub(" -> ", ":") # handle version arrow syntax
|
|
@@ -230,45 +234,50 @@ module Bibliothecary
|
|
|
230
234
|
|
|
231
235
|
if dep.count == 6
|
|
232
236
|
# get name from renamed package resolution "org:name:version -> renamed_org:name:version"
|
|
233
|
-
Dependency.new(
|
|
237
|
+
dependencies << Dependency.new(
|
|
234
238
|
original_name: dep[0, 2].join(":"),
|
|
235
239
|
original_requirement: dep[2],
|
|
236
240
|
name: dep[-3..-2].join(":"),
|
|
237
241
|
requirement: dep[-1],
|
|
238
242
|
type: current_type,
|
|
239
|
-
source:
|
|
243
|
+
source: source,
|
|
240
244
|
platform: platform_name
|
|
241
245
|
)
|
|
242
246
|
elsif dep.count == 5
|
|
243
247
|
# get name from renamed package resolution "org:name -> renamed_org:name:version"
|
|
244
|
-
Dependency.new(
|
|
248
|
+
dependencies << Dependency.new(
|
|
245
249
|
original_name: dep[0, 2].join(":"),
|
|
246
250
|
original_requirement: "*",
|
|
247
251
|
name: dep[-3..-2].join(":"),
|
|
248
252
|
requirement: dep[-1],
|
|
249
253
|
type: current_type,
|
|
250
|
-
source:
|
|
254
|
+
source: source,
|
|
251
255
|
platform: platform_name
|
|
252
256
|
)
|
|
253
257
|
else
|
|
254
258
|
# get name from version conflict resolution ("org:name:version -> version") and no-resolution ("org:name:version")
|
|
255
|
-
Dependency.new(
|
|
259
|
+
dependencies << Dependency.new(
|
|
256
260
|
name: dep[0..1].join(":"),
|
|
257
261
|
requirement: dep[-1],
|
|
258
262
|
type: current_type,
|
|
259
|
-
source:
|
|
263
|
+
source: source,
|
|
260
264
|
platform: platform_name
|
|
261
265
|
)
|
|
262
266
|
end
|
|
263
267
|
end
|
|
264
|
-
|
|
265
|
-
|
|
268
|
+
|
|
269
|
+
dependencies.uniq! { |item| [item.name, item.requirement, item.type, item.original_name, item.original_requirement] }
|
|
266
270
|
ParserResult.new(dependencies: dependencies)
|
|
267
271
|
end
|
|
268
272
|
|
|
273
|
+
def self.strip_ansi(string)
|
|
274
|
+
return string unless string.include?("\033")
|
|
275
|
+
|
|
276
|
+
string.gsub(ANSI_MATCHER, "")
|
|
277
|
+
end
|
|
278
|
+
|
|
269
279
|
def self.parse_maven_resolved(file_contents, options: {})
|
|
270
|
-
dependencies = file_contents
|
|
271
|
-
.gsub(ANSI_MATCHER, "")
|
|
280
|
+
dependencies = strip_ansi(file_contents)
|
|
272
281
|
.split("\n")
|
|
273
282
|
.map { |line| parse_resolved_dep_line(line, options: options) }
|
|
274
283
|
.compact
|
|
@@ -281,11 +290,12 @@ module Bibliothecary
|
|
|
281
290
|
# The depth-0 items are the (sub)project names
|
|
282
291
|
# These are in the original order, with no de-duplication.
|
|
283
292
|
def self.parse_maven_tree_items_with_depths(file_contents)
|
|
284
|
-
file_contents
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
293
|
+
content = strip_ansi(file_contents)
|
|
294
|
+
content = content.gsub("\r\n", "\n").gsub("\r", "\n") if content.include?("\r")
|
|
295
|
+
|
|
296
|
+
# capture two groups; one is the ASCII art telling us the tree depth,
|
|
297
|
+
# and two is the actual dependency
|
|
298
|
+
content
|
|
289
299
|
.scan(/^\[INFO\]\s((?:[-+|\\]|\s)*)((?:[\w.-]+:)+[\w.\-${}]+)/)
|
|
290
300
|
# lines that start with "-" aren't part of the tree, example: "[INFO] --- dependency:3.8.1:tree"
|
|
291
301
|
.reject { |(tree_ascii_art, _dep_info)| tree_ascii_art.start_with?("-") }
|
|
@@ -6,19 +6,29 @@ module Bibliothecary
|
|
|
6
6
|
module Parsers
|
|
7
7
|
class Meteor
|
|
8
8
|
include Bibliothecary::Analyser
|
|
9
|
-
extend Bibliothecary::MultiParsers::JSONRuntime
|
|
10
9
|
|
|
11
10
|
def self.mapping
|
|
12
11
|
{
|
|
13
12
|
match_filename("versions.json") => {
|
|
14
13
|
kind: "manifest",
|
|
15
|
-
parser: :
|
|
14
|
+
parser: :parse_manifest,
|
|
16
15
|
},
|
|
17
16
|
}
|
|
18
17
|
end
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
def self.parse_manifest(file_contents, options: {})
|
|
20
|
+
manifest = JSON.parse(file_contents)
|
|
21
|
+
dependencies = manifest.fetch("dependencies", {}).map do |name, requirement|
|
|
22
|
+
Dependency.new(
|
|
23
|
+
name: name,
|
|
24
|
+
requirement: requirement,
|
|
25
|
+
type: "runtime",
|
|
26
|
+
source: options.fetch(:filename, nil),
|
|
27
|
+
platform: platform_name
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
ParserResult.new(dependencies: dependencies)
|
|
31
|
+
end
|
|
22
32
|
end
|
|
23
33
|
end
|
|
24
34
|
end
|
|
@@ -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_mlmodel(file_contents, options: {})
|
|
22
20
|
source = options.fetch(:filename, 'MLmodel')
|
|
@@ -43,9 +43,6 @@ module Bibliothecary
|
|
|
43
43
|
}
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
47
|
-
add_multi_parser(Bibliothecary::MultiParsers::Spdx)
|
|
48
|
-
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
49
46
|
|
|
50
47
|
def self.parse_package_lock(file_contents, options: {})
|
|
51
48
|
manifest = JSON.parse(file_contents)
|
|
@@ -203,65 +200,56 @@ module Bibliothecary
|
|
|
203
200
|
# version: "1.2.0",
|
|
204
201
|
# }, ...]
|
|
205
202
|
def self.parse_v1_yarn_lock(contents, source = nil)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
source: source,
|
|
231
|
-
}
|
|
232
|
-
end
|
|
203
|
+
deps = []
|
|
204
|
+
# Normalize line endings only if needed
|
|
205
|
+
contents = contents.gsub("\r\n", "\n").gsub("\r", "\n") if contents.include?("\r")
|
|
206
|
+
|
|
207
|
+
# Match package blocks: header line(s) ending with ":" followed by version line
|
|
208
|
+
# Header examples: 'package@version:' or '"package@version", "package@version2":'
|
|
209
|
+
contents.scan(/^([^\s#][^\n]*?):\s*\r?\n\s+version "?([^"\n]+)"?/m) do |header, version|
|
|
210
|
+
# Parse requirements from header (remove quotes and trailing colon)
|
|
211
|
+
requirements = header.gsub(/"/, "").split(",").map(&:strip)
|
|
212
|
+
|
|
213
|
+
name, alias_name = yarn_strip_npm_protocol(requirements.first)
|
|
214
|
+
name = name.strip.split(/(?<!^)@/).first
|
|
215
|
+
req_versions = requirements.map { |d| d.strip.split(/(?<!^)@/, 2) }
|
|
216
|
+
|
|
217
|
+
deps << {
|
|
218
|
+
name: name,
|
|
219
|
+
original_name: alias_name,
|
|
220
|
+
requirements: req_versions.map { |x| x[1] },
|
|
221
|
+
original_requirement: alias_name.nil? ? nil : version,
|
|
222
|
+
version: version,
|
|
223
|
+
source: source,
|
|
224
|
+
}
|
|
225
|
+
end
|
|
226
|
+
deps
|
|
233
227
|
end
|
|
234
228
|
|
|
235
229
|
def self.parse_v2_yarn_lock(contents, source = nil)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
requirements: requirements,
|
|
260
|
-
original_requirement: alias_name.nil? ? nil : info["version"].to_s,
|
|
261
|
-
version: info["version"].to_s,
|
|
262
|
-
source: source,
|
|
263
|
-
}
|
|
264
|
-
end
|
|
230
|
+
deps = []
|
|
231
|
+
# Match package blocks: "package@npm:req": followed by version: x.y.z
|
|
232
|
+
# Examples: "js-tokens@npm:^3.0.0 || ^4.0.0": or "pkg1@npm:req1, pkg2@npm:req2":
|
|
233
|
+
contents.scan(/^"([^"]+)":\s*\n\s+version:\s*([^\n]+)/m) do |packages_str, version|
|
|
234
|
+
# Skip workspace/local packages and patches
|
|
235
|
+
next if version.include?("use.local") && packages_str.include?("workspace")
|
|
236
|
+
next if packages_str.include?("@patch:")
|
|
237
|
+
|
|
238
|
+
packages = packages_str.split(", ")
|
|
239
|
+
# use first requirement's name, assuming that deps will always resolve from deps of the same name
|
|
240
|
+
name, alias_name = yarn_strip_npm_protocol(packages.first.rpartition("@").first)
|
|
241
|
+
requirements = packages.map { |p| p.rpartition("@").last.gsub(/^.*:/, "") }
|
|
242
|
+
|
|
243
|
+
deps << {
|
|
244
|
+
name: name,
|
|
245
|
+
original_name: alias_name,
|
|
246
|
+
requirements: requirements,
|
|
247
|
+
original_requirement: alias_name.nil? ? nil : version.to_s,
|
|
248
|
+
version: version.to_s,
|
|
249
|
+
source: source,
|
|
250
|
+
}
|
|
251
|
+
end
|
|
252
|
+
deps
|
|
265
253
|
end
|
|
266
254
|
|
|
267
255
|
def self.parse_v5_pnpm_lock(parsed_contents, source = nil)
|