spandx 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -2
- data/README.md +59 -2
- data/exe/spandx +3 -4
- data/lib/spandx.rb +13 -32
- data/lib/spandx/cli.rb +1 -30
- data/lib/spandx/cli/commands/build.rb +41 -0
- data/lib/spandx/cli/commands/pull.rb +21 -0
- data/lib/spandx/cli/commands/scan.rb +17 -2
- data/lib/spandx/cli/main.rb +54 -0
- data/lib/spandx/core/cache.rb +3 -3
- data/lib/spandx/core/circuit.rb +34 -0
- data/lib/spandx/core/dependency.rb +32 -7
- data/lib/spandx/core/gateway.rb +19 -0
- data/lib/spandx/core/{database.rb → git.rb} +7 -2
- data/lib/spandx/core/guess.rb +42 -4
- data/lib/spandx/core/http.rb +30 -5
- data/lib/spandx/core/license_plugin.rb +54 -0
- data/lib/spandx/core/null_gateway.rb +11 -0
- data/lib/spandx/core/parser.rb +8 -25
- data/lib/spandx/core/plugin.rb +15 -0
- data/lib/spandx/core/registerable.rb +27 -0
- data/lib/spandx/core/report.rb +30 -6
- data/lib/spandx/core/table.rb +29 -0
- data/lib/spandx/dotnet/index.rb +10 -5
- data/lib/spandx/dotnet/nuget_gateway.rb +20 -31
- data/lib/spandx/dotnet/parsers/csproj.rb +3 -12
- data/lib/spandx/dotnet/parsers/packages_config.rb +2 -10
- data/lib/spandx/dotnet/parsers/sln.rb +2 -2
- data/lib/spandx/java/gateway.rb +37 -0
- data/lib/spandx/java/index.rb +84 -2
- data/lib/spandx/java/metadata.rb +6 -3
- data/lib/spandx/java/parsers/maven.rb +11 -21
- data/lib/spandx/js/parsers/npm.rb +39 -0
- data/lib/spandx/js/parsers/yarn.rb +30 -0
- data/lib/spandx/js/yarn_lock.rb +67 -0
- data/lib/spandx/js/yarn_pkg.rb +59 -0
- data/lib/spandx/php/packagist_gateway.rb +25 -0
- data/lib/spandx/php/parsers/composer.rb +33 -0
- data/lib/spandx/python/index.rb +78 -0
- data/lib/spandx/python/parsers/pipfile_lock.rb +12 -16
- data/lib/spandx/python/pypi.rb +91 -8
- data/lib/spandx/python/source.rb +5 -1
- data/lib/spandx/{rubygems → ruby}/gateway.rb +8 -9
- data/lib/spandx/{rubygems → ruby}/parsers/gemfile_lock.rb +14 -16
- data/lib/spandx/spdx/catalogue.rb +1 -1
- data/lib/spandx/spdx/license.rb +12 -2
- data/lib/spandx/version.rb +1 -1
- data/spandx.gemspec +4 -1
- metadata +66 -10
- data/lib/spandx/cli/command.rb +0 -65
- data/lib/spandx/cli/commands/index.rb +0 -36
- data/lib/spandx/cli/commands/index/build.rb +0 -32
- data/lib/spandx/cli/commands/index/update.rb +0 -27
@@ -3,21 +3,46 @@
|
|
3
3
|
module Spandx
|
4
4
|
module Core
|
5
5
|
class Dependency
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :package_manager, :name, :version, :licenses, :meta
|
7
7
|
|
8
|
-
def initialize(name:, version:, licenses: [], meta: {})
|
8
|
+
def initialize(package_manager:, name:, version:, licenses: [], meta: {})
|
9
|
+
@package_manager = package_manager
|
9
10
|
@name = name
|
10
11
|
@version = version
|
11
12
|
@licenses = licenses
|
12
13
|
@meta = meta
|
13
14
|
end
|
14
15
|
|
16
|
+
def managed_by?(value)
|
17
|
+
package_manager == value&.to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
def <=>(other)
|
21
|
+
to_s <=> other.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def hash
|
25
|
+
to_s.hash
|
26
|
+
end
|
27
|
+
|
28
|
+
def eql?(other)
|
29
|
+
to_s == other.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
@to_s ||= [name, version].compact.join(' ')
|
34
|
+
end
|
35
|
+
|
36
|
+
def inspect
|
37
|
+
"#<Spandx::Core::Dependency name=#{name}, version=#{version}>"
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_a
|
41
|
+
[name, version, licenses.map(&:id)]
|
42
|
+
end
|
43
|
+
|
15
44
|
def to_h
|
16
|
-
{
|
17
|
-
name: name,
|
18
|
-
version: version,
|
19
|
-
licenses: licenses.compact.map(&:id)
|
20
|
-
}
|
45
|
+
{ name: name, version: version, licenses: licenses.map(&:id) }
|
21
46
|
end
|
22
47
|
end
|
23
48
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spandx
|
4
|
+
module Core
|
5
|
+
class Gateway
|
6
|
+
def matches?(_dependency)
|
7
|
+
raise ::Spandx::Error, :matches?
|
8
|
+
end
|
9
|
+
|
10
|
+
def licenses_for(_dependency)
|
11
|
+
raise ::Spandx::Error, :licenses_for
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
include Registerable
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Spandx
|
4
4
|
module Core
|
5
|
-
class
|
5
|
+
class Git
|
6
6
|
attr_reader :path, :url
|
7
7
|
|
8
8
|
def initialize(url:)
|
@@ -28,7 +28,10 @@ module Spandx
|
|
28
28
|
def open(path, mode: 'r')
|
29
29
|
update! unless dotgit?
|
30
30
|
|
31
|
-
|
31
|
+
full_path = expand_path(path)
|
32
|
+
return unless File.exist?(full_path)
|
33
|
+
|
34
|
+
File.open(full_path, mode) do |io|
|
32
35
|
yield io
|
33
36
|
end
|
34
37
|
end
|
@@ -61,5 +64,7 @@ module Spandx
|
|
61
64
|
end
|
62
65
|
end
|
63
66
|
end
|
67
|
+
|
68
|
+
Database = Git
|
64
69
|
end
|
65
70
|
end
|
data/lib/spandx/core/guess.rb
CHANGED
@@ -9,8 +9,44 @@ module Spandx
|
|
9
9
|
@catalogue = catalogue
|
10
10
|
end
|
11
11
|
|
12
|
-
def license_for(
|
13
|
-
|
12
|
+
def license_for(raw, algorithm: :dice_coefficient)
|
13
|
+
raw.is_a?(Hash) ? from_hash(raw, algorithm) : from_string(raw, algorithm)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def from_hash(hash, algorithm)
|
19
|
+
from_string(hash[:name], algorithm) ||
|
20
|
+
from_url(hash[:url], algorithm) ||
|
21
|
+
unknown(hash[:name] || hash[:url])
|
22
|
+
end
|
23
|
+
|
24
|
+
def from_string(raw, algorithm)
|
25
|
+
content = Content.new(raw)
|
26
|
+
|
27
|
+
catalogue[raw] ||
|
28
|
+
match_name(content, algorithm) ||
|
29
|
+
match_body(content, algorithm) ||
|
30
|
+
unknown(raw)
|
31
|
+
end
|
32
|
+
|
33
|
+
def from_url(url, algorithm)
|
34
|
+
return if url.nil? || url.empty?
|
35
|
+
|
36
|
+
response = Spandx.http.get(url)
|
37
|
+
return unless Spandx.http.ok?(response)
|
38
|
+
|
39
|
+
license_for(response.body, algorithm: algorithm)
|
40
|
+
end
|
41
|
+
|
42
|
+
def match_name(content, _algorithm)
|
43
|
+
catalogue.find do |license|
|
44
|
+
score = content.similarity_score(::Spandx::Core::Content.new(license.name))
|
45
|
+
score > 85
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def match_body(content, algorithm)
|
14
50
|
score = Score.new(nil, nil)
|
15
51
|
threshold = threshold_for(algorithm)
|
16
52
|
direction = algorithm == :levenshtein ? method(:min) : method(:max)
|
@@ -18,10 +54,12 @@ module Spandx
|
|
18
54
|
catalogue.each do |license|
|
19
55
|
direction.call(content, license, score, threshold, algorithm) unless license.deprecated_license_id?
|
20
56
|
end
|
21
|
-
score&.item
|
57
|
+
score&.item
|
22
58
|
end
|
23
59
|
|
24
|
-
|
60
|
+
def unknown(text)
|
61
|
+
::Spandx::Spdx::License.unknown(text)
|
62
|
+
end
|
25
63
|
|
26
64
|
def threshold_for(algorithm)
|
27
65
|
{
|
data/lib/spandx/core/http.rb
CHANGED
@@ -3,17 +3,24 @@
|
|
3
3
|
module Spandx
|
4
4
|
module Core
|
5
5
|
class Http
|
6
|
-
attr_reader :driver
|
6
|
+
attr_reader :driver, :retries
|
7
7
|
|
8
|
-
def initialize(driver: Http.default_driver)
|
8
|
+
def initialize(driver: Http.default_driver, retries: 3)
|
9
9
|
@driver = driver
|
10
|
+
@retries = retries
|
11
|
+
@circuits = Hash.new { |hash, key| hash[key] = Circuit.new }
|
10
12
|
end
|
11
13
|
|
12
|
-
def get(uri, default: nil)
|
14
|
+
def get(uri, default: nil, escape: true)
|
13
15
|
return default if Spandx.airgap?
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
+
circuit = circuit_for(uri)
|
18
|
+
return default if circuit.open?
|
19
|
+
|
20
|
+
circuit.attempt do
|
21
|
+
driver.with_retry(retries: retries) do |client|
|
22
|
+
client.get(escape ? Addressable::URI.escape(uri) : uri)
|
23
|
+
end
|
17
24
|
end
|
18
25
|
rescue *Net::Hippie::CONNECTION_ERRORS
|
19
26
|
default
|
@@ -26,9 +33,27 @@ module Spandx
|
|
26
33
|
def self.default_driver
|
27
34
|
@default_driver ||= Net::Hippie::Client.new.tap do |client|
|
28
35
|
client.logger = Spandx.logger
|
36
|
+
client.open_timeout = 1
|
37
|
+
client.read_timeout = 5
|
29
38
|
client.follow_redirects = 3
|
30
39
|
end
|
31
40
|
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def circuit_breaker_for(host, default)
|
45
|
+
return default unless @circuits[host]
|
46
|
+
|
47
|
+
@circuits[host] = false
|
48
|
+
result = yield
|
49
|
+
@circuits[host] = true
|
50
|
+
result
|
51
|
+
end
|
52
|
+
|
53
|
+
def circuit_for(url)
|
54
|
+
uri = URI.parse(url.to_s)
|
55
|
+
@circuits[uri.host]
|
56
|
+
end
|
32
57
|
end
|
33
58
|
end
|
34
59
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spandx
|
4
|
+
module Core
|
5
|
+
class LicensePlugin < Spandx::Core::Plugin
|
6
|
+
def initialize(catalogue: Spdx::Catalogue.from_git)
|
7
|
+
@guess = Guess.new(catalogue)
|
8
|
+
end
|
9
|
+
|
10
|
+
def enhance(dependency)
|
11
|
+
return dependency unless known?(dependency.package_manager)
|
12
|
+
return enhance_from_metadata(dependency) if available_in?(dependency.meta)
|
13
|
+
|
14
|
+
licenses_for(dependency).each do |text|
|
15
|
+
dependency.licenses << @guess.license_for(text)
|
16
|
+
end
|
17
|
+
dependency
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def licenses_for(dependency)
|
23
|
+
results = cache_for(dependency).licenses_for(dependency.name, dependency.version)
|
24
|
+
results && !results.empty? ? results : gateway_for(dependency).licenses_for(dependency)
|
25
|
+
end
|
26
|
+
|
27
|
+
def cache_for(dependency, git: Spandx.git)
|
28
|
+
db = git[dependency.package_manager.to_sym] || git[:cache]
|
29
|
+
Spandx::Core::Cache.new(dependency.package_manager, db: db)
|
30
|
+
end
|
31
|
+
|
32
|
+
def known?(package_manager)
|
33
|
+
%i[nuget maven rubygems npm yarn pypi composer].include?(package_manager)
|
34
|
+
end
|
35
|
+
|
36
|
+
def gateway_for(dependency)
|
37
|
+
::Spandx::Core::Gateway.find do |gateway|
|
38
|
+
gateway.matches?(dependency)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def available_in?(metadata)
|
43
|
+
metadata.respond_to?(:[]) && metadata['license']
|
44
|
+
end
|
45
|
+
|
46
|
+
def enhance_from_metadata(dependency)
|
47
|
+
dependency.meta['license'].each do |x|
|
48
|
+
dependency.licenses << @guess.license_for(x)
|
49
|
+
end
|
50
|
+
dependency
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/spandx/core/parser.rb
CHANGED
@@ -9,36 +9,19 @@ module Spandx
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
|
12
|
+
def matches?(_filename)
|
13
|
+
raise ::Spandx::Error, :matches?
|
14
|
+
end
|
13
15
|
|
14
|
-
def
|
15
|
-
|
16
|
+
def parse(_dependency)
|
17
|
+
raise ::Spandx::Error, :parse
|
16
18
|
end
|
17
19
|
|
18
20
|
class << self
|
19
|
-
include
|
20
|
-
|
21
|
-
def each(&block)
|
22
|
-
registry.each do |x|
|
23
|
-
block.call(x)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def inherited(subclass)
|
28
|
-
registry.push(subclass)
|
29
|
-
end
|
30
|
-
|
31
|
-
def registry
|
32
|
-
@registry ||= []
|
33
|
-
end
|
34
|
-
|
35
|
-
def for(path, catalogue: Spandx::Spdx::Catalogue.from_git)
|
36
|
-
Spandx.logger.debug(path)
|
37
|
-
result = ::Spandx::Core::Parser.find do |x|
|
38
|
-
x.matches?(File.basename(path))
|
39
|
-
end
|
21
|
+
include Registerable
|
40
22
|
|
41
|
-
|
23
|
+
def for(path)
|
24
|
+
find { |x| x.matches?(File.basename(path)) } || UNKNOWN
|
42
25
|
end
|
43
26
|
end
|
44
27
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spandx
|
4
|
+
module Core
|
5
|
+
module Registerable
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def all
|
9
|
+
@all ||= registry.map(&:new)
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
all.each do |x|
|
14
|
+
block.call(x)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def inherited(subclass)
|
19
|
+
registry.push(subclass)
|
20
|
+
end
|
21
|
+
|
22
|
+
def registry
|
23
|
+
@registry ||= []
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/spandx/core/report.rb
CHANGED
@@ -3,26 +3,44 @@
|
|
3
3
|
module Spandx
|
4
4
|
module Core
|
5
5
|
class Report
|
6
|
+
include Enumerable
|
7
|
+
|
6
8
|
FORMATS = {
|
7
|
-
|
9
|
+
csv: :to_csv,
|
8
10
|
hash: :to_h,
|
11
|
+
json: :to_json,
|
12
|
+
table: :to_table,
|
9
13
|
}.freeze
|
10
14
|
|
11
15
|
def initialize
|
12
|
-
@dependencies =
|
16
|
+
@dependencies = SortedSet.new
|
13
17
|
end
|
14
18
|
|
15
19
|
def add(dependency)
|
16
|
-
@dependencies
|
20
|
+
@dependencies << dependency
|
17
21
|
end
|
18
22
|
|
19
|
-
def
|
20
|
-
|
23
|
+
def each
|
24
|
+
@dependencies.each do |dependency|
|
25
|
+
yield dependency
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def to(format, formats: FORMATS)
|
30
|
+
public_send(formats.fetch(format&.to_sym, :to_json))
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_table
|
34
|
+
Table.new do |table|
|
35
|
+
map do |dependency|
|
36
|
+
table << dependency
|
37
|
+
end
|
38
|
+
end
|
21
39
|
end
|
22
40
|
|
23
41
|
def to_h
|
24
42
|
{ version: '1.0', dependencies: [] }.tap do |report|
|
25
|
-
|
43
|
+
each do |dependency|
|
26
44
|
report[:dependencies].push(dependency.to_h)
|
27
45
|
end
|
28
46
|
end
|
@@ -31,6 +49,12 @@ module Spandx
|
|
31
49
|
def to_json(*_args)
|
32
50
|
JSON.pretty_generate(to_h)
|
33
51
|
end
|
52
|
+
|
53
|
+
def to_csv
|
54
|
+
map do |dependency|
|
55
|
+
CSV.generate_line(dependency.to_a)
|
56
|
+
end
|
57
|
+
end
|
34
58
|
end
|
35
59
|
end
|
36
60
|
end
|