spandx 0.13.1 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -2
  3. data/exe/spandx +0 -1
  4. data/ext/spandx/spandx.c +6 -4
  5. data/lib/spandx.rb +1 -1
  6. data/lib/spandx/cli.rb +2 -1
  7. data/lib/spandx/cli/commands/scan.rb +15 -32
  8. data/lib/spandx/cli/main.rb +3 -3
  9. data/lib/spandx/cli/printer.rb +27 -0
  10. data/lib/spandx/cli/printers/csv.rb +17 -0
  11. data/lib/spandx/cli/printers/json.rb +17 -0
  12. data/lib/spandx/cli/printers/table.rb +41 -0
  13. data/lib/spandx/core/dependency.rb +48 -13
  14. data/lib/spandx/core/git.rb +6 -8
  15. data/lib/spandx/core/guess.rb +12 -1
  16. data/lib/spandx/core/http.rb +7 -7
  17. data/lib/spandx/core/index_file.rb +2 -0
  18. data/lib/spandx/core/license_plugin.rb +15 -4
  19. data/lib/spandx/core/parser.rb +10 -3
  20. data/lib/spandx/core/path_traversal.rb +4 -13
  21. data/lib/spandx/core/plugin.rb +6 -0
  22. data/lib/spandx/core/thread_pool.rb +11 -11
  23. data/lib/spandx/dotnet/nuget_gateway.rb +1 -1
  24. data/lib/spandx/dotnet/parsers/csproj.rb +7 -7
  25. data/lib/spandx/dotnet/parsers/packages_config.rb +7 -7
  26. data/lib/spandx/dotnet/parsers/sln.rb +10 -13
  27. data/lib/spandx/dotnet/project_file.rb +3 -3
  28. data/lib/spandx/java/parsers/maven.rb +7 -7
  29. data/lib/spandx/js/parsers/npm.rb +8 -8
  30. data/lib/spandx/js/parsers/yarn.rb +7 -7
  31. data/lib/spandx/js/yarn_pkg.rb +1 -1
  32. data/lib/spandx/os/parsers/apk.rb +51 -0
  33. data/lib/spandx/php/packagist_gateway.rb +1 -1
  34. data/lib/spandx/php/parsers/composer.rb +7 -7
  35. data/lib/spandx/python/parsers/pipfile_lock.rb +4 -4
  36. data/lib/spandx/python/pypi.rb +19 -9
  37. data/lib/spandx/python/source.rb +13 -1
  38. data/lib/spandx/ruby/gateway.rb +1 -1
  39. data/lib/spandx/ruby/parsers/gemfile_lock.rb +10 -9
  40. data/lib/spandx/spdx/catalogue.rb +1 -1
  41. data/lib/spandx/version.rb +1 -1
  42. data/spandx.gemspec +5 -3
  43. metadata +43 -14
  44. data/lib/spandx/core/concurrent.rb +0 -40
  45. data/lib/spandx/core/line_io.rb +0 -23
  46. data/lib/spandx/core/report.rb +0 -60
  47. data/lib/spandx/core/table.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 288989db458fbe8da46cf782ab6d0410dbcfe5f167a4fa258b2fb0bdd819b2c3
4
- data.tar.gz: 3df236283d2d8149bb9c6c9633969540147cd31cc6dfdabb4c26b56d3a6d5b92
3
+ metadata.gz: 7e01f7023f4a164fb867c7d457769d1c9dd2eb2b480cee88c5d4d682c2d6dc4e
4
+ data.tar.gz: f202f85c254d11041b79e1305d12641eb66ea66cec9afea25a38e9724a5636d6
5
5
  SHA512:
6
- metadata.gz: 7c6bb83298a7298814f484236df79234aac9710cf990eb728db53465e1ec82943085a9057bc7d29e8f49ce74e76a6229606f1392bb13835db13ebbbfa18f173d
7
- data.tar.gz: a95189cec4d7b0d5f4a6e8007bb79b805fe5072bf5a502c995140cfa5b495fb774e9e36fc2c6dbc17190ce8641fc9eb4ca1485e8dd033c70fce7960382a5d346
6
+ metadata.gz: 926df592dfc76466a7e26bcdfd9fc581957b2c748c9272e30a22180a61f4d6498ebb14e04e8acbbccc813f4aae929ecb8ba82ee54064d642990e5874b05bb0b1
7
+ data.tar.gz: 5dede807761bf9d4fa91f6a0ea9df1bec9531d0a397fedea5c687b7c12860216e41df47c09fc7e94c8951e7dc0f88c99fc74395913002890b199409b700830f0
@@ -1,4 +1,4 @@
1
- Version 0.13.1
1
+ Version 0.14.0
2
2
 
3
3
  # Changelog
4
4
 
