bibliothecary 8.2.1 → 8.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bibliothecary/multi_parsers/dependencies_csv.rb +149 -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 +2 -1
- 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: 4dd448ab1be90710e81700b68541de4ba3f17731a67a24f734c9ff12d0898d1f
|
4
|
+
data.tar.gz: d595b3746c16a87f4442650f0c28ed07f9e4097875b0ca1b646b99914e9c699f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd75677e52714d25f33ae3da3295d1146eba3679bb89c093d91840c8c3d8f65bac2365f21021d41e7e58b9d88fa6e4160375870e4fab0d6c4cdd4e753775d68f
|
7
|
+
data.tar.gz: c3c70847f495b5c7eb3d0c4fe41454ae50534a4e131b6ef2618d31e3e188147c144363d31d024aab0daef76b62bec9e30f0cdd3d09ef4d61ae793912aaee184d
|
@@ -0,0 +1,149 @@
|
|
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
|
+
/^version$/i,
|
43
|
+
/^(lockfile |)requirement$/i,
|
44
|
+
],
|
45
|
+
},
|
46
|
+
# Manifests have versions that can have operators.
|
47
|
+
"requirement" => {
|
48
|
+
match: [
|
49
|
+
/^manifest requirement$/i,
|
50
|
+
/^version$/i,
|
51
|
+
/^(lockfile |)requirement$/i,
|
52
|
+
],
|
53
|
+
default: nil
|
54
|
+
},
|
55
|
+
"type" => {
|
56
|
+
default: "runtime",
|
57
|
+
match: [
|
58
|
+
/^(lockfile |)type$/i,
|
59
|
+
/^(manifest |)type$/i
|
60
|
+
]
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
attr_reader :result
|
65
|
+
|
66
|
+
def initialize(file_contents)
|
67
|
+
@file_contents = file_contents
|
68
|
+
|
69
|
+
@result = nil
|
70
|
+
|
71
|
+
# A Hash of "our field name" => ["header in CSV file", "lower priority header in CSV file"]
|
72
|
+
@header_mappings = {}
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse!
|
76
|
+
table = parse_and_validate_csv_file
|
77
|
+
|
78
|
+
@result = table.map.with_index do |row, idx|
|
79
|
+
HEADERS.each_with_object({}) do |(header, info), obj|
|
80
|
+
# find the first non-empty field in the row for this header, or nil if not found
|
81
|
+
row_data = row[@header_mappings[header]]
|
82
|
+
|
83
|
+
# some column have default data to fall back on
|
84
|
+
if row_data
|
85
|
+
obj[header.to_sym] = row_data
|
86
|
+
elsif info.has_key?(:default)
|
87
|
+
# if the default is nil, don't even add the key to the hash
|
88
|
+
obj[header.to_sym] = info[:default] if info[:default]
|
89
|
+
else
|
90
|
+
# use 1-based index just like the 'csv' std lib, and count the headers as first row.
|
91
|
+
raise "Missing required field '#{header}' on line #{idx + 2}."
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def parse_and_validate_csv_file
|
100
|
+
table = CSV.parse(@file_contents, headers: true)
|
101
|
+
|
102
|
+
header_examination_results = map_table_headers_to_local_lookups(table, HEADERS)
|
103
|
+
unless header_examination_results[:missing].empty?
|
104
|
+
raise "Missing required headers #{header_examination_results[:missing].join(', ')} in CSV. Check to make sure header names are all lowercase."
|
105
|
+
end
|
106
|
+
@header_mappings = header_examination_results[:found]
|
107
|
+
|
108
|
+
table
|
109
|
+
end
|
110
|
+
|
111
|
+
def map_table_headers_to_local_lookups(table, local_lookups)
|
112
|
+
result = local_lookups.each_with_object({ found: {}, missing: [] }) do |(header, info), obj|
|
113
|
+
results = table.headers.each_with_object([]) do |table_header, matches|
|
114
|
+
info[:match].each_with_index do |match_regexp, index|
|
115
|
+
matches << [table_header, index] if table_header[match_regexp]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
if results.empty?
|
120
|
+
# if a header has a default value it's optional
|
121
|
+
obj[:missing] << header unless info.has_key?(:default)
|
122
|
+
else
|
123
|
+
# select the highest priority header possible
|
124
|
+
obj[:found][header] ||= nil
|
125
|
+
obj[:found][header] = ([obj[:found][header]] + results).compact.min_by(&:last)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# strip off the priorities. only one mapping should remain.
|
130
|
+
result[:found].transform_values!(&:first)
|
131
|
+
|
132
|
+
result
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def parse_dependencies_csv(file_contents, options: {})
|
137
|
+
csv_file = try_cache(options, options[:filename]) do
|
138
|
+
raw_csv_file = CSVFile.new(file_contents)
|
139
|
+
raw_csv_file.parse!
|
140
|
+
raw_csv_file
|
141
|
+
end
|
142
|
+
|
143
|
+
csv_file.result.find_all do |dependency|
|
144
|
+
dependency[:platform] == platform_name.to_s
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
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)
|
@@ -84,7 +84,8 @@ module Bibliothecary
|
|
84
84
|
}
|
85
85
|
end
|
86
86
|
|
87
|
-
add_multi_parser
|
87
|
+
add_multi_parser(Bibliothecary::MultiParsers::CycloneDX)
|
88
|
+
add_multi_parser(Bibliothecary::MultiParsers::DependenciesCSV)
|
88
89
|
|
89
90
|
def self.parse_ivy_manifest(file_contents, options: {})
|
90
91
|
manifest = Ox.parse file_contents
|
@@ -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.2
|
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-05-
|
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
|