spandx 0.13.3 → 0.15.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -2
- data/exe/spandx +0 -1
- data/ext/spandx/spandx.c +7 -3
- data/lib/spandx.rb +1 -1
- data/lib/spandx/cli.rb +2 -2
- data/lib/spandx/cli/commands/pull.rb +33 -4
- data/lib/spandx/cli/commands/scan.rb +19 -22
- data/lib/spandx/cli/main.rb +3 -3
- data/lib/spandx/cli/printer.rb +27 -0
- data/lib/spandx/cli/printers/csv.rb +17 -0
- data/lib/spandx/cli/printers/json.rb +17 -0
- data/lib/spandx/cli/printers/table.rb +42 -0
- data/lib/spandx/core/dependency.rb +48 -13
- data/lib/spandx/core/git.rb +6 -6
- data/lib/spandx/core/http.rb +6 -6
- data/lib/spandx/core/license_plugin.rb +10 -4
- data/lib/spandx/core/parser.rb +9 -4
- data/lib/spandx/core/path_traversal.rb +4 -13
- data/lib/spandx/core/plugin.rb +6 -0
- data/lib/spandx/core/thread_pool.rb +49 -0
- data/lib/spandx/dotnet/nuget_gateway.rb +1 -1
- data/lib/spandx/dotnet/parsers/csproj.rb +7 -7
- data/lib/spandx/dotnet/parsers/packages_config.rb +7 -7
- data/lib/spandx/dotnet/parsers/sln.rb +10 -13
- data/lib/spandx/dotnet/project_file.rb +3 -3
- data/lib/spandx/java/parsers/maven.rb +7 -7
- data/lib/spandx/js/parsers/npm.rb +8 -8
- data/lib/spandx/js/parsers/yarn.rb +7 -7
- data/lib/spandx/js/yarn_pkg.rb +1 -1
- data/lib/spandx/os/parsers/apk.rb +51 -0
- data/lib/spandx/os/parsers/dpkg.rb +69 -0
- data/lib/spandx/php/packagist_gateway.rb +1 -1
- data/lib/spandx/php/parsers/composer.rb +7 -7
- data/lib/spandx/python/parsers/pipfile_lock.rb +4 -4
- data/lib/spandx/python/pypi.rb +19 -7
- data/lib/spandx/python/source.rb +1 -1
- data/lib/spandx/ruby/gateway.rb +1 -1
- data/lib/spandx/ruby/parsers/gemfile_lock.rb +10 -9
- data/lib/spandx/spdx/catalogue.rb +1 -1
- data/lib/spandx/version.rb +1 -1
- data/spandx.gemspec +5 -4
- metadata +38 -20
- data/lib/spandx/core/report.rb +0 -60
- data/lib/spandx/core/spinner.rb +0 -51
- data/lib/spandx/core/table.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 120ae97e54f2a138b7d1053d9e71c8b702174cf70fbac0fc861b8971f4aec0bd
|
4
|
+
data.tar.gz: c89d3ffe76d00d0a6fb18e91b5cdd3ef1487a826047213dd264be3d59796d188
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab85f497f8ce4fe46a03b31d25ffa07816975dcbcb91a1438cf09bcd38f857ed652a1003569e2a8cbb091c5fb5b5b88a52043f4a61eec71337eff58107c75737
|
7
|
+
data.tar.gz: e73a923228358065c7d67bdfa8aaea328657269758f2c2d664621b5e6dd6da449b1d585655ae351e0edd99dbd155ef2efa24f0e37c95cd24c75f85da517503af
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Version 0.
|
1
|
+
Version 0.15.1
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
@@ -9,6 +9,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
9
9
|
|
10
10
|
## [Unreleased]
|
11
11
|
|
12
|
+
## [0.15.1] - 2020-11-18
|
13
|
+
### Fixed
|
14
|
+
- Rebuild index after pulling latest cache.
|
15
|
+
|
16
|
+
## [0.15.0] - 2020-11-18
|
17
|
+
### Added
|
18
|
+
- Parse `/var/lib/dpkg/status` file.
|
19
|
+
|
20
|
+
## [0.14.0] - 2020-11-14
|
21
|
+
### Added
|
22
|
+
- Parse `/lib/apk/db/installed` file.
|
23
|
+
|
24
|
+
## [0.13.5] - 2020-05-26
|
25
|
+
### Fixed
|
26
|
+
- Process PyPI package urls with single digit versions.
|
27
|
+
- Remove unsupported `hash` report from help text.
|
28
|
+
|
29
|
+
### Changed
|
30
|
+
- Stream output to output stream as soon as results are available.
|
31
|
+
- Switch to `Oj` for JSON parsing.
|
32
|
+
- Run spinner on background thread.
|
33
|
+
|
34
|
+
## [0.13.4] - 2020-05-26
|
35
|
+
### Added
|
36
|
+
- Add detected file path to report output.
|
37
|
+
|
38
|
+
### Changed
|
39
|
+
- Use `Pathname` instead of `String` to represent file paths.
|
40
|
+
- Scan current directory when a path is not specified.
|
41
|
+
|
12
42
|
## [0.13.3] - 2020-05-19
|
13
43
|
### Fixed
|
14
44
|
- Ignore invalid URLs during scan.
|
@@ -181,7 +211,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
181
211
|
### Added
|
182
212
|
- Provide ruby API to the latest SPDX catalogue.
|
183
213
|
|
184
|
-
[Unreleased]: https://github.com/spandx/spandx/compare/v0.
|
214
|
+
[Unreleased]: https://github.com/spandx/spandx/compare/v0.15.1...HEAD
|
215
|
+
[0.15.1]: https://github.com/spandx/spandx/compare/v0.15.0...v0.15.1
|
216
|
+
[0.15.0]: https://github.com/spandx/spandx/compare/v0.14.0...v0.15.0
|
217
|
+
[0.14.0]: https://github.com/spandx/spandx/compare/v0.13.5...v0.14.0
|
218
|
+
[0.13.5]: https://github.com/spandx/spandx/compare/v0.13.4...v0.13.5
|
219
|
+
[0.13.4]: https://github.com/spandx/spandx/compare/v0.13.3...v0.13.4
|
185
220
|
[0.13.3]: https://github.com/spandx/spandx/compare/v0.13.2...v0.13.3
|
186
221
|
[0.13.2]: https://github.com/spandx/spandx/compare/v0.13.1...v0.13.2
|
187
222
|
[0.13.1]: https://github.com/spandx/spandx/compare/v0.13.0...v0.13.1
|
data/exe/spandx
CHANGED
data/ext/spandx/spandx.c
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
#include "spandx.h"
|
2
2
|
|
3
|
+
#define NEWLINE 10
|
4
|
+
|
3
5
|
VALUE rb_mSpandx;
|
4
6
|
VALUE rb_mCore;
|
5
7
|
VALUE rb_mCsvParser;
|
@@ -9,7 +11,7 @@ VALUE rb_mCsvParser;
|
|
9
11
|
// "name","version","license"\r
|
10
12
|
// "name","version","license"\r\n
|
11
13
|
// "name","version",""\r\n
|
12
|
-
|
14
|
+
VALUE parse(VALUE self, VALUE line)
|
13
15
|
{
|
14
16
|
if (NIL_P(line)) return Qnil;
|
15
17
|
|
@@ -32,13 +34,13 @@ static VALUE parse(VALUE self, VALUE line)
|
|
32
34
|
s = n;
|
33
35
|
state = open;
|
34
36
|
} else if (state == open) {
|
35
|
-
if (!*n || n == p || *n == ',' || *n ==
|
37
|
+
if (!*n || n == p || *n == ',' || *n == NEWLINE) {
|
36
38
|
rb_ary_push(items, rb_str_new(s, p - s));
|
37
39
|
state = closed;
|
38
40
|
}
|
39
41
|
}
|
40
42
|
}
|
41
|
-
*p
|
43
|
+
*(p++);
|
42
44
|
}
|
43
45
|
|
44
46
|
return items;
|
@@ -50,4 +52,6 @@ void Init_spandx(void)
|
|
50
52
|
rb_mCore = rb_define_module_under(rb_mSpandx, "Core");
|
51
53
|
rb_mCsvParser = rb_define_module_under(rb_mCore, "CsvParser");
|
52
54
|
rb_define_module_function(rb_mCsvParser, "parse", parse, 1);
|
55
|
+
|
56
|
+
rb_gc_register_mark_object(rb_mCsvParser);
|
53
57
|
}
|
data/lib/spandx.rb
CHANGED
data/lib/spandx/cli.rb
CHANGED
@@ -8,12 +8,41 @@ module Spandx
|
|
8
8
|
@options = options
|
9
9
|
end
|
10
10
|
|
11
|
-
def execute(output: $
|
11
|
+
def execute(output: $stderr)
|
12
|
+
sync(output)
|
13
|
+
build(output, ::Spandx::Core::Dependency::PACKAGE_MANAGERS.values.uniq)
|
14
|
+
output.puts 'OK'
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def sync(output)
|
12
20
|
Spandx.git.each_value do |db|
|
13
|
-
|
14
|
-
|
21
|
+
with_spinner("Updating #{db.url}...", output: output) do
|
22
|
+
db.update!
|
23
|
+
end
|
15
24
|
end
|
16
|
-
|
25
|
+
end
|
26
|
+
|
27
|
+
def build(output, sources)
|
28
|
+
index_path = Spandx.git[:cache].root.join('.index')
|
29
|
+
|
30
|
+
with_spinner('Rebuilding index...', output: output) do
|
31
|
+
sources.each do |source|
|
32
|
+
Spandx::Core::Cache
|
33
|
+
.new(source, root: index_path)
|
34
|
+
.rebuild_index
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def with_spinner(message, output:)
|
40
|
+
spinner = TTY::Spinner.new("[:spinner] #{message}", output: output)
|
41
|
+
spinner.auto_spin
|
42
|
+
yield
|
43
|
+
spinner.success('(done)')
|
44
|
+
ensure
|
45
|
+
spinner.stop
|
17
46
|
end
|
18
47
|
end
|
19
48
|
end
|
@@ -4,52 +4,49 @@ module Spandx
|
|
4
4
|
module Cli
|
5
5
|
module Commands
|
6
6
|
class Scan
|
7
|
-
|
7
|
+
include Spandx::Core
|
8
|
+
attr_reader :scan_path
|
8
9
|
|
9
10
|
def initialize(scan_path, options)
|
10
11
|
@scan_path = ::Pathname.new(scan_path)
|
11
12
|
@options = options
|
12
|
-
@spinner = options[:show_progress] ? ::Spandx::Core::Spinner.new : ::Spandx::Core::Spinner::NULL
|
13
13
|
require(options[:require]) if options[:require]
|
14
14
|
end
|
15
15
|
|
16
16
|
def execute(output: $stdout)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
each_dependency_from(file) do |dependency|
|
21
|
-
spinner.spin(file)
|
22
|
-
report.add(dependency)
|
17
|
+
with_printer(output) do |printer|
|
18
|
+
each_dependency do |dependency|
|
19
|
+
printer.print_line(Plugin.enhance(dependency), output)
|
23
20
|
end
|
24
21
|
end
|
25
|
-
spinner.stop
|
26
|
-
output.puts(format(report.to(@options[:format])))
|
27
22
|
end
|
28
23
|
|
29
24
|
private
|
30
25
|
|
31
26
|
def each_file
|
32
|
-
|
33
|
-
.new(scan_path, recursive: @options[
|
27
|
+
PathTraversal
|
28
|
+
.new(scan_path, recursive: @options[:recursive])
|
34
29
|
.each { |file| yield file }
|
35
30
|
end
|
36
31
|
|
37
|
-
def
|
38
|
-
|
39
|
-
.
|
40
|
-
|
41
|
-
|
42
|
-
|
32
|
+
def each_dependency
|
33
|
+
each_file do |file|
|
34
|
+
Parser.parse(file).each do |dependency|
|
35
|
+
yield dependency
|
36
|
+
end
|
37
|
+
end
|
43
38
|
end
|
44
39
|
|
45
40
|
def format(output)
|
46
41
|
Array(output).map(&:to_s)
|
47
42
|
end
|
48
43
|
|
49
|
-
def
|
50
|
-
::Spandx::
|
51
|
-
|
52
|
-
|
44
|
+
def with_printer(output)
|
45
|
+
printer = ::Spandx::Cli::Printer.for(@options[:format])
|
46
|
+
printer.print_header(output)
|
47
|
+
yield printer
|
48
|
+
ensure
|
49
|
+
printer.print_footer(output)
|
53
50
|
end
|
54
51
|
end
|
55
52
|
end
|
data/lib/spandx/cli/main.rb
CHANGED
@@ -8,14 +8,14 @@ module Spandx
|
|
8
8
|
method_option :recursive, aliases: '-R', type: :boolean, desc: 'Perform recursive scan', default: false
|
9
9
|
method_option :airgap, aliases: '-a', type: :boolean, desc: 'Disable network connections', default: false
|
10
10
|
method_option :logfile, aliases: '-l', type: :string, desc: 'Path to a logfile', default: '/dev/null'
|
11
|
-
method_option :format, aliases: '-f', type: :string, desc: 'Format of report', default: 'table'
|
11
|
+
method_option :format, aliases: '-f', type: :string, desc: 'Format of report. (table, csv, json)', default: 'table'
|
12
12
|
method_option :pull, aliases: '-p', type: :boolean, desc: 'Pull the latest cache before the scan', default: false
|
13
13
|
method_option :require, aliases: '-r', type: :string, desc: 'Causes spandx to load the library using require.', default: nil
|
14
|
-
|
15
|
-
def scan(lockfile)
|
14
|
+
def scan(lockfile = Pathname.pwd)
|
16
15
|
if options[:help]
|
17
16
|
invoke :help, ['scan']
|
18
17
|
else
|
18
|
+
Oj.default_options = { mode: :strict }
|
19
19
|
Spandx.airgap = options[:airgap]
|
20
20
|
Spandx.logger = Logger.new(options[:logfile])
|
21
21
|
pull if options[:pull]
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spandx
|
4
|
+
module Cli
|
5
|
+
class Printer
|
6
|
+
def match?(_format)
|
7
|
+
raise ::Spandx::Error, :match?
|
8
|
+
end
|
9
|
+
|
10
|
+
def print_header(io); end
|
11
|
+
|
12
|
+
def print_line(dependency, io)
|
13
|
+
io.puts(dependency.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def print_footer(io); end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
include Core::Registerable
|
20
|
+
|
21
|
+
def for(format)
|
22
|
+
find { |x| x.match?(format) } || new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spandx
|
4
|
+
module Cli
|
5
|
+
module Printers
|
6
|
+
class Csv < Printer
|
7
|
+
def match?(format)
|
8
|
+
format.to_sym == :csv
|
9
|
+
end
|
10
|
+
|
11
|
+
def print_line(dependency, io)
|
12
|
+
io.puts(CSV.generate_line(dependency.to_a))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spandx
|
4
|
+
module Cli
|
5
|
+
module Printers
|
6
|
+
class Json < Printer
|
7
|
+
def match?(format)
|
8
|
+
format.to_sym == :json
|
9
|
+
end
|
10
|
+
|
11
|
+
def print_line(dependency, io)
|
12
|
+
io.puts(Oj.dump(dependency.to_h))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spandx
|
4
|
+
module Cli
|
5
|
+
module Printers
|
6
|
+
class Table < Printer
|
7
|
+
HEADINGS = ['Name', 'Version', 'Licenses', 'Location'].freeze
|
8
|
+
|
9
|
+
def initialize(output: $stderr)
|
10
|
+
@spinner = TTY::Spinner.new('[:spinner] Scanning...', output: output, clear: true, format: :dots)
|
11
|
+
@spinner.auto_spin
|
12
|
+
end
|
13
|
+
|
14
|
+
def match?(format)
|
15
|
+
format.to_sym == :table
|
16
|
+
end
|
17
|
+
|
18
|
+
def print_header(_io)
|
19
|
+
@dependencies = SortedSet.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def print_line(dependency, _io)
|
23
|
+
@dependencies << dependency
|
24
|
+
end
|
25
|
+
|
26
|
+
def print_footer(io)
|
27
|
+
@spinner.stop
|
28
|
+
@spinner.reset
|
29
|
+
io.puts(to_table(@dependencies.map(&:to_a)))
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def to_table(rows)
|
35
|
+
Terminal::Table.new(headings: HEADINGS) do |table|
|
36
|
+
rows.each { |row| table.add_row(row) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -3,46 +3,81 @@
|
|
3
3
|
module Spandx
|
4
4
|
module Core
|
5
5
|
class Dependency
|
6
|
-
|
6
|
+
PACKAGE_MANAGERS = {
|
7
|
+
Spandx::Dotnet::Parsers::Csproj => :nuget,
|
8
|
+
Spandx::Dotnet::Parsers::PackagesConfig => :nuget,
|
9
|
+
Spandx::Dotnet::Parsers::Sln => :nuget,
|
10
|
+
Spandx::Java::Parsers::Maven => :maven,
|
11
|
+
Spandx::Js::Parsers::Npm => :npm,
|
12
|
+
Spandx::Js::Parsers::Yarn => :yarn,
|
13
|
+
Spandx::Php::Parsers::Composer => :composer,
|
14
|
+
Spandx::Python::Parsers::PipfileLock => :pypi,
|
15
|
+
Spandx::Ruby::Parsers::GemfileLock => :rubygems,
|
16
|
+
Spandx::Os::Parsers::Apk => :apk,
|
17
|
+
}.freeze
|
18
|
+
attr_reader :path, :name, :version, :licenses, :meta
|
7
19
|
|
8
|
-
def initialize(
|
9
|
-
@
|
10
|
-
@name = name
|
11
|
-
@version = version
|
12
|
-
@licenses =
|
20
|
+
def initialize(name:, version:, path:, meta: {})
|
21
|
+
@path = Pathname.new(path).realpath
|
22
|
+
@name = name || @path.basename.to_s
|
23
|
+
@version = version || @path.mtime.to_i.to_s
|
24
|
+
@licenses = []
|
13
25
|
@meta = meta
|
14
26
|
end
|
15
27
|
|
16
|
-
def
|
17
|
-
|
28
|
+
def package_manager
|
29
|
+
PACKAGE_MANAGERS[Parser.for(path).class]
|
18
30
|
end
|
19
31
|
|
20
32
|
def <=>(other)
|
21
|
-
|
33
|
+
return 1 if other.nil?
|
34
|
+
|
35
|
+
score = (name <=> other.name)
|
36
|
+
score = score.zero? ? (version <=> other&.version) : score
|
37
|
+
score.zero? ? (path.to_s <=> other&.path.to_s) : score
|
22
38
|
end
|
23
39
|
|
24
40
|
def hash
|
25
41
|
to_s.hash
|
26
42
|
end
|
27
43
|
|
44
|
+
def ==(other)
|
45
|
+
eql?(other)
|
46
|
+
end
|
47
|
+
|
28
48
|
def eql?(other)
|
29
49
|
to_s == other.to_s
|
30
50
|
end
|
31
51
|
|
32
52
|
def to_s
|
33
|
-
@to_s ||= [name, version].compact.join(' ')
|
53
|
+
@to_s ||= [name, version, path].compact.join(' ')
|
34
54
|
end
|
35
55
|
|
36
56
|
def inspect
|
37
|
-
"
|
57
|
+
"#<#{self.class} name=#{name} version=#{version} path=#{relative_path}>"
|
38
58
|
end
|
39
59
|
|
40
60
|
def to_a
|
41
|
-
[name, version,
|
61
|
+
[name, version, license_expression, relative_path.to_s]
|
42
62
|
end
|
43
63
|
|
44
64
|
def to_h
|
45
|
-
{
|
65
|
+
{
|
66
|
+
name: name,
|
67
|
+
version: version,
|
68
|
+
licenses: license_expression,
|
69
|
+
path: relative_path.to_s
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def relative_path(from: Pathname.pwd)
|
76
|
+
path.relative_path_from(from)
|
77
|
+
end
|
78
|
+
|
79
|
+
def license_expression
|
80
|
+
licenses.map(&:id).join(' AND ')
|
46
81
|
end
|
47
82
|
end
|
48
83
|
end
|