bibliothecary 6.3.1 → 6.3.2
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|