@@ -9,6 +9,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.14.0] - 2020-11-14
13
+ ### Added
14
+ - Parse `/lib/apk/db/installed` file.
15
+
16
+ ## [0.13.5] - 2020-05-26
17
+ ### Fixed
18
+ - Process PyPI package urls with single digit versions.
19
+ - Remove unsupported `hash` report from help text.
20
+
21
+ ### Changed
22
+ - Stream output to output stream as soon as results are available.
23
+ - Switch to `Oj` for JSON parsing.
24
+ - Run spinner on background thread.
25
+
26
+ ## [0.13.4] - 2020-05-26
27
+ ### Added
28
+ - Add detected file path to report output.
29
+
30
+ ### Changed
31
+ - Use `Pathname` instead of `String` to represent file paths.
32
+ - Scan current directory when a path is not specified.
33
+
34
+ ## [0.13.3] - 2020-05-19
35
+ ### Fixed
36
+ - Ignore invalid URLs during scan.
37
+
38
+ ## [0.13.2] - 2020-05-17
39
+ ### Fixed
40
+ - Detect licenses when provided as an array.
41
+ - Skip empty lockfiles.
42
+
12
43
  ## [0.13.1] - 2020-05-16
13
44
  ### Fixed
14
45
  - Add `ext/**/*.c` and `ext/**/*.h` to list of files.
@@ -172,7 +203,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
172
203
  ### Added
173
204
  - Provide ruby API to the latest SPDX catalogue.
174
205
 
175
- [Unreleased]: https://github.com/spandx/spandx/compare/v0.13.0...HEAD
206
+ [Unreleased]: https://github.com/spandx/spandx/compare/v0.14.0...HEAD
207
+ [0.14.0]: https://github.com/spandx/spandx/compare/v0.13.5...v0.14.0
208
+ [0.13.5]: https://github.com/spandx/spandx/compare/v0.13.4...v0.13.5
209
+ [0.13.4]: https://github.com/spandx/spandx/compare/v0.13.3...v0.13.4
210
+ [0.13.3]: https://github.com/spandx/spandx/compare/v0.13.2...v0.13.3
211
+ [0.13.2]: https://github.com/spandx/spandx/compare/v0.13.1...v0.13.2
212
+ [0.13.1]: https://github.com/spandx/spandx/compare/v0.13.0...v0.13.1
176
213
  [0.13.0]: https://github.com/spandx/spandx/compare/v0.12.3...v0.13.0
177
214
  [0.12.3]: https://github.com/spandx/spandx/compare/v0.12.2...v0.12.3
178
215
  [0.12.2]: https://github.com/spandx/spandx/compare/v0.12.1...v0.12.2
data/exe/spandx CHANGED
@@ -4,7 +4,6 @@
4
4
  require 'spandx'
5
5
 
6
6
  Signal.trap('INT') do
7
- warn("\n#{caller.join("\n")}: interrupted")
8
7
  exit(1)
9
8
  end
10
9
 
