bibliothecary 8.2.0 → 8.2.3
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/lib/bibliothecary/multi_parsers/dependencies_csv.rb +151 -0
- data/lib/bibliothecary/parsers/bower.rb +2 -0
- data/lib/bibliothecary/parsers/cargo.rb +1 -0
- data/lib/bibliothecary/parsers/carthage.rb +2 -0
- data/lib/bibliothecary/parsers/clojars.rb +2 -0
- data/lib/bibliothecary/parsers/cocoapods.rb +2 -0
- data/lib/bibliothecary/parsers/conda.rb +1 -0
- data/lib/bibliothecary/parsers/cpan.rb +2 -0
- data/lib/bibliothecary/parsers/cran.rb +1 -0
- data/lib/bibliothecary/parsers/dub.rb +2 -0
- data/lib/bibliothecary/parsers/elm.rb +2 -0
- data/lib/bibliothecary/parsers/go.rb +1 -0
- data/lib/bibliothecary/parsers/hackage.rb +1 -0
- data/lib/bibliothecary/parsers/haxelib.rb +3 -0
- data/lib/bibliothecary/parsers/hex.rb +1 -0
- data/lib/bibliothecary/parsers/julia.rb +2 -0
- data/lib/bibliothecary/parsers/maven.rb +12 -11
- data/lib/bibliothecary/parsers/meteor.rb +2 -0
- data/lib/bibliothecary/parsers/npm.rb +1 -0
- data/lib/bibliothecary/parsers/nuget.rb +1 -0
- data/lib/bibliothecary/parsers/packagist.rb +1 -0
- data/lib/bibliothecary/parsers/pub.rb +2 -0
- data/lib/bibliothecary/parsers/pypi.rb +1 -0
- data/lib/bibliothecary/parsers/rubygems.rb +1 -0
- data/lib/bibliothecary/parsers/shard.rb +2 -0
- data/lib/bibliothecary/parsers/swift_pm.rb +1 -0
- data/lib/bibliothecary/related_files_info.rb +32 -8
- data/lib/bibliothecary/runner/multi_manifest_filter.rb +67 -0
- data/lib/bibliothecary/runner.rb +37 -12
- data/lib/bibliothecary/version.rb +1 -1
- data/lib/bibliothecary.rb +2 -0
- metadata +4 -3
- data/lib/bibliothecary/parsers/generic.rb +0 -39
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6bf3193b6daf685565ca3fe2105ca8be8e03c5e83d1edcafe3ca6d3c7be12d8b
|
|
4
|
+
data.tar.gz: c2b96281c11a89bf49f830bbb0be00e35d18096dcc8a8e8b2d0038e1256a6eed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3d45d37ac4c8e3c982d708f454906ab41db86fc285ba800d90acb6d2e541ae7151e7d09c5ae10eccec2678760b24f2bbb3d5c2e77048b714aa71c953395ce70
|
|
7
|
+
data.tar.gz: b86748a552b1774895cad46925205a7c4915907d336436c945a7dc027d80e8feff7d12465c9524ea12a3495d4635e45c4cd32489456453b4c744a22fdf7c5c32
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
require 'csv'
|
|
2
|
+
|
|
3
|
+
module Bibliothecary
|
|
4
|
+
module MultiParsers
|
|
5
|
+
module DependenciesCSV
|
|
6
|
+
include Bibliothecary::Analyser
|
|
7
|
+
include Bibliothecary::Analyser::TryCache
|
|
8
|
+
|
|
9
|
+
def self.mapping
|
|
10
|
+
{
|
|
11
|
+
match_filename('dependencies.csv') => {
|
|
12
|
+
kind: 'lockfile',
|
|
13
|
+
parser: :parse_dependencies_csv
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Processing a CSV file isn't as exact as using a real manifest file,
|
|
19
|
+
# but you can get pretty close as long as the data you're importing
|
|
20
|
+
# is simple.
|
|
21
|
+
class CSVFile
|
|
22
|
+
# Header structures are:
|
|
23
|
+
#
|
|
24
|
+
# <field to fill in for dependency> => {
|
|
25
|
+
# match: [<regexp of incoming column name to match in priority order, highest priority first>...],
|
|
26
|
+
# [default]: <optional default value for this field>
|
|
27
|
+
# }
|
|
28
|
+
HEADERS = {
|
|
29
|
+
"platform" => {
|
|
30
|
+
match: [
|
|
31
|
+
/^platform$/i
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"name" => {
|
|
35
|
+
match: [
|
|
36
|
+
/^name$/i
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
# Lockfiles have exact versions.
|
|
40
|
+
"lockfile_requirement" => {
|
|
41
|
+
match: [
|
|
42
|
+
/^(lockfile |)requirement$/i,
|
|
43
|
+
/^version$/i,
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
# Manifests have versions that can have operators.
|
|
47
|
+
# However, since Bibliothecary only currently supports analyzing a
|
|
48
|
+
# single file as a single thing (either manifest or lockfile)
|
|
49
|
+
# we can't return manifest-y data. Only take the lockfile requirement
|
|
50
|
+
# when processing dependencies.csv for now.
|
|
51
|
+
"requirement" => {
|
|
52
|
+
match: [
|
|
53
|
+
/^(lockfile |)requirement$/i,
|
|
54
|
+
/^version$/i,
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
"type" => {
|
|
58
|
+
default: "runtime",
|
|
59
|
+
match: [
|
|
60
|
+
/^(lockfile |)type$/i,
|
|
61
|
+
/^(manifest |)type$/i
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
attr_reader :result
|
|
67
|
+
|
|
68
|
+
def initialize(file_contents)
|
|
69
|
+
@file_contents = file_contents
|
|
70
|
+
|
|
71
|
+
@result = nil
|
|
72
|
+
|
|
73
|
+
# A Hash of "our field name" => ["header in CSV file", "lower priority header in CSV file"]
|
|
74
|
+
@header_mappings = {}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def parse!
|
|
78
|
+
table = parse_and_validate_csv_file
|
|
79
|
+
|
|
80
|
+
@result = table.map.with_index do |row, idx|
|
|
81
|
+
HEADERS.each_with_object({}) do |(header, info), obj|
|
|
82
|
+
# find the first non-empty field in the row for this header, or nil if not found
|
|
83
|
+
row_data = row[@header_mappings[header]]
|
|
84
|
+
|
|
85
|
+
# some column have default data to fall back on
|
|
86
|
+
if row_data
|
|
87
|
+
obj[header.to_sym] = row_data
|
|
88
|
+
elsif info.has_key?(:default)
|
|
89
|
+
# if the default is nil, don't even add the key to the hash
|
|
90
|
+
obj[header.to_sym] = info[:default] if info[:default]
|
|
91
|
+
else
|
|
92
|
+
# use 1-based index just like the 'csv' std lib, and count the headers as first row.
|
|
93
|
+
raise "Missing required field '#{header}' on line #{idx + 2}."
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def parse_and_validate_csv_file
|
|
102
|
+
table = CSV.parse(@file_contents, headers: true)
|
|
103
|
+
|
|
104
|
+
header_examination_results = map_table_headers_to_local_lookups(table, HEADERS)
|
|
105
|
+
unless header_examination_results[:missing].empty?
|
|
106
|
+
raise "Missing required headers #{header_examination_results[:missing].join(', ')} in CSV. Check to make sure header names are all lowercase."
|
|
107
|
+
end
|
|
108
|
+
@header_mappings = header_examination_results[:found]
|
|
109
|
+
|
|
110
|
+
table
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def map_table_headers_to_local_lookups(table, local_lookups)
|
|
114
|
+
result = local_lookups.each_with_object({ found: {}, missing: [] }) do |(header, info), obj|
|
|
115
|
+
results = table.headers.each_with_object([]) do |table_header, matches|
|
|
116
|
+
info[:match].each_with_index do |match_regexp, index|
|
|
117
|
+
matches << [table_header, index] if table_header[match_regexp]
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
if results.empty?
|
|
122
|
+
# if a header has a default value it's optional
|
|
123
|
+
obj[:missing] << header unless info.has_key?(:default)
|
|
124
|
+
else
|
|
125
|
+
# select the highest priority header possible
|
|
126
|
+
obj[:found][header] ||= nil
|
|
127
|
+
obj[:found][header] = ([obj[:found][header]] + results).compact.min_by(&:last)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# strip off the priorities. only one mapping should remain.
|
|
132
|
+
result[:found].transform_values!(&:first)
|
|
133
|
+
|
|
134
|
+
result
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def parse_dependencies_csv(file_contents, options: {})
|
|
139
|
+
csv_file = try_cache(options, options[:filename]) do
|
|
140
|
+
raw_csv_file = CSVFile.new(file_contents)
|
|
141
|
+
raw_csv_file.parse!
|
|
142
|
+
raw_csv_file
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
csv_file.result.find_all do |dependency|
|
|
146
|
+
dependency[:platform] == platform_name.to_s
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -15,6 +15,8 @@ module Bibliothecary
|
|
|
15
15
|
}
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
19
|
+
|
|
18
20
|
def self.parse_manifest(file_contents, options: {})
|
|
19
21
|
response = Typhoeus.post("#{Bibliothecary.configuration.clojars_parser_host}/project.clj", body: file_contents)
|
|
20
22
|
raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.clojars_parser_host}/project.clj", response.response_code) unless response.success?
|
|
@@ -27,6 +27,7 @@ module Bibliothecary
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
30
|
+
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
30
31
|
|
|
31
32
|
def self.parse_conda(file_contents, options: {})
|
|
32
33
|
parse_conda_with_kind(file_contents, "manifest")
|
|
@@ -17,6 +17,7 @@ module Bibliothecary
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
20
|
+
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
20
21
|
|
|
21
22
|
def self.parse_description(file_contents, options: {})
|
|
22
23
|
manifest = DebControl::ControlFileBase.parse(file_contents)
|
|
@@ -19,6 +19,7 @@ module Bibliothecary
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
22
|
+
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
22
23
|
|
|
23
24
|
def self.parse_mix(file_contents, options: {})
|
|
24
25
|
response = Typhoeus.post("#{Bibliothecary.configuration.mix_parser_host}/", body: file_contents)
|
|
@@ -18,10 +18,11 @@ module Bibliothecary
|
|
|
18
18
|
# An intentionally overly-simplified regex to scrape deps from build.gradle.kts files.
|
|
19
19
|
# To be truly useful bibliothecary would need a full Kotlin parser that speaks Gradle,
|
|
20
20
|
# because the Kotlin DSL has many dynamic ways of declaring dependencies.
|
|
21
|
-
GRADLE_KTS_SIMPLE_REGEX = /(#{GRADLE_KTS_DEPENDENCY_METHODS.join('|')})\s*\(\s*"([^"]+)"\s*\)/m
|
|
22
21
|
|
|
23
|
-
# e.g.
|
|
24
|
-
|
|
22
|
+
GRADLE_KTS_VERSION_REGEX = /[\w.-]+/ # e.g. '1.2.3'
|
|
23
|
+
GRADLE_KTS_INTERPOLATED_VERSION_REGEX = /\$\{.*\}/ # e.g. '${my-project-settings["version"]}'
|
|
24
|
+
GRADLE_KTS_GAV_REGEX = /([\w.-]+)\:([\w.-]+)(?:\:(#{GRADLE_KTS_VERSION_REGEX}|#{GRADLE_KTS_INTERPOLATED_VERSION_REGEX}))?/
|
|
25
|
+
GRADLE_KTS_SIMPLE_REGEX = /(#{GRADLE_KTS_DEPENDENCY_METHODS.join('|')})\s*\(\s*"#{GRADLE_KTS_GAV_REGEX}"\s*\)\s*$/m # e.g. "group:artifactId:1.2.3"
|
|
25
26
|
|
|
26
27
|
MAVEN_PROPERTY_REGEX = /\$\{(.+?)\}/
|
|
27
28
|
MAX_DEPTH = 5
|
|
@@ -83,7 +84,8 @@ module Bibliothecary
|
|
|
83
84
|
}
|
|
84
85
|
end
|
|
85
86
|
|
|
86
|
-
add_multi_parser
|
|
87
|
+
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
88
|
+
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
87
89
|
|
|
88
90
|
def self.parse_ivy_manifest(file_contents, options: {})
|
|
89
91
|
manifest = Ox.parse file_contents
|
|
@@ -248,14 +250,13 @@ module Bibliothecary
|
|
|
248
250
|
|
|
249
251
|
def self.parse_gradle_kts(file_contents, options: {})
|
|
250
252
|
file_contents
|
|
251
|
-
.scan(GRADLE_KTS_SIMPLE_REGEX)
|
|
252
|
-
.
|
|
253
|
-
.
|
|
254
|
-
.map { |gav_match|
|
|
253
|
+
.scan(GRADLE_KTS_SIMPLE_REGEX) # match 'implementation("group:artifactId:version")'
|
|
254
|
+
.reject { |(_type, group, artifactId, _version)| group.nil? || artifactId.nil? } # remove any matches with missing group/artifactId
|
|
255
|
+
.map { |(type, group, artifactId, version)|
|
|
255
256
|
{
|
|
256
|
-
name: [
|
|
257
|
-
requirement:
|
|
258
|
-
type:
|
|
257
|
+
name: [group, artifactId].join(":"),
|
|
258
|
+
requirement: version || "*",
|
|
259
|
+
type: type
|
|
259
260
|
}
|
|
260
261
|
}
|
|
261
262
|
end
|
|
@@ -45,6 +45,7 @@ module Bibliothecary
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
48
|
+
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
48
49
|
|
|
49
50
|
def self.parse_project_lock_json(file_contents, options: {})
|
|
50
51
|
manifest = JSON.parse file_contents
|
|
@@ -18,6 +18,8 @@ module Bibliothecary
|
|
|
18
18
|
}
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
22
|
+
|
|
21
23
|
def self.parse_yaml_manifest(file_contents, options: {})
|
|
22
24
|
manifest = YAML.load file_contents
|
|
23
25
|
map_dependencies(manifest, 'dependencies', 'runtime') +
|
|
@@ -30,6 +30,7 @@ module Bibliothecary
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
33
|
+
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
33
34
|
|
|
34
35
|
def self.parse_gemfile_lock(file_contents, options: {})
|
|
35
36
|
file_contents.lines(chomp: true).map do |line|
|
|
@@ -13,6 +13,7 @@ module Bibliothecary
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
|
16
|
+
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
|
16
17
|
|
|
17
18
|
def self.parse_package_swift(file_contents, options: {})
|
|
18
19
|
response = Typhoeus.post("#{Bibliothecary.configuration.swift_parser_host}/to-json", body: file_contents)
|
|
@@ -5,28 +5,52 @@ module Bibliothecary
|
|
|
5
5
|
attr_reader :manifests
|
|
6
6
|
attr_reader :lockfiles
|
|
7
7
|
|
|
8
|
+
# Create a set of RelatedFilesInfo for the provided file_infos,
|
|
9
|
+
# where each RelatedFilesInfo contains all the file_infos
|
|
8
10
|
def self.create_from_file_infos(file_infos)
|
|
9
11
|
returns = []
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
|
|
13
|
+
file_infos_by_directory = file_infos.group_by { |info| File.dirname(info.relative_path) }
|
|
14
|
+
file_infos_by_directory.values.each do |file_infos_for_path|
|
|
15
|
+
file_infos_by_directory_by_package_manager = file_infos_for_path.group_by { |info| info.package_manager}
|
|
16
|
+
|
|
17
|
+
file_infos_by_directory_by_package_manager.values.each do |file_infos_in_directory_for_package_manager|
|
|
18
|
+
returns.append(RelatedFilesInfo.new(file_infos_in_directory_for_package_manager))
|
|
15
19
|
end
|
|
16
20
|
end
|
|
21
|
+
|
|
17
22
|
returns
|
|
18
23
|
end
|
|
19
24
|
|
|
20
25
|
def initialize(file_infos)
|
|
21
26
|
package_manager = file_infos.first.package_manager
|
|
27
|
+
ordered_file_infos = file_infos
|
|
28
|
+
|
|
22
29
|
if package_manager.respond_to?(:lockfile_preference_order)
|
|
23
|
-
|
|
30
|
+
ordered_file_infos = package_manager.lockfile_preference_order(file_infos)
|
|
24
31
|
end
|
|
32
|
+
|
|
25
33
|
@platform = package_manager.platform_name
|
|
26
34
|
@path = Pathname.new(File.dirname(file_infos.first.relative_path)).cleanpath.to_path
|
|
35
|
+
|
|
36
|
+
@manifests = filter_file_infos_by_package_manager_type(
|
|
37
|
+
file_infos: ordered_file_infos,
|
|
38
|
+
package_manager: package_manager,
|
|
39
|
+
type: "manifest"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@lockfiles = filter_file_infos_by_package_manager_type(
|
|
43
|
+
file_infos: ordered_file_infos,
|
|
44
|
+
package_manager: package_manager,
|
|
45
|
+
type: "lockfile"
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def filter_file_infos_by_package_manager_type(file_infos:, package_manager:, type:)
|
|
27
52
|
# `package_manager.determine_kind_from_info(info)` can be an Array, so use include? which also works for string
|
|
28
|
-
|
|
29
|
-
@lockfiles = file_infos.select { |info| package_manager.determine_kind_from_info(info).include? "lockfile" }.map(&:relative_path)
|
|
53
|
+
file_infos.select { |info| package_manager.determine_kind_from_info(info).include?(type) }.map(&:relative_path)
|
|
30
54
|
end
|
|
31
55
|
end
|
|
32
56
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Bibliothecary
|
|
2
|
+
class Runner
|
|
3
|
+
class MultiManifestFilter
|
|
4
|
+
def initialize(path:, related_files_info_entries:, runner:)
|
|
5
|
+
@path = path
|
|
6
|
+
@related_files_info_entries = related_files_info_entries
|
|
7
|
+
@runner = runner
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Standalone multi manifest files should *always* be treated as lockfiles,
|
|
11
|
+
# since there's no human-written manifest file to go with them.
|
|
12
|
+
def files_to_check
|
|
13
|
+
@files_to_check ||= @related_files_info_entries.each_with_object({}) do |files_info, all|
|
|
14
|
+
files_info.lockfiles.each do |file|
|
|
15
|
+
all[file] ||= 0
|
|
16
|
+
all[file] += 1
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def results
|
|
22
|
+
partition_file_entries!
|
|
23
|
+
|
|
24
|
+
no_lockfile_results + single_file_results + multiple_file_results
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def no_lockfile_results
|
|
28
|
+
@no_lockfile_results ||= @related_files_info_entries.find_all { |rfi| rfi.lockfiles.empty? }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def single_file_results
|
|
32
|
+
@single_file_results ||= @single_file_entries.map do |file|
|
|
33
|
+
@related_files_info_entries.find { |rfi| rfi.lockfiles.include?(file) }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def multiple_file_results
|
|
38
|
+
return @multiple_file_results if @multiple_file_results
|
|
39
|
+
|
|
40
|
+
@multiple_file_results = []
|
|
41
|
+
@multiple_file_entries.each do |file|
|
|
42
|
+
analysis = @runner.analyse_file(file, File.read(File.join(@path, file)))
|
|
43
|
+
|
|
44
|
+
rfis_for_file = @related_files_info_entries.find_all { |rfi| rfi.lockfiles.include?(file) }
|
|
45
|
+
rfis_for_file.each do |rfi|
|
|
46
|
+
file_analysis = analysis.find { |a| a[:platform] == rfi.platform }
|
|
47
|
+
|
|
48
|
+
next unless file_analysis
|
|
49
|
+
next if file_analysis[:dependencies].empty?
|
|
50
|
+
|
|
51
|
+
@multiple_file_results << rfi
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@multiple_file_results
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def partition_file_entries!
|
|
59
|
+
@single_file_entries, @multiple_file_entries = files_to_check.partition { |file, count| count == 1 }
|
|
60
|
+
|
|
61
|
+
@single_file_entries = @single_file_entries.map(&:first)
|
|
62
|
+
@multiple_file_entries = @multiple_file_entries.map(&:first)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
data/lib/bibliothecary/runner.rb
CHANGED
|
@@ -3,7 +3,6 @@ module Bibliothecary
|
|
|
3
3
|
# A runner is created every time a file is targeted to be parsed. Don't call
|
|
4
4
|
# parse methods directory! Use a Runner.
|
|
5
5
|
class Runner
|
|
6
|
-
|
|
7
6
|
def initialize(configuration)
|
|
8
7
|
@configuration = configuration
|
|
9
8
|
@options = {
|
|
@@ -47,9 +46,11 @@ module Bibliothecary
|
|
|
47
46
|
Bibliothecary::Parsers.constants.map{|c| Bibliothecary::Parsers.const_get(c) }.sort_by{|c| c.to_s.downcase }
|
|
48
47
|
end
|
|
49
48
|
|
|
49
|
+
# Parses an array of format [{file_path: "", contents: ""},] to match
|
|
50
|
+
# on both filename matches and on content_match patterns.
|
|
51
|
+
#
|
|
52
|
+
# @return [Array<Bibliothecary::FileInfo>] A list of FileInfo, one for each package manager match for each file
|
|
50
53
|
def load_file_info_list_from_contents(file_path_contents_hash)
|
|
51
|
-
# Parses an array of format [{file_path: "", contents: ""},] to match
|
|
52
|
-
# on both filename matches, and one content_match patterns.
|
|
53
54
|
file_list = []
|
|
54
55
|
|
|
55
56
|
file_path_contents_hash.each do |file|
|
|
@@ -57,7 +58,7 @@ module Bibliothecary
|
|
|
57
58
|
|
|
58
59
|
next if ignored_files.include?(info.relative_path)
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
add_matching_package_managers_for_file_to_list(file_list, info)
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
file_list
|
|
@@ -71,7 +72,7 @@ module Bibliothecary
|
|
|
71
72
|
|
|
72
73
|
next if ignored_files.include?(info.relative_path)
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
add_matching_package_managers_for_file_to_list(file_list, info)
|
|
75
76
|
end
|
|
76
77
|
|
|
77
78
|
file_list
|
|
@@ -87,12 +88,15 @@ module Bibliothecary
|
|
|
87
88
|
next unless FileTest.file?(subpath)
|
|
88
89
|
next if ignored_files.include?(info.relative_path)
|
|
89
90
|
|
|
90
|
-
|
|
91
|
+
add_matching_package_managers_for_file_to_list(file_list, info)
|
|
91
92
|
end
|
|
92
93
|
|
|
93
94
|
file_list
|
|
94
95
|
end
|
|
95
96
|
|
|
97
|
+
# Get a list of files in this path grouped by filename and repeated by package manager.
|
|
98
|
+
#
|
|
99
|
+
# @return [Array<Bibliothecary::RelatedFilesInfo>]
|
|
96
100
|
def find_manifests(path)
|
|
97
101
|
RelatedFilesInfo.create_from_file_infos(load_file_info_list(path).reject { |info| info.package_manager.nil? })
|
|
98
102
|
end
|
|
@@ -101,10 +105,16 @@ module Bibliothecary
|
|
|
101
105
|
RelatedFilesInfo.create_from_file_infos(load_file_info_list_from_paths(paths).reject { |info| info.package_manager.nil? })
|
|
102
106
|
end
|
|
103
107
|
|
|
108
|
+
# file_path_contents_hash contains an Array of { file_path, contents }
|
|
104
109
|
def find_manifests_from_contents(file_path_contents_hash)
|
|
105
|
-
RelatedFilesInfo.create_from_file_infos(
|
|
110
|
+
RelatedFilesInfo.create_from_file_infos(
|
|
111
|
+
load_file_info_list_from_contents(
|
|
112
|
+
file_path_contents_hash
|
|
113
|
+
).reject { |info| info.package_manager.nil? }
|
|
114
|
+
)
|
|
106
115
|
end
|
|
107
116
|
|
|
117
|
+
# Read a manifest file and extract the list of dependencies from that file.
|
|
108
118
|
def analyse_file(file_path, contents)
|
|
109
119
|
package_managers.select { |pm| pm.match?(file_path, contents) }.map do |pm|
|
|
110
120
|
pm.analyse_contents(file_path, contents, options: @options)
|
|
@@ -140,15 +150,30 @@ module Bibliothecary
|
|
|
140
150
|
@configuration.ignored_files
|
|
141
151
|
end
|
|
142
152
|
|
|
153
|
+
# We don't know what file groups are in multi file manifests until
|
|
154
|
+
# we process them. In those cases, process those, then reject the
|
|
155
|
+
# RelatedFilesInfo objects that aren't in the manifest.
|
|
156
|
+
#
|
|
157
|
+
# This means we're likely analyzing these files twice in processing,
|
|
158
|
+
# but we need that accurate package manager information.
|
|
159
|
+
def filter_multi_manifest_entries(path, related_files_info_entries)
|
|
160
|
+
MultiManifestFilter.new(path: path, related_files_info_entries: related_files_info_entries , runner: self).results
|
|
161
|
+
end
|
|
162
|
+
|
|
143
163
|
private
|
|
144
164
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
165
|
+
# Get the list of all package managers that apply to the file provided
|
|
166
|
+
# as file_info, and, for each one, duplicate file_info and fill in
|
|
167
|
+
# the appropriate package manager.
|
|
168
|
+
def add_matching_package_managers_for_file_to_list(file_list, file_info)
|
|
169
|
+
applicable_package_managers(file_info).each do |package_manager|
|
|
170
|
+
new_file_info = file_info.dup
|
|
171
|
+
new_file_info.package_manager = package_manager
|
|
149
172
|
|
|
150
|
-
file_list.push(
|
|
173
|
+
file_list.push(new_file_info)
|
|
151
174
|
end
|
|
152
175
|
end
|
|
153
176
|
end
|
|
154
177
|
end
|
|
178
|
+
|
|
179
|
+
require_relative './runner/multi_manifest_filter.rb'
|
data/lib/bibliothecary.rb
CHANGED
|
@@ -16,6 +16,8 @@ Dir[File.expand_path('../bibliothecary/parsers/*.rb', __FILE__)].each do |file|
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
module Bibliothecary
|
|
19
|
+
VERSION_OPERATORS = /[~^<>*"]/
|
|
20
|
+
|
|
19
21
|
def self.analyse(path, ignore_unparseable_files: true)
|
|
20
22
|
runner.analyse(path, ignore_unparseable_files: ignore_unparseable_files)
|
|
21
23
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bibliothecary
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.2.
|
|
4
|
+
version: 8.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Nesbitt
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-
|
|
11
|
+
date: 2022-05-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: tomlrb
|
|
@@ -260,6 +260,7 @@ files:
|
|
|
260
260
|
- lib/bibliothecary/file_info.rb
|
|
261
261
|
- lib/bibliothecary/multi_parsers/bundler_like_manifest.rb
|
|
262
262
|
- lib/bibliothecary/multi_parsers/cyclonedx.rb
|
|
263
|
+
- lib/bibliothecary/multi_parsers/dependencies_csv.rb
|
|
263
264
|
- lib/bibliothecary/multi_parsers/json_runtime.rb
|
|
264
265
|
- lib/bibliothecary/parsers/bower.rb
|
|
265
266
|
- lib/bibliothecary/parsers/cargo.rb
|
|
@@ -271,7 +272,6 @@ files:
|
|
|
271
272
|
- lib/bibliothecary/parsers/cran.rb
|
|
272
273
|
- lib/bibliothecary/parsers/dub.rb
|
|
273
274
|
- lib/bibliothecary/parsers/elm.rb
|
|
274
|
-
- lib/bibliothecary/parsers/generic.rb
|
|
275
275
|
- lib/bibliothecary/parsers/go.rb
|
|
276
276
|
- lib/bibliothecary/parsers/hackage.rb
|
|
277
277
|
- lib/bibliothecary/parsers/haxelib.rb
|
|
@@ -289,6 +289,7 @@ files:
|
|
|
289
289
|
- lib/bibliothecary/parsers/swift_pm.rb
|
|
290
290
|
- lib/bibliothecary/related_files_info.rb
|
|
291
291
|
- lib/bibliothecary/runner.rb
|
|
292
|
+
- lib/bibliothecary/runner/multi_manifest_filter.rb
|
|
292
293
|
- lib/bibliothecary/version.rb
|
|
293
294
|
- lib/sdl_parser.rb
|
|
294
295
|
homepage: https://github.com/librariesio/bibliothecary
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
require 'csv'
|
|
2
|
-
|
|
3
|
-
module Bibliothecary
|
|
4
|
-
module Parsers
|
|
5
|
-
class Generic
|
|
6
|
-
include Bibliothecary::Analyser
|
|
7
|
-
|
|
8
|
-
def self.mapping
|
|
9
|
-
{
|
|
10
|
-
match_filename("dependencies.csv") => {
|
|
11
|
-
kind: 'lockfile',
|
|
12
|
-
parser: :parse_lockfile
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def self.parse_lockfile(file_contents, options: {})
|
|
18
|
-
table = CSV.parse(file_contents, headers: true)
|
|
19
|
-
|
|
20
|
-
required_headers = ["platform", "name", "requirement"]
|
|
21
|
-
missing_headers = required_headers - table.headers
|
|
22
|
-
raise "Missing headers #{missing_headers} in CSV" unless missing_headers.empty?
|
|
23
|
-
|
|
24
|
-
table.map.with_index do |row, idx|
|
|
25
|
-
line = idx + 2 # use 1-based index just like the 'csv' std lib, and count the headers as first row.
|
|
26
|
-
required_headers.each do |h|
|
|
27
|
-
raise "missing field '#{h}' on line #{line}" if row[h].nil? || row[h].empty?
|
|
28
|
-
end
|
|
29
|
-
{
|
|
30
|
-
platform: row['platform'],
|
|
31
|
-
name: row['name'],
|
|
32
|
-
requirement: row['requirement'],
|
|
33
|
-
type: row.fetch('type', 'runtime'),
|
|
34
|
-
}
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|