bibliothecary 6.3.1 → 6.3.2
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/.tidelift.yml +2 -0
- data/.travis.yml +0 -1
- data/bibliothecary.gemspec +1 -0
- data/lib/bibliothecary.rb +34 -18
- data/lib/bibliothecary/analyser.rb +115 -35
- data/lib/bibliothecary/configuration.rb +3 -1
- data/lib/bibliothecary/exceptions.rb +18 -0
- data/lib/bibliothecary/file_info.rb +51 -0
- data/lib/bibliothecary/parsers/carthage.rb +1 -0
- data/lib/bibliothecary/parsers/clojars.rb +1 -0
- data/lib/bibliothecary/parsers/hackage.rb +2 -5
- data/lib/bibliothecary/parsers/hex.rb +2 -0
- data/lib/bibliothecary/parsers/maven.rb +76 -4
- data/lib/bibliothecary/parsers/npm.rb +10 -2
- data/lib/bibliothecary/parsers/nuget.rb +2 -2
- data/lib/bibliothecary/parsers/swift_pm.rb +1 -0
- data/lib/bibliothecary/runner.rb +98 -0
- data/lib/bibliothecary/version.rb +1 -1
- metadata +20 -5
- data/.github/ISSUE_TEMPLATE.md +0 -18
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -10
- data/.github/SUPPORT.md +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4a0567c8ec55a4efdce0ef3582ffb085587a4efcc50b91e3f3596444629d60c
|
4
|
+
data.tar.gz: 63c5203b34b171ef99ce8d9beac0e05aabc2fcb202c1125a3184a2968ba6faba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e68baf27de703a993167504612de962db9ae01b8aa8ab84e2de8556074521076fe7ea27dc0e5596c1188dfd747bb541a31898da5c6ca6f2e40d0af2e7adbd09
|
7
|
+
data.tar.gz: 25f3d2f9f38bcce1c339851e5b68b1f92f354ecd0b4ea0608f258c69c7a6a0a0376dd4f2e160208c537aa1370aa0298680af6d10c22c4e82c89a14936ed9aa69
|
data/.tidelift.yml
ADDED
data/.travis.yml
CHANGED
data/bibliothecary.gemspec
CHANGED
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_dependency "commander"
|
28
28
|
|
29
29
|
spec.add_development_dependency "bundler", "~> 1.11"
|
30
|
+
spec.add_development_dependency "pry"
|
30
31
|
spec.add_development_dependency "rake", "~> 12.0"
|
31
32
|
spec.add_development_dependency "rspec", "~> 3.0"
|
32
33
|
spec.add_development_dependency "webmock"
|
data/lib/bibliothecary.rb
CHANGED
@@ -1,55 +1,71 @@
|
|
1
1
|
require "bibliothecary/version"
|
2
2
|
require "bibliothecary/analyser"
|
3
3
|
require "bibliothecary/configuration"
|
4
|
+
require "bibliothecary/runner"
|
5
|
+
require "bibliothecary/exceptions"
|
6
|
+
require "bibliothecary/file_info"
|
7
|
+
require "find"
|
4
8
|
|
5
9
|
Dir[File.expand_path('../bibliothecary/parsers/*.rb', __FILE__)].each do |file|
|
6
10
|
require file
|
7
11
|
end
|
8
12
|
|
9
13
|
module Bibliothecary
|
10
|
-
def self.analyse(path)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
def self.analyse(path, ignore_unparseable_files: true)
|
15
|
+
runner.analyse(path, ignore_unparseable_files: ignore_unparseable_files)
|
16
|
+
end
|
17
|
+
|
18
|
+
# deprecated; use load_file_info_list.
|
19
|
+
def self.load_file_list(path)
|
20
|
+
runner.load_file_list(path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.init_package_manager(info)
|
24
|
+
runner.init_package_manager(info)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.load_file_info_list(path)
|
28
|
+
runner.load_file_info_list(path)
|
14
29
|
end
|
15
30
|
|
16
31
|
def self.analyse_file(file_path, contents)
|
17
|
-
|
18
|
-
pm.analyse_contents(file_path, contents)
|
19
|
-
end.flatten.uniq.compact
|
32
|
+
runner.analyse_file(file_path, contents)
|
20
33
|
end
|
21
34
|
|
22
35
|
def self.identify_manifests(file_list)
|
23
|
-
|
24
|
-
package_managers.map do |pm|
|
25
|
-
allowed_file_list.select do |file_path|
|
26
|
-
pm.match?(file_path)
|
27
|
-
end
|
28
|
-
end.flatten.uniq.compact
|
36
|
+
runner.identify_manifests(file_list)
|
29
37
|
end
|
30
38
|
|
31
39
|
def self.package_managers
|
32
|
-
|
40
|
+
runner.package_managers
|
33
41
|
end
|
34
42
|
|
35
|
-
def self.
|
36
|
-
configuration.
|
43
|
+
def self.ignored_dirs
|
44
|
+
configuration.ignored_dirs
|
37
45
|
end
|
38
46
|
|
39
|
-
def self.
|
40
|
-
ignored_files
|
47
|
+
def self.ignored_files
|
48
|
+
configuration.ignored_files
|
41
49
|
end
|
42
50
|
|
43
51
|
class << self
|
44
52
|
attr_writer :configuration
|
45
53
|
end
|
46
54
|
|
55
|
+
def self.runner
|
56
|
+
configuration
|
57
|
+
@runner
|
58
|
+
end
|
59
|
+
|
47
60
|
def self.configuration
|
48
61
|
@configuration ||= Configuration.new
|
62
|
+
@runner = Runner.new(@configuration)
|
63
|
+
@configuration
|
49
64
|
end
|
50
65
|
|
51
66
|
def self.reset
|
52
67
|
@configuration = Configuration.new
|
68
|
+
@runner = Runner.new(@configuration)
|
53
69
|
end
|
54
70
|
|
55
71
|
def self.configure
|
@@ -1,20 +1,81 @@
|
|
1
1
|
module Bibliothecary
|
2
2
|
module Analyser
|
3
|
+
def self.create_error_analysis(platform_name, relative_path, kind, message)
|
4
|
+
{
|
5
|
+
platform: platform_name,
|
6
|
+
path: relative_path,
|
7
|
+
dependencies: nil,
|
8
|
+
kind: kind,
|
9
|
+
success: false,
|
10
|
+
error_message: message
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.create_analysis(platform_name, relative_path, kind, dependencies)
|
15
|
+
{
|
16
|
+
platform: platform_name,
|
17
|
+
path: relative_path,
|
18
|
+
dependencies: dependencies,
|
19
|
+
kind: kind,
|
20
|
+
success: true
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
3
24
|
def self.included(base)
|
4
25
|
base.extend(ClassMethods)
|
5
26
|
end
|
6
27
|
module ClassMethods
|
28
|
+
def mapping_entry_match?(regex, details, info)
|
29
|
+
if info.relative_path.match(regex)
|
30
|
+
# we only want to load contents if we don't have them already
|
31
|
+
# and there's a content_matcher method to use
|
32
|
+
return true if details[:content_matcher].nil?
|
33
|
+
# this is the libraries.io case where we won't load all .xml
|
34
|
+
# files (for example) just to look at their contents, we'll
|
35
|
+
# assume they are not manifests.
|
36
|
+
return false if info.contents.nil?
|
37
|
+
return send(details[:content_matcher], info.contents)
|
38
|
+
else
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
7
43
|
def parse_file(filename, contents)
|
8
44
|
mapping.each do |regex, details|
|
9
|
-
if
|
10
|
-
|
45
|
+
if mapping_entry_match?(regex, details, FileInfo.new(nil, filename, contents))
|
46
|
+
begin
|
47
|
+
# The `parser` method should raise an exception if the file is malformed,
|
48
|
+
# should return empty [] if the file is fine but simply doesn't contain
|
49
|
+
# any dependencies, and should never return nil. At the time of writing
|
50
|
+
# this comment, some of the parsers return [] or nil to mean an error
|
51
|
+
# which is confusing to users.
|
52
|
+
return send(details[:parser], contents)
|
53
|
+
rescue Exception => e # default is StandardError but C bindings throw Exceptions
|
54
|
+
# the C xml parser also puts a newline at the end of the message
|
55
|
+
raise Bibliothecary::FileParsingError.new(e.message.strip, filename)
|
56
|
+
end
|
11
57
|
end
|
12
58
|
end
|
13
|
-
|
59
|
+
# this can be raised if we don't check match?/match_info?,
|
60
|
+
# OR don't have the file contents when we check them, so
|
61
|
+
# it turns out for example that a .xml file isn't a
|
62
|
+
# manifest after all.
|
63
|
+
raise Bibliothecary::FileParsingError.new("No parser for this file type", filename)
|
64
|
+
end
|
65
|
+
|
66
|
+
# this is broken with contents=nil because it can't look at file
|
67
|
+
# contents, so skips manifests that are ambiguously a
|
68
|
+
# manifest considering only the filename. However, those are
|
69
|
+
# the semantics that libraries.io uses since it doesn't have
|
70
|
+
# the files locally.
|
71
|
+
def match?(filename, contents = nil)
|
72
|
+
match_info?(FileInfo.new(nil, filename, contents))
|
14
73
|
end
|
15
74
|
|
16
|
-
def
|
17
|
-
mapping.
|
75
|
+
def match_info?(info)
|
76
|
+
mapping.any? do |regex, details|
|
77
|
+
mapping_entry_match?(regex, details, info)
|
78
|
+
end
|
18
79
|
end
|
19
80
|
|
20
81
|
def platform_name
|
@@ -43,15 +104,15 @@ module Bibliothecary
|
|
43
104
|
|
44
105
|
def set_related_paths_field(by_dirname_dest, by_dirname_source)
|
45
106
|
by_dirname_dest.each do |dirname, analyses|
|
46
|
-
analyses.each do |analysis|
|
47
|
-
source_analyses = by_dirname_source[dirname].map { |source_analysis|
|
107
|
+
analyses.each do |(info, analysis)|
|
108
|
+
source_analyses = by_dirname_source[dirname].map { |(info, source_analysis)| info.relative_path }
|
48
109
|
analysis[:related_paths] = source_analyses.sort
|
49
110
|
end
|
50
111
|
end
|
51
112
|
end
|
52
113
|
|
53
114
|
def add_related_paths(analyses)
|
54
|
-
analyses.each do |analysis|
|
115
|
+
analyses.each do |(info, analysis)|
|
55
116
|
analysis[:related_paths] = []
|
56
117
|
end
|
57
118
|
|
@@ -67,13 +128,16 @@ module Bibliothecary
|
|
67
128
|
"lockfile" => Hash.new { |h, k| h[k] = [] }
|
68
129
|
}
|
69
130
|
|
70
|
-
analyses.each do |analysis|
|
71
|
-
dirname = File.dirname(
|
72
|
-
by_dirname[analysis[:kind]][dirname].push(analysis)
|
131
|
+
analyses.each do |(info, analysis)|
|
132
|
+
dirname = File.dirname(info.relative_path)
|
133
|
+
by_dirname[analysis[:kind]][dirname].push([info, analysis])
|
73
134
|
end
|
74
135
|
|
75
136
|
by_dirname["manifest"].each do |_, manifests|
|
76
|
-
|
137
|
+
# This determine_can_have_lockfile in theory needs the file contents but
|
138
|
+
# in practice doesn't right now since the only mapping that needs
|
139
|
+
# file contents is a lockfile and not a manifest so won't reach here.
|
140
|
+
manifests.delete_if { |(info, manifest)| !determine_can_have_lockfile_from_info(info) }
|
77
141
|
end
|
78
142
|
|
79
143
|
set_related_paths_field(by_dirname["manifest"], by_dirname["lockfile"])
|
@@ -81,44 +145,60 @@ module Bibliothecary
|
|
81
145
|
end
|
82
146
|
|
83
147
|
def analyse(folder_path, file_list)
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
148
|
+
analyse_file_info(file_list.map { |full_path| FileInfo.new(folder_path, full_path) })
|
149
|
+
end
|
150
|
+
|
151
|
+
def analyse_file_info(file_info_list)
|
152
|
+
analyses = file_info_list.map do |info|
|
153
|
+
next unless match_info?(info)
|
154
|
+
[info, analyse_contents_from_info(info)]
|
155
|
+
end
|
156
|
+
|
157
|
+
# strip the ones we failed to analyse
|
158
|
+
analyses = analyses.reject { |(info, analysis)| analysis.nil? }
|
90
159
|
|
91
160
|
add_related_paths(analyses)
|
92
161
|
|
93
|
-
analyses
|
162
|
+
analyses.map { |(info, analysis)| analysis }
|
94
163
|
end
|
95
164
|
|
96
165
|
def analyse_contents(filename, contents)
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
166
|
+
analyse_contents_from_info(FileInfo.new(nil, filename, contents))
|
167
|
+
end
|
168
|
+
|
169
|
+
def analyse_contents_from_info(info)
|
170
|
+
kind = determine_kind_from_info(info)
|
171
|
+
dependencies = parse_file(info.relative_path, info.contents)
|
172
|
+
|
173
|
+
Bibliothecary::Analyser::create_analysis(platform_name, info.relative_path, kind, dependencies || [])
|
174
|
+
rescue Bibliothecary::FileParsingError => e
|
175
|
+
Bibliothecary::Analyser::create_error_analysis(platform_name, info.relative_path, kind, e.message)
|
176
|
+
end
|
177
|
+
|
178
|
+
# calling this with contents=nil can produce less-informed
|
179
|
+
# results, but kept for back compat
|
180
|
+
def determine_kind(filename, contents = nil)
|
181
|
+
determine_kind_from_info(FileInfo.new(nil, filename, contents))
|
108
182
|
end
|
109
183
|
|
110
|
-
def
|
184
|
+
def determine_kind_from_info(info)
|
111
185
|
mapping.each do |regex, details|
|
112
|
-
if
|
186
|
+
if mapping_entry_match?(regex, details, info)
|
113
187
|
return details[:kind]
|
114
188
|
end
|
115
189
|
end
|
116
|
-
return
|
190
|
+
return nil
|
191
|
+
end
|
192
|
+
|
193
|
+
# calling this with contents=nil can produce less-informed
|
194
|
+
# results, but kept for back compat
|
195
|
+
def determine_can_have_lockfile(filename, contents = nil)
|
196
|
+
determine_can_have_lockfile_from_info(FileInfo.new(nil, filename, contents))
|
117
197
|
end
|
118
198
|
|
119
|
-
def
|
199
|
+
def determine_can_have_lockfile_from_info(info)
|
120
200
|
mapping.each do |regex, details|
|
121
|
-
if
|
201
|
+
if mapping_entry_match?(regex, details, info)
|
122
202
|
return details.fetch(:can_have_lockfile, true)
|
123
203
|
end
|
124
204
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Bibliothecary
|
2
2
|
class Configuration
|
3
|
+
attr_accessor :ignored_dirs
|
3
4
|
attr_accessor :ignored_files
|
4
5
|
attr_accessor :carthage_parser_host
|
5
6
|
attr_accessor :clojars_parser_host
|
@@ -10,7 +11,8 @@ module Bibliothecary
|
|
10
11
|
attr_accessor :cabal_parser_host
|
11
12
|
|
12
13
|
def initialize
|
13
|
-
@
|
14
|
+
@ignored_dirs = ['.git', 'node_modules', 'bower_components', 'vendor', 'dist']
|
15
|
+
@ignored_files = []
|
14
16
|
@carthage_parser_host = 'https://carthage.libraries.io'
|
15
17
|
@clojars_parser_host = 'https://clojars.libraries.io'
|
16
18
|
@mix_parser_host = 'https://mix.libraries.io'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Bibliothecary
|
2
|
+
class RemoteParsingError < StandardError
|
3
|
+
attr_accessor :code
|
4
|
+
def initialize(msg, code)
|
5
|
+
@code = code
|
6
|
+
super(msg)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class FileParsingError < StandardError
|
11
|
+
attr_accessor :filename
|
12
|
+
def initialize(msg, filename)
|
13
|
+
@filename = filename
|
14
|
+
msg = "#{filename}: #{msg}" unless msg.include?(filename)
|
15
|
+
super("#{msg}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Bibliothecary
|
4
|
+
class FileInfo
|
5
|
+
attr_reader :folder_path
|
6
|
+
attr_reader :relative_path
|
7
|
+
attr_reader :full_path
|
8
|
+
attr_accessor :package_manager
|
9
|
+
|
10
|
+
def contents
|
11
|
+
@contents ||=
|
12
|
+
begin
|
13
|
+
if @folder_path.nil?
|
14
|
+
# if we have no folder_path then we aren't dealing with a
|
15
|
+
# file that's actually on the filesystem
|
16
|
+
nil
|
17
|
+
else
|
18
|
+
File.open(@full_path).read
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# If the FileInfo represents an actual file on disk,
|
24
|
+
# the contents can be nil and lazy-loaded; we allow
|
25
|
+
# contents to be passed in here to allow pulling them
|
26
|
+
# from somewhere other than the disk.
|
27
|
+
def initialize(folder_path, full_path, contents = nil)
|
28
|
+
# Note that cleanpath does NOT touch the filesystem,
|
29
|
+
# leaving the lazy-load of file contents as the only
|
30
|
+
# time we touch the filesystem.
|
31
|
+
|
32
|
+
full_pathname = Pathname.new(full_path)
|
33
|
+
@full_path = full_pathname.cleanpath.to_path
|
34
|
+
|
35
|
+
if folder_path.nil?
|
36
|
+
# this is the case where we e.g. have filenames from the GitHub API
|
37
|
+
# and don't have a local folder
|
38
|
+
@folder_path = nil
|
39
|
+
@relative_path = @full_path
|
40
|
+
else
|
41
|
+
folder_pathname = Pathname.new(folder_path)
|
42
|
+
@folder_path = folder_pathname.cleanpath.to_path
|
43
|
+
@relative_path = full_pathname.relative_path_from(folder_pathname).cleanpath.to_path
|
44
|
+
end
|
45
|
+
|
46
|
+
@contents = contents
|
47
|
+
|
48
|
+
@package_manager = nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -34,6 +34,7 @@ module Bibliothecary
|
|
34
34
|
|
35
35
|
def self.map_dependencies(manifest, path)
|
36
36
|
response = Typhoeus.post("#{Bibliothecary.configuration.carthage_parser_host}/#{path}", params: {body: manifest})
|
37
|
+
raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.carthage_parser_host}/#{path}", response.response_code) unless response.success?
|
37
38
|
json = JSON.parse(response.body)
|
38
39
|
|
39
40
|
json.map do |dependency|
|
@@ -17,6 +17,7 @@ module Bibliothecary
|
|
17
17
|
|
18
18
|
def self.parse_manifest(manifest)
|
19
19
|
response = Typhoeus.post("#{Bibliothecary.configuration.clojars_parser_host}/project.clj", body: manifest)
|
20
|
+
raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.clojars_parser_host}/project.clj", response.response_code) unless response.success?
|
20
21
|
json = JSON.parse response.body
|
21
22
|
index = json.index("dependencies")
|
22
23
|
|
@@ -26,11 +26,8 @@ module Bibliothecary
|
|
26
26
|
|
27
27
|
response = Typhoeus.post("#{Bibliothecary.configuration.cabal_parser_host}/parse", headers: headers, body: file_contents)
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
else
|
32
|
-
[]
|
33
|
-
end
|
29
|
+
raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.cabal_parser_host}/parse", response.response_code) unless response.success?
|
30
|
+
JSON.parse(response.body, symbolize_names: true)
|
34
31
|
end
|
35
32
|
|
36
33
|
def self.parse_cabal_config(file_contents)
|
@@ -20,6 +20,7 @@ module Bibliothecary
|
|
20
20
|
|
21
21
|
def self.parse_mix(manifest)
|
22
22
|
response = Typhoeus.post("#{Bibliothecary.configuration.mix_parser_host}/", body: manifest)
|
23
|
+
raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.mix_parser_host}/", response.response_code) unless response.success?
|
23
24
|
json = JSON.parse response.body
|
24
25
|
|
25
26
|
json.map do |name, version|
|
@@ -33,6 +34,7 @@ module Bibliothecary
|
|
33
34
|
|
34
35
|
def self.parse_mix_lock(manifest)
|
35
36
|
response = Typhoeus.post("#{Bibliothecary.configuration.mix_parser_host}/lock", body: manifest)
|
37
|
+
raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.mix_parser_host}/", response.response_code) unless response.success?
|
36
38
|
json = JSON.parse response.body
|
37
39
|
|
38
40
|
json.map do |name, info|
|
@@ -5,6 +5,12 @@ module Bibliothecary
|
|
5
5
|
class Maven
|
6
6
|
include Bibliothecary::Analyser
|
7
7
|
|
8
|
+
# e.g. "annotationProcessor - Annotation processors and their dependencies for source set 'main'."
|
9
|
+
GRADLE_TYPE_REGEX = /^(\w+)/
|
10
|
+
|
11
|
+
# "| \\--- com.google.guava:guava:23.5-jre (*)"
|
12
|
+
GRADLE_DEP_REGEX = /(\+---|\\---){1}/
|
13
|
+
|
8
14
|
def self.mapping
|
9
15
|
{
|
10
16
|
/^ivy\.xml$|.*\/ivy\.xml$/i => {
|
@@ -18,6 +24,15 @@ module Bibliothecary
|
|
18
24
|
/^build.gradle$|.*\/build.gradle$/i => {
|
19
25
|
kind: 'manifest',
|
20
26
|
parser: :parse_gradle
|
27
|
+
},
|
28
|
+
/^.+.xml$/i => {
|
29
|
+
content_matcher: :ivy_report?,
|
30
|
+
kind: 'lockfile',
|
31
|
+
parser: :parse_ivy_report
|
32
|
+
},
|
33
|
+
/^gradle-dependencies-q\.txt|.*\/gradle-dependencies-q\.txt$/i => {
|
34
|
+
kind: 'lockfile',
|
35
|
+
parser: :parse_gradle_resolved
|
21
36
|
}
|
22
37
|
}
|
23
38
|
end
|
@@ -32,8 +47,66 @@ module Bibliothecary
|
|
32
47
|
type: 'runtime'
|
33
48
|
}
|
34
49
|
end
|
35
|
-
|
36
|
-
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.ivy_report?(file_contents)
|
53
|
+
doc = Ox.parse file_contents
|
54
|
+
root = doc&.locate("ivy-report")&.first
|
55
|
+
return !root.nil?
|
56
|
+
rescue Exception => e
|
57
|
+
# We rescue exception here since native libs can throw a non-StandardError
|
58
|
+
# We don't want to throw errors during the matching phase, only during
|
59
|
+
# parsing after we match.
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.parse_ivy_report(file_contents)
|
64
|
+
doc = Ox.parse file_contents
|
65
|
+
root = doc.locate("ivy-report").first
|
66
|
+
raise "ivy-report document does not have ivy-report at the root" if root.nil?
|
67
|
+
info = doc.locate("ivy-report/info").first
|
68
|
+
raise "ivy-report document lacks <info> element" if info.nil?
|
69
|
+
type = info.attributes[:conf]
|
70
|
+
type = "unknown" if type.nil?
|
71
|
+
modules = doc.locate("ivy-report/dependencies/module")
|
72
|
+
modules.map do |mod|
|
73
|
+
attrs = mod.attributes
|
74
|
+
org = attrs[:organisation]
|
75
|
+
name = attrs[:name]
|
76
|
+
version = mod.locate('revision').first&.attributes[:name]
|
77
|
+
|
78
|
+
next nil if org.nil? or name.nil? or version.nil?
|
79
|
+
|
80
|
+
{
|
81
|
+
name: "#{org}:#{name}",
|
82
|
+
requirement: version,
|
83
|
+
type: type
|
84
|
+
}
|
85
|
+
end.compact
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.parse_gradle_resolved(file_contents)
|
89
|
+
type = nil
|
90
|
+
file_contents.split("\n").map do |line|
|
91
|
+
type_match = GRADLE_TYPE_REGEX.match(line)
|
92
|
+
type = type_match.captures[0] if type_match
|
93
|
+
|
94
|
+
gradle_dep_match = GRADLE_DEP_REGEX.match(line)
|
95
|
+
next unless gradle_dep_match
|
96
|
+
|
97
|
+
split = gradle_dep_match.captures[0]
|
98
|
+
|
99
|
+
# org.springframework.boot:spring-boot-starter-web:2.1.0.M3 (*)
|
100
|
+
# Lines can end with (n) or (*) to indicate that something was not resolved (n) or resolved previously (*).
|
101
|
+
dep = line.split(split)[1].sub(/\(n\)$/, "").sub(/\(\*\)$/,"").strip.split(":")
|
102
|
+
version = dep[-1]
|
103
|
+
version = version.split("->")[-1].strip if line.include?("->")
|
104
|
+
{
|
105
|
+
name: dep[0, dep.length - 1].join(":"),
|
106
|
+
requirement: version,
|
107
|
+
type: type
|
108
|
+
}
|
109
|
+
end.compact.uniq {|item| [item[:name], item[:requirement], item[:type]]}
|
37
110
|
end
|
38
111
|
|
39
112
|
def self.parse_pom_manifest(file_contents)
|
@@ -51,12 +124,11 @@ module Bibliothecary
|
|
51
124
|
type: extract_pom_dep_info(xml, dependency, 'scope') || 'runtime'
|
52
125
|
}
|
53
126
|
end
|
54
|
-
rescue
|
55
|
-
[]
|
56
127
|
end
|
57
128
|
|
58
129
|
def self.parse_gradle(manifest)
|
59
130
|
response = Typhoeus.post("#{Bibliothecary.configuration.gradle_parser_host}/parse", body: manifest)
|
131
|
+
raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.gradle_parser_host}/parse", response.response_code) unless response.success?
|
60
132
|
json = JSON.parse(response.body)
|
61
133
|
return [] unless json['dependencies']
|
62
134
|
json['dependencies'].map do |dependency|
|
@@ -40,23 +40,31 @@ module Bibliothecary
|
|
40
40
|
def self.parse_package_lock(file_contents)
|
41
41
|
manifest = JSON.parse(file_contents)
|
42
42
|
manifest.fetch('dependencies',[]).map do |name, requirement|
|
43
|
+
if requirement.fetch("dev", false)
|
44
|
+
type = 'development'
|
45
|
+
else
|
46
|
+
type = 'runtime'
|
47
|
+
end
|
43
48
|
{
|
44
49
|
name: name,
|
45
50
|
requirement: requirement["version"],
|
46
|
-
type:
|
51
|
+
type: type
|
47
52
|
}
|
48
53
|
end
|
49
54
|
end
|
50
55
|
|
51
56
|
def self.parse_manifest(file_contents)
|
52
57
|
manifest = JSON.parse(file_contents)
|
58
|
+
raise "appears to be a lockfile rather than manifest format" if manifest.key?('lockfileVersion')
|
53
59
|
map_dependencies(manifest, 'dependencies', 'runtime') +
|
54
60
|
map_dependencies(manifest, 'devDependencies', 'development')
|
55
61
|
end
|
56
62
|
|
57
63
|
def self.parse_yarn_lock(file_contents)
|
58
64
|
response = Typhoeus.post("#{Bibliothecary.configuration.yarn_parser_host}/parse", body: file_contents)
|
59
|
-
|
65
|
+
|
66
|
+
raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.yarn_parser_host}/parse", response.response_code) unless response.success?
|
67
|
+
|
60
68
|
json = JSON.parse(response.body, symbolize_names: true)
|
61
69
|
json.uniq.map do |dep|
|
62
70
|
{
|
@@ -52,7 +52,7 @@ module Bibliothecary
|
|
52
52
|
manifest.packages.locate('package').map do |dependency|
|
53
53
|
{
|
54
54
|
name: dependency.id,
|
55
|
-
requirement: dependency.version,
|
55
|
+
requirement: (dependency.version if dependency.respond_to? "version") || "*",
|
56
56
|
type: 'runtime'
|
57
57
|
}
|
58
58
|
end
|
@@ -65,7 +65,7 @@ module Bibliothecary
|
|
65
65
|
packages = manifest.locate('ItemGroup/PackageReference').map do |dependency|
|
66
66
|
{
|
67
67
|
name: dependency.Include,
|
68
|
-
requirement: dependency.Version,
|
68
|
+
requirement: (dependency.Version if dependency.respond_to? "Version") || "*",
|
69
69
|
type: 'runtime'
|
70
70
|
}
|
71
71
|
end
|
@@ -14,6 +14,7 @@ module Bibliothecary
|
|
14
14
|
|
15
15
|
def self.parse_package_swift(manifest)
|
16
16
|
response = Typhoeus.post("#{Bibliothecary.configuration.swift_parser_host}/to-json", body: manifest)
|
17
|
+
raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.swift_parser_host}/to-json", response.response_code) unless response.success?
|
17
18
|
json = JSON.parse(response.body)
|
18
19
|
json["dependencies"].map do |dependency|
|
19
20
|
name = dependency['url'].gsub(/^https?:\/\//, '').gsub(/\.git$/,'')
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Bibliothecary
|
2
|
+
# A class that allows bibliothecary to run with multiple configurations at once, rather than with one global
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
def initialize(configuration)
|
6
|
+
@configuration = configuration
|
7
|
+
end
|
8
|
+
|
9
|
+
def analyse(path, ignore_unparseable_files: true)
|
10
|
+
info_list = load_file_info_list(path)
|
11
|
+
|
12
|
+
info_list = info_list.reject { |info| info.package_manager.nil? } if ignore_unparseable_files
|
13
|
+
|
14
|
+
# Each package manager needs to see its entire list so it can
|
15
|
+
# associate related manifests and lockfiles for example.
|
16
|
+
analyses = package_managers.map do |pm|
|
17
|
+
matching_infos = info_list.select { |info| info.package_manager == pm }
|
18
|
+
pm.analyse_file_info(matching_infos)
|
19
|
+
end
|
20
|
+
analyses = analyses.flatten.compact
|
21
|
+
|
22
|
+
info_list.select { |info| info.package_manager.nil? }.each do |info|
|
23
|
+
analyses.push(Bibliothecary::Analyser::create_error_analysis('unknown', info.relative_path, 'unknown',
|
24
|
+
'No parser for this file type'))
|
25
|
+
end
|
26
|
+
|
27
|
+
analyses
|
28
|
+
end
|
29
|
+
|
30
|
+
# deprecated; use load_file_info_list.
|
31
|
+
def load_file_list(path)
|
32
|
+
load_file_info_list(path).map { |info| info.full_path }
|
33
|
+
end
|
34
|
+
|
35
|
+
def init_package_manager(info)
|
36
|
+
# set the package manager on each info
|
37
|
+
matches = package_managers.select { |pm| pm.match_info?(info) }
|
38
|
+
|
39
|
+
info.package_manager = matches[0] if matches.length == 1
|
40
|
+
|
41
|
+
# this is a bug at the moment if it's raised (we don't handle it sensibly)
|
42
|
+
raise "Multiple package managers fighting over #{info.relative_path}: #{matches.map(&:to_s)}" if matches.length > 1
|
43
|
+
end
|
44
|
+
|
45
|
+
def package_managers
|
46
|
+
Bibliothecary::Parsers.constants.map{|c| Bibliothecary::Parsers.const_get(c) }.sort_by{|c| c.to_s.downcase }
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_file_info_list(path)
|
50
|
+
file_list = []
|
51
|
+
Find.find(path) do |subpath|
|
52
|
+
info = FileInfo.new(path, subpath)
|
53
|
+
Find.prune if FileTest.directory?(subpath) && ignored_dirs.include?(info.relative_path)
|
54
|
+
next unless FileTest.file?(subpath)
|
55
|
+
next if ignored_files.include?(info.relative_path)
|
56
|
+
|
57
|
+
init_package_manager(info)
|
58
|
+
|
59
|
+
file_list.push(info)
|
60
|
+
end
|
61
|
+
file_list
|
62
|
+
end
|
63
|
+
|
64
|
+
def analyse_file(file_path, contents)
|
65
|
+
package_managers.select { |pm| pm.match?(file_path, contents) }.map do |pm|
|
66
|
+
pm.analyse_contents(file_path, contents)
|
67
|
+
end.flatten.uniq.compact
|
68
|
+
end
|
69
|
+
|
70
|
+
# this skips manifests sometimes because it doesn't look at file
|
71
|
+
# contents and can't establish from only regexes that the thing
|
72
|
+
# is a manifest. We exclude rather than include ambiguous filenames
|
73
|
+
# because this API is used by libraries.io and we don't want to
|
74
|
+
# download all .xml files from GitHub.
|
75
|
+
def identify_manifests(file_list)
|
76
|
+
ignored_dirs_with_slash = ignored_dirs.map { |d| if d.end_with?("/") then d else d + "/" end }
|
77
|
+
allowed_file_list = file_list.reject do |f|
|
78
|
+
ignored_dirs.include?(f) || f.start_with?(*ignored_dirs_with_slash)
|
79
|
+
end
|
80
|
+
allowed_file_list = allowed_file_list.reject{|f| ignored_files.include?(f)}
|
81
|
+
package_managers.map do |pm|
|
82
|
+
allowed_file_list.select do |file_path|
|
83
|
+
# this is a call to match? without file contents, which will skip
|
84
|
+
# ambiguous filenames that are only possibly a manifest
|
85
|
+
pm.match?(file_path)
|
86
|
+
end
|
87
|
+
end.flatten.uniq.compact
|
88
|
+
end
|
89
|
+
|
90
|
+
def ignored_dirs
|
91
|
+
@configuration.ignored_dirs
|
92
|
+
end
|
93
|
+
|
94
|
+
def ignored_files
|
95
|
+
@configuration.ignored_files
|
96
|
+
end
|
97
|
+
end
|
98
|
+
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: 6.3.
|
4
|
+
version: 6.3.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:
|
11
|
+
date: 2019-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: toml-rb
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '1.11'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pry
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
125
139
|
- !ruby/object:Gem::Dependency
|
126
140
|
name: rake
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -190,13 +204,11 @@ extra_rdoc_files: []
|
|
190
204
|
files:
|
191
205
|
- ".codeclimate.yml"
|
192
206
|
- ".github/CONTRIBUTING.md"
|
193
|
-
- ".github/ISSUE_TEMPLATE.md"
|
194
|
-
- ".github/PULL_REQUEST_TEMPLATE.md"
|
195
|
-
- ".github/SUPPORT.md"
|
196
207
|
- ".gitignore"
|
197
208
|
- ".rspec"
|
198
209
|
- ".rubocop.yml"
|
199
210
|
- ".ruby-version"
|
211
|
+
- ".tidelift.yml"
|
200
212
|
- ".travis.yml"
|
201
213
|
- CODE_OF_CONDUCT.md
|
202
214
|
- Gemfile
|
@@ -212,6 +224,8 @@ files:
|
|
212
224
|
- lib/bibliothecary/analyser.rb
|
213
225
|
- lib/bibliothecary/cli.rb
|
214
226
|
- lib/bibliothecary/configuration.rb
|
227
|
+
- lib/bibliothecary/exceptions.rb
|
228
|
+
- lib/bibliothecary/file_info.rb
|
215
229
|
- lib/bibliothecary/parsers/bower.rb
|
216
230
|
- lib/bibliothecary/parsers/cargo.rb
|
217
231
|
- lib/bibliothecary/parsers/carthage.rb
|
@@ -236,6 +250,7 @@ files:
|
|
236
250
|
- lib/bibliothecary/parsers/rubygems.rb
|
237
251
|
- lib/bibliothecary/parsers/shard.rb
|
238
252
|
- lib/bibliothecary/parsers/swift_pm.rb
|
253
|
+
- lib/bibliothecary/runner.rb
|
239
254
|
- lib/bibliothecary/version.rb
|
240
255
|
- lib/sdl_parser.rb
|
241
256
|
homepage: https://github.com/librariesio/bibliothecary
|
data/.github/ISSUE_TEMPLATE.md
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
Thanks for taking the time to raise an issue. This template should guide you through the process of submitting a bug, enhancement or feature request. Please erase any part of this template that is not relevant to your issue.
|
2
|
-
|
3
|
-
## Bugs
|
4
|
-
Before submitting a bug report:
|
5
|
-
|
6
|
-
- [ ] Double-check that the bug is persistent,
|
7
|
-
- [ ] Double-check the bug hasn't already been reported [on our issue tracker](https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+org%3Alibrariesio), they *should* be labelled `bug` or `bugsnag`.
|
8
|
-
|
9
|
-
If you have completed those steps then please replace this section with a description of the steps taken to recreate the bug, the expected behavior and the observed behavior.
|
10
|
-
|
11
|
-
## Enhancements and Features
|
12
|
-
|
13
|
-
Before submitting an enhancement or feature request:
|
14
|
-
|
15
|
-
- [ ] Check that the enhancement is not already [in our issue tracker](https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+org%3Alibrariesio), they should be labelled 'enhancement'.,
|
16
|
-
- [ ] For large feature requests, check that your request aligns with our strategy http://docs.libraries.io/strategy.
|
17
|
-
|
18
|
-
If you have complete the above step then please replace this section with a description of your proposed enhancement or feature, the motivation for it, an approach and any alternative approaches considered, and whether you are willing and able to create a pull request for it. Note that we may close this issue if it's not something we're planning on working on.
|
@@ -1,10 +0,0 @@
|
|
1
|
-
Thanks taking the time to contribute. This template should help guide you through the process of creating a pull request for review. Please erase any part of this template that is not relevant to your pull request:
|
2
|
-
|
3
|
-
|
4
|
-
- [ ] Have you followed the guidelines for [contributors](http://docs.libraries.io/contributorshandbook)?
|
5
|
-
- [ ] Have you checked to ensure there aren't other open pull requests on the repository for a similar change?
|
6
|
-
- [ ] Is there a corresponding ticket for your pull request?
|
7
|
-
- [ ] Have you written new tests for your changes?
|
8
|
-
- [ ] Have you successfully run the project with your changes locally?
|
9
|
-
|
10
|
-
If so then please replace this section with a link to the ticket(s) it addressed, an explanation of your change and why you think we should include it. Thanks again!
|
data/.github/SUPPORT.md
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
# Libraries.io Support
|
2
|
-
|
3
|
-
If you're looking for support for Libraries.io there are a lot of options, check out:
|
4
|
-
|
5
|
-
* Documentation — https://docs.libraries.io
|
6
|
-
* Email — support@libraries.io
|
7
|
-
* Twitter — https://twitter.com/librariesio
|
8
|
-
* Chat — https://slack.libraries.io
|
9
|
-
|
10
|
-
On Discuss and in the Libraries.io Slack team, there are a bunch of helpful community members that should be willing to point you in the right direction.
|