@@ -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
- static VALUE parse(VALUE self, VALUE line)
14
+ VALUE parse(VALUE self, VALUE line)
13
15
  {
14
16
  if (NIL_P(line)) return Qnil;
15
17
 
@@ -20,7 +22,7 @@ static VALUE parse(VALUE self, VALUE line)
20
22
 
21
23
  const VALUE items = rb_ary_new2(3);
22
24
  const char *s, *n;
23
- const int len = RSTRING_LEN(line);
25
+ const long len = RSTRING_LEN(line);
24
26
  enum { open, closed } state = closed;
25
27
 
26
28
  for (int i = 0; i < len && *p; i++) {
@@ -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 == 10) {
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;
@@ -8,11 +8,11 @@ require 'json'
8
8
  require 'logger'
9
9
  require 'net/hippie'
10
10
  require 'nokogiri'
11
+ require 'oj'
11
12
  require 'parslet'
12
13
  require 'pathname'
13
14
  require 'yaml'
14
15
  require 'zeitwerk'
15
-
16
16
  require 'spandx/spandx'
17
17
 
18
18
  loader = Zeitwerk::Loader.for_gem
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
- require 'tty-progressbar'
4
+ require 'tty-spinner'
5
+ require 'terminal-table'
5
6
 
6
7
  module Spandx
7
8
  module Cli
@@ -4,10 +4,7 @@ module Spandx
4
4
  module Cli
5
5
  module Commands
6
6
  class Scan
7
- NULL_BAR = Class.new do
8
- def advance(*args); end
9
- end.new
10
-
7
+ include Spandx::Core
11
8
  attr_reader :scan_path
12
9
 
13
10
  def initialize(scan_path, options)
@@ -17,32 +14,24 @@ module Spandx
17
14
  end
18
15
 
19
16
  def execute(output: $stdout)
20
- Spandx::Core::ThreadPool.open do |pool|
21
- report = ::Spandx::Core::Report.new
22
- each_file do |file|
23
- each_dependency_from(file, pool) do |dependency|
24
- report.add(dependency)
25
- end
17
+ with_printer(output) do |printer|
18
+ each_dependency do |dependency|
19
+ printer.print_line(Plugin.enhance(dependency), output)
26
20
  end
27
- output.puts(format(report.to(@options[:format])))
28
21
  end
29
22
  end
30
23
 
31
24
  private
32
25
 
33
26
  def each_file
34
- Spandx::Core::PathTraversal
35
- .new(scan_path, recursive: @options['recursive'])
27
+ PathTraversal
28
+ .new(scan_path, recursive: @options[:recursive])
36
29
  .each { |file| yield file }
37
30
  end
38
31
 
39
- def each_dependency_from(file, pool)
40
- dependencies = ::Spandx::Core::Parser.for(file).parse(file)
41
- with_progress(title_for(file), dependencies.size) do |bar|
42
- ::Spandx::Core::Concurrent
43
- .map(dependencies, pool: pool) { |dependency| enhance(dependency) }
44
- .each do |dependency|
45
- bar.advance(1)
32
+ def each_dependency
33
+ each_file do |file|
34
+ Parser.parse(file).each do |dependency|
46
35
  yield dependency
47
36
  end
48
37
  end
@@ -52,18 +41,12 @@ module Spandx
52
41
  Array(output).map(&:to_s)
53
42
  end
54
43
 
55
- def enhance(dependency)
56
- ::Spandx::Core::Plugin
57
- .all
58
- .inject(dependency) { |memo, plugin| plugin.enhance(memo) }
59
- end
60
-
61
- def title_for(file)
62
- "#{file} [:bar, :elapsed] :percent"
63
- end
64
-
65
- def with_progress(title, total)
66
- yield @options[:show_progress] ? TTY::ProgressBar.new(title, total: total) : NULL_BAR
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)
67
50
  end
68
51
  end
69
52
  end
@@ -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
- method_option :show_progress, aliases: '-sp', type: :boolean, desc: 'Shows a progress bar', default: true
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,41 @@
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
10
+ @spinner = TTY::Spinner.new(output: $stderr)
11
+ end
12
+
13
+ def match?(format)
14
+ format.to_sym == :table
15
+ end
16
+
17
+ def print_header(_io)
18
+ @dependencies = SortedSet.new
19
+ @spinner.auto_spin
20
+ end
21
+
22
+ def print_line(dependency, _io)
23
+ @dependencies << dependency
24
+ end
25
+
26
+ def print_footer(io)
27
+ @spinner.stop
28
+ io.puts(to_table(@dependencies.map(&:to_a)))
29
+ end
30
+
31
+ private
32
+
33
+ def to_table(rows)
34
+ Terminal::Table.new(headings: HEADINGS) do |table|
35
+ rows.each { |row| table.add_row(row) }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -3,46 +3,81 @@
3
3
  module Spandx
4
4
  module Core
5
5
  class Dependency
6
- attr_reader :package_manager, :name, :version, :licenses, :meta
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(package_manager:, name:, version:, licenses: [], meta: {})
9
- @package_manager = package_manager
10
- @name = name
11
- @version = version
12
- @licenses = 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 managed_by?(value)
17
- package_manager == value&.to_sym
28
+ def package_manager
29
+ PACKAGE_MANAGERS[Parser.for(path).class]
18
30
  end
19
31
 
20
32
  def <=>(other)
21
- to_s <=> other.to_s
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
- "#<Spandx::Core::Dependency name=#{name}, version=#{version}>"
57
+ "#<#{self.class} name=#{name} version=#{version} path=#{relative_path}>"
38
58
  end
39
59
 
40
60
  def to_a
41
- [name, version, licenses.map(&:id)]
61
+ [name, version, license_expression, relative_path.to_s]
42
62
  end
43
63
 
44
64
  def to_h
45
- { name: name, version: version, licenses: licenses.map(&:id) }
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
@@ -11,9 +11,8 @@ module Spandx
11
11
  end
12
12
 
13
13
  def read(path)
14
- full_path = File.join(root, path)
15
-
16
- IO.read(full_path) if File.exist?(full_path)
14
+ full_path = root.join(path)
15
+ full_path.read if full_path.exist?
17
16
  end
18
17
 
19
18
  def update!
@@ -25,15 +24,16 @@ module Spandx
25
24
  def path_for(url)
26
25
  uri = URI.parse(url)
27
26
  name = uri.path.gsub(/\.git$/, '')
28
- File.expand_path(File.join(Dir.home, '.local', 'share', name))
27
+ Pathname(File.expand_path(File.join(Dir.home, '.local', 'share', name)))
29
28
  end
30
29
 
31
30
  def dotgit?
32
- File.directory?(File.join(root, '.git'))
31
+ root.join('.git').directory?
33
32
  end
34
33
 
35
34
  def clone!
36
- system('git', 'clone', '--quiet', '--depth=1', '--single-branch', '--branch', 'master', url, root)
35
+ system('rm', '-rf', root.to_s) if root.exist?
36
+ system('git', 'clone', '--quiet', '--depth=1', '--single-branch', '--branch', 'master', url, root.to_s)
37
37
  end
38
38
 
39
39
  def pull!
@@ -42,7 +42,5 @@ module Spandx
42
42
  end
43
43
  end
44
44
  end
45
-
46
- Database = Git
47
45
  end
48
46
  end