spandx 0.13.4 → 0.16.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef9e117562bb153d2bf7a7aa8561244f50f7dcbca8a81300e10279efec45c674
4
- data.tar.gz: '069f96ae764417f3b005ebd8e7fee919c66eee38a63649918f7dea3209a9bc34'
3
+ metadata.gz: 252cdcff5bb25e6699751e6f842159e46a1a19485e889dca067d2120063bd11b
4
+ data.tar.gz: 4a1c66430fd54d64d8fa18a4a015dbe49d94454bd2800369d384b09e1c5fc984
5
5
  SHA512:
6
- metadata.gz: 67ec66d00236b0c4a98bc770e0b33698ecabb6e868b4e1b309c735a0d3cf5288a49393280f0cff2829d8ab5d1005920b16a877bdabdb2a0b63c88ac9f7af7df9
7
- data.tar.gz: 9260aeb08a495fb4f8bd1ba4c2b17fed8e34c2e60e96d5c826c79f7c51d83a8686b4194e060fa77e3b898f1beb17404006dd5370b727d2a7ce98d87ddce41507
6
+ metadata.gz: 3699f961272816332c5c1ec0ab97fc5f769cfeafc80d8058bb03671f8f3eaee89a3ac831ba8c40304e5d63a59c37436ca30472da4eec8bf69496f3875fb793ee
7
+ data.tar.gz: 8b06322b0d3103eadd726a77cf8dfd43759c94d67c628710e50247946112dba479bd0f317c45639f2bf99aed083c665edde36f0c06e2860dc97c6afe1f08cb1d
@@ -1,4 +1,4 @@
1
- Version 0.13.4
1
+ Version 0.16.0
2
2
 
3
3
  # Changelog
4
4
 
@@ -9,6 +9,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.16.0] - 2020-11-19
13
+ ### Changed
14
+ - Pull smaller license cache.
15
+ - Print index files after building them.
16
+
17
+ ## [0.15.1] - 2020-11-18
18
+ ### Fixed
19
+ - Rebuild index after pulling latest cache.
20
+
21
+ ## [0.15.0] - 2020-11-18
22
+ ### Added
23
+ - Parse `/var/lib/dpkg/status` file.
24
+
25
+ ## [0.14.0] - 2020-11-14
26
+ ### Added
27
+ - Parse `/lib/apk/db/installed` file.
28
+
29
+ ## [0.13.5] - 2020-05-26
30
+ ### Fixed
31
+ - Process PyPI package urls with single digit versions.
32
+ - Remove unsupported `hash` report from help text.
33
+
34
+ ### Changed
35
+ - Stream output to output stream as soon as results are available.
36
+ - Switch to `Oj` for JSON parsing.
37
+ - Run spinner on background thread.
38
+
12
39
  ## [0.13.4] - 2020-05-26
13
40
  ### Added
14
41
  - Add detected file path to report output.
@@ -189,7 +216,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
189
216
  ### Added
190
217
  - Provide ruby API to the latest SPDX catalogue.
191
218
 
192
- [Unreleased]: https://github.com/spandx/spandx/compare/v0.13.3...HEAD
219
+ [Unreleased]: https://github.com/spandx/spandx/compare/v0.16.0...HEAD
220
+ [0.16.0]: https://github.com/spandx/spandx/compare/v0.15.1...v0.16.0
221
+ [0.15.1]: https://github.com/spandx/spandx/compare/v0.15.0...v0.15.1
222
+ [0.15.0]: https://github.com/spandx/spandx/compare/v0.14.0...v0.15.0
223
+ [0.14.0]: https://github.com/spandx/spandx/compare/v0.13.5...v0.14.0
224
+ [0.13.5]: https://github.com/spandx/spandx/compare/v0.13.4...v0.13.5
225
+ [0.13.4]: https://github.com/spandx/spandx/compare/v0.13.3...v0.13.4
193
226
  [0.13.3]: https://github.com/spandx/spandx/compare/v0.13.2...v0.13.3
194
227
  [0.13.2]: https://github.com/spandx/spandx/compare/v0.13.1...v0.13.2
195
228
  [0.13.1]: https://github.com/spandx/spandx/compare/v0.13.0...v0.13.1
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
 
@@ -52,4 +52,6 @@ void Init_spandx(void)
52
52
  rb_mCore = rb_define_module_under(rb_mSpandx, "Core");
53
53
  rb_mCsvParser = rb_define_module_under(rb_mCore, "CsvParser");
54
54
  rb_define_module_function(rb_mCsvParser, "parse", parse, 1);
55
+
56
+ rb_gc_register_mark_object(rb_mCsvParser);
55
57
  }
@@ -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
- require 'terminal-table'
16
16
  require 'spandx/spandx'
17
17
 
18
18
  loader = Zeitwerk::Loader.for_gem
@@ -45,7 +45,7 @@ module Spandx
45
45
  @git ||= {
46
46
  cache: ::Spandx::Core::Git.new(url: 'https://github.com/spandx/cache.git'),
47
47
  rubygems: ::Spandx::Core::Git.new(url: 'https://github.com/spandx/rubygems-cache.git'),
48
- spdx: ::Spandx::Core::Git.new(url: 'https://github.com/spdx/license-list-data.git'),
48
+ spdx: ::Spandx::Core::Git.new(url: 'https://github.com/spdx/license-list-data.git', default_branch: 'master'),
49
49
  }
50
50
  end
51
51
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'nanospinner'
4
3
  require 'thor'
5
- require 'tty-screen'
4
+ require 'tty-spinner'
5
+ require 'terminal-table'
6
6
 
7
7
  module Spandx
8
8
  module Cli
@@ -4,17 +4,56 @@ module Spandx
4
4
  module Cli
5
5
  module Commands
6
6
  class Pull
7
+ attr_reader :cache_dir, :rubygems_cache_dir
8
+
7
9
  def initialize(options)
8
10
  @options = options
11
+ @cache_dir = Spandx.git[:cache].root.join('.index')
12
+ @rubygems_cache_dir = Spandx.git[:rubygems].root.join('.index')
9
13
  end
10
14
 
11
- def execute(output: $stdout)
12
- Spandx.git.each_value do |db|
13
- output.puts "Updating #{db.url}..."
14
- db.update!
15
+ def execute(output: $stderr)
16
+ sync(output)
17
+ build(output, ::Spandx::Core::Dependency::PACKAGE_MANAGERS.values.uniq)
18
+ index_files_in(cache_dir, rubygems_cache_dir).each do |item|
19
+ output.puts item.to_s.gsub(Dir.home, '~')
15
20
  end
16
21
  output.puts 'OK'
17
22
  end
23
+
24
+ private
25
+
26
+ def sync(output)
27
+ Spandx.git.each_value do |db|
28
+ with_spinner("Updating #{db.url}...", output: output) do
29
+ db.update!
30
+ end
31
+ end
32
+ end
33
+
34
+ def build(output, sources)
35
+ with_spinner('Building index...', output: output) do
36
+ sources.each do |source|
37
+ Spandx::Core::Cache.new(source, root: cache_dir).rebuild_index
38
+ end
39
+ Spandx::Core::Cache.new(:rubygems, root: rubygems_cache_dir).rebuild_index
40
+ end
41
+ end
42
+
43
+ def with_spinner(message, output:)
44
+ spinner = TTY::Spinner.new("[:spinner] #{message}", output: output)
45
+ spinner.auto_spin
46
+ yield
47
+ spinner.success('(done)')
48
+ rescue StandardError => error
49
+ spinner.error("(#{error.message})")
50
+ ensure
51
+ spinner.stop
52
+ end
53
+
54
+ def index_files_in(*dirs)
55
+ dirs.map { |x| x.glob('**/*.idx') }.flatten.sort
56
+ end
18
57
  end
19
58
  end
20
59
  end
@@ -4,51 +4,49 @@ module Spandx
4
4
  module Cli
5
5
  module Commands
6
6
  class Scan
7
- attr_reader :scan_path, :spinner
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
- report = ::Spandx::Core::Report.new
18
- each_file do |file|
19
- spinner.spin(file)
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
- Spandx::Core::PathTraversal
27
+ PathTraversal
33
28
  .new(scan_path, recursive: @options[:recursive])
34
29
  .each { |file| yield file }
35
30
  end
36
31
 
37
- def each_dependency_from(file)
38
- ::Spandx::Core::Parser
39
- .parse(file)
40
- .map { |x| enhance(x) }
41
- .each { |dependency| yield dependency }
32
+ def each_dependency
33
+ each_file do |file|
34
+ Parser.parse(file).each do |dependency|
35
+ yield dependency
36
+ end
37
+ end
42
38
  end
43
39
 
44
40
  def format(output)
45
41
  Array(output).map(&:to_s)
46
42
  end
47
43
 
48
- def enhance(dependency)
49
- ::Spandx::Core::Plugin
50
- .all
51
- .inject(dependency) { |memo, plugin| plugin.enhance(memo) }
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)
52
50
  end
53
51
  end
54
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. (table, csv, json, hash)', 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
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
@@ -56,6 +56,10 @@ module Spandx
56
56
  @index ||= IndexFile.new(self)
57
57
  end
58
58
 
59
+ def to_s
60
+ absolute_path.to_s
61
+ end
62
+
59
63
  private
60
64
 
61
65
  def to_csv(array)
@@ -13,6 +13,7 @@ module Spandx
13
13
  Spandx::Php::Parsers::Composer => :composer,
14
14
  Spandx::Python::Parsers::PipfileLock => :pypi,
15
15
  Spandx::Ruby::Parsers::GemfileLock => :rubygems,
16
+ Spandx::Os::Parsers::Apk => :apk,
16
17
  }.freeze
17
18
  attr_reader :path, :name, :version, :licenses, :meta
18
19
 
@@ -3,17 +3,17 @@
3
3
  module Spandx
4
4
  module Core
5
5
  class Git
6
- attr_reader :root, :url
6
+ attr_reader :root, :url, :default_branch
7
7
 
8
- def initialize(url:)
8
+ def initialize(url:, default_branch: 'main')
9
9
  @url = url
10
+ @default_branch = default_branch
10
11
  @root = path_for(url)
11
12
  end
12
13
 
13
14
  def read(path)
14
- full_path = File.join(root, path)
15
-
16
- IO.read(full_path) if File.exist?(full_path)
15
+ full_path = root.join(path)
16
+ full_path.read if full_path.exist?
17
17
  end
18
18
 
19
19
  def update!
@@ -25,20 +25,22 @@ module Spandx
25
25
  def path_for(url)
26
26
  uri = URI.parse(url)
27
27
  name = uri.path.gsub(/\.git$/, '')
28
- File.expand_path(File.join(Dir.home, '.local', 'share', name))
28
+ Pathname(File.expand_path(File.join(Dir.home, '.local', 'share', name)))
29
29
  end
30
30
 
31
31
  def dotgit?
32
- File.directory?(File.join(root, '.git'))
32
+ root.join('.git').directory?
33
33
  end
34
34
 
35
- def clone!
36
- system('git', 'clone', '--quiet', '--depth=1', '--single-branch', '--branch', 'master', url, root)
35
+ def clone!(branch: default_branch)
36
+ system('rm', '-rf', root.to_s) if root.exist?
37
+ system('git', 'clone', '--quiet', '--depth=1', '--single-branch', '--branch', branch, url, root.to_s)
37
38
  end
38
39
 
39
- def pull!
40
+ def pull!(remote: 'origin', branch: default_branch)
40
41
  Dir.chdir(root) do
41
- system('git', 'pull', '--no-rebase', '--quiet', 'origin', 'master')
42
+ system('git', 'fetch', '--quiet', '--depth=1', '--prune', '--no-tags', remote)
43
+ system('git', 'checkout', '--quiet', branch)
42
44
  end
43
45
  end
44
46
  end
@@ -36,12 +36,12 @@ module Spandx
36
36
  end
37
37
 
38
38
  def self.default_driver
39
- @default_driver ||= Net::Hippie::Client.new.tap do |client|
40
- client.logger = Spandx.logger
41
- client.open_timeout = 1
42
- client.read_timeout = 5
43
- client.follow_redirects = 3
44
- end
39
+ @default_driver ||= Net::Hippie::Client.new(
40
+ follow_redirects: 3,
41
+ logger: Spandx.logger,
42
+ open_timeout: 1,
43
+ read_timeout: 5
44
+ )
45
45
  end
46
46
 
47
47
  private
@@ -33,7 +33,7 @@ module Spandx
33
33
  end
34
34
 
35
35
  def known?(package_manager)
36
- %i[nuget maven rubygems npm yarn pypi composer].include?(package_manager)
36
+ %i[nuget maven rubygems npm yarn pypi composer apk].include?(package_manager)
37
37
  end
38
38
 
39
39
  def gateway_for(dependency)
@@ -9,6 +9,12 @@ module Spandx
9
9
 
10
10
  class << self
11
11
  include Registerable
12
+
13
+ def enhance(dependency)
14
+ Plugin.all.inject(dependency) do |memo, plugin|
15
+ plugin.enhance(memo)
16
+ end
17
+ end
12
18
  end
13
19
  end
14
20
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ module Core
5
+ class ThreadPool
6
+ def initialize(size: 1)
7
+ @size = size
8
+ @queue = Queue.new
9
+ @pool = size.times.map { start_worker_thread(@queue) }
10
+ end
11
+
12
+ def run(*args, &job)
13
+ @queue.enq([job, args])
14
+ end
15
+
16
+ def done?
17
+ @queue.empty?
18
+ end
19
+
20
+ def shutdown
21
+ @size.times do
22
+ run { throw :exit }
23
+ end
24
+
25
+ @pool.map(&:join)
26
+ end
27
+
28
+ def self.open(**args)
29
+ pool = new(**args)
30
+ yield pool
31
+ ensure
32
+ pool.shutdown
33
+ end
34
+
35
+ private
36
+
37
+ def start_worker_thread(queue)
38
+ Thread.new(queue) do |q|
39
+ catch(:exit) do
40
+ loop do
41
+ job, args = q.deq
42
+ job.call(args)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -69,7 +69,7 @@ module Spandx
69
69
 
70
70
  def fetch_json(url)
71
71
  response = http.get(url)
72
- http.ok?(response) ? JSON.parse(response.body) : {}
72
+ http.ok?(response) ? Oj.load(response.body) : {}
73
73
  end
74
74
 
75
75
  def fetch_xml(url)
@@ -18,8 +18,8 @@ module Spandx
18
18
 
19
19
  private
20
20
 
21
- def each_metadata(file_path)
22
- package_lock = JSON.parse(IO.read(file_path))
21
+ def each_metadata(path)
22
+ package_lock = Oj.load(path.read)
23
23
  package_lock['dependencies'].each do |name, metadata|
24
24
  yield metadata.merge('name' => name)
25
25
  end
@@ -27,7 +27,7 @@ module Spandx
27
27
  response = http.get(uri, escape: false)
28
28
 
29
29
  if http.ok?(response)
30
- json = JSON.parse(response.body)
30
+ json = Oj.load(response.body)
31
31
  json['versions'] ? json['versions'][dependency.version] : json
32
32
  else
33
33
  {}
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ module Os
5
+ module Parsers
6
+ class Apk < ::Spandx::Core::Parser
7
+ def match?(path)
8
+ path.basename.fnmatch?('installed')
9
+ end
10
+
11
+ def parse(lockfile)
12
+ path = lockfile.to_s
13
+
14
+ [].tap do |items|
15
+ lockfile.open(mode: 'r') do |io|
16
+ each_package(io) do |data|
17
+ items.push(map_from(data, path))
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def each_package(io)
26
+ package = {}
27
+
28
+ until io.eof?
29
+ line = io.readline.chomp
30
+ if line.empty?
31
+ yield package
32
+
33
+ package = {}
34
+ else
35
+ line.split(':').tap { |(key, value)| package[key] = value }
36
+ end
37
+ end
38
+ end
39
+
40
+ def map_from(data, path)
41
+ ::Spandx::Core::Dependency.new(
42
+ path: path,
43
+ name: data['P'],
44
+ version: data['V'],
45
+ meta: data.merge('license' => [data['L']])
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ module Os
5
+ module Parsers
6
+ class Dpkg < ::Spandx::Core::Parser
7
+ class LineReader
8
+ attr_reader :io
9
+
10
+ def initialize(io)
11
+ @io = io
12
+ end
13
+
14
+ def each
15
+ yield read_package(io, Hash.new(''), nil) until io.eof?
16
+ end
17
+
18
+ private
19
+
20
+ def read_package(io, package, prev_key)
21
+ return package if io.eof?
22
+
23
+ line = io.readline.chomp
24
+ return package if line.empty?
25
+
26
+ key, value = split(line, prev_key)
27
+ package[key] += value
28
+ read_package(io, package, key)
29
+ end
30
+
31
+ def split(line, prev_key)
32
+ if prev_key && line.start_with?(' ')
33
+ [prev_key, line]
34
+ else
35
+ key, *rest = line.split(':')
36
+ value = rest&.join(':')&.strip
37
+ [key, value]
38
+ end
39
+ end
40
+ end
41
+
42
+ def match?(path)
43
+ path.basename.fnmatch?('status')
44
+ end
45
+
46
+ def parse(lockfile)
47
+ [].tap do |items|
48
+ lockfile.open(mode: 'r') do |io|
49
+ LineReader.new(io).each do |data|
50
+ items.push(map_from(data, lockfile.to_s))
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def map_from(data, path)
59
+ ::Spandx::Core::Dependency.new(
60
+ path: path,
61
+ name: data['Package'],
62
+ version: data['Version'],
63
+ meta: data
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -17,7 +17,7 @@ module Spandx
17
17
  response = http.get("https://repo.packagist.org/p/#{dependency.name}.json")
18
18
  return [] unless http.ok?(response)
19
19
 
20
- json = JSON.parse(response.body)
20
+ json = Oj.load(response.body)
21
21
  json['packages'][dependency.name][dependency.version]['license']
22
22
  end
23
23
  end
@@ -10,7 +10,7 @@ module Spandx
10
10
 
11
11
  def parse(path)
12
12
  items = Set.new
13
- composer_lock = JSON.parse(path.read)
13
+ composer_lock = Oj.load(path.read)
14
14
  composer_lock['packages'].concat(composer_lock['packages-dev']).each do |dependency|
15
15
  items.add(map_from(path, dependency))
16
16
  end
@@ -19,7 +19,7 @@ module Spandx
19
19
  private
20
20
 
21
21
  def dependencies_from(lockfile)
22
- json = JSON.parse(lockfile.read)
22
+ json = Oj.load(lockfile.read)
23
23
  each_dependency(json) do |name, version|
24
24
  yield ::Spandx::Core::Dependency.new(
25
25
  path: lockfile,
@@ -49,19 +49,26 @@ module Spandx
49
49
  end
50
50
 
51
51
  def version_from(url)
52
- path = SUBSTITUTIONS.inject(URI.parse(url).path.split('/')[-1]) do |memo, item|
53
- memo.gsub(item, '')
54
- end
55
-
52
+ path = cleanup(url)
56
53
  return if path.rindex('-').nil?
57
54
 
58
- path.scan(/-\d+\..*/)[-1][1..-1]
55
+ section = path.scan(/-\d+\..*/)
56
+ section = path.scan(/-\d+\.?.*/) if section.empty?
57
+ section[-1][1..-1]
58
+ rescue StandardError => error
59
+ warn([url, error].inspect)
59
60
  end
60
61
 
61
62
  private
62
63
 
63
64
  attr_reader :http
64
65
 
66
+ def cleanup(url)
67
+ SUBSTITUTIONS.inject(URI.parse(url).path.split('/')[-1]) do |memo, item|
68
+ memo.gsub(item, '')
69
+ end
70
+ end
71
+
65
72
  def sources_for(dependency)
66
73
  return default_sources if dependency.meta.empty?
67
74
 
@@ -76,7 +83,7 @@ module Spandx
76
83
  sources.each do |source|
77
84
  html_from(source, '/simple/').css('a[href*="/simple"]').each do |node|
78
85
  each_version(source, node[:href]) do |dependency|
79
- definition = source.lookup(dependency[:name], dependency[:version])
86
+ definition = source.lookup(dependency[:name], dependency[:version], http: http)
80
87
  yield dependency.merge(license: definition['license'])
81
88
  end
82
89
  end
@@ -93,7 +100,12 @@ module Spandx
93
100
 
94
101
  def html_from(source, path)
95
102
  url = URI.join(source.uri.to_s, path).to_s
96
- Nokogiri::HTML(http.get(url).body)
103
+ response = http.get(url)
104
+ if http.ok?(response)
105
+ Nokogiri::HTML(response.body)
106
+ else
107
+ Nokogiri::HTML('<html><head></head><body></body></html>')
108
+ end
97
109
  end
98
110
  end
99
111
  end
@@ -22,7 +22,7 @@ module Spandx
22
22
  def lookup(name, version, http: Spandx.http)
23
23
  response = http.get(uri_for(name, version))
24
24
  if http.ok?(response)
25
- JSON.parse(response.body)
25
+ Oj.load(response.body)
26
26
  else
27
27
  {}
28
28
  end
@@ -27,7 +27,7 @@ module Spandx
27
27
  end
28
28
 
29
29
  def parse(json)
30
- JSON.parse(json)
30
+ Oj.load(json)
31
31
  end
32
32
  end
33
33
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spandx
4
- VERSION = '0.13.4'
4
+ VERSION = '0.16.0'
5
5
  end
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
 
19
19
  spec.metadata['homepage_uri'] = spec.homepage
20
20
  spec.metadata['source_code_uri'] = 'https://github.com/spandx/spandx'
21
- spec.metadata['changelog_uri'] = 'https://github.com/spandx/spandx/blob/master/CHANGELOG.md'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/spandx/spandx/blob/main/CHANGELOG.md'
22
22
 
23
23
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
24
  Dir.glob('exe/*') +
@@ -34,13 +34,13 @@ Gem::Specification.new do |spec|
34
34
 
35
35
  spec.add_dependency 'addressable', '~> 2.7'
36
36
  spec.add_dependency 'bundler', '>= 1.16', '< 3.0.0'
37
- spec.add_dependency 'nanospinner', '~> 1.0.0'
38
- spec.add_dependency 'net-hippie', '~> 0.3'
37
+ spec.add_dependency 'net-hippie', '~> 1.0'
39
38
  spec.add_dependency 'nokogiri', '~> 1.10'
39
+ spec.add_dependency 'oj', '~> 3.10'
40
40
  spec.add_dependency 'parslet', '~> 2.0'
41
41
  spec.add_dependency 'terminal-table', '~> 1.8'
42
42
  spec.add_dependency 'thor'
43
- spec.add_dependency 'tty-screen', '~> 0.7'
43
+ spec.add_dependency 'tty-spinner', '~> 0.9'
44
44
  spec.add_dependency 'zeitwerk', '~> 2.3'
45
45
 
46
46
  spec.add_development_dependency 'benchmark-ips', '~> 2.8'
@@ -54,6 +54,6 @@ Gem::Specification.new do |spec|
54
54
  spec.add_development_dependency 'rubocop', '~> 0.52'
55
55
  spec.add_development_dependency 'rubocop-rspec', '~> 1.22'
56
56
  spec.add_development_dependency 'ruby-prof', '~> 1.3'
57
- spec.add_development_dependency 'vcr', '~> 5.0'
57
+ spec.add_development_dependency 'vcr', '~> 6.0'
58
58
  spec.add_development_dependency 'webmock', '~> 3.7'
59
59
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spandx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.4
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Can Eldem
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2020-05-26 00:00:00.000000000 Z
12
+ date: 2020-11-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: addressable
@@ -46,47 +46,47 @@ dependencies:
46
46
  - !ruby/object:Gem::Version
47
47
  version: 3.0.0
48
48
  - !ruby/object:Gem::Dependency
49
- name: nanospinner
49
+ name: net-hippie
50
50
  requirement: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 1.0.0
54
+ version: '1.0'
55
55
  type: :runtime
56
56
  prerelease: false
57
57
  version_requirements: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 1.0.0
61
+ version: '1.0'
62
62
  - !ruby/object:Gem::Dependency
63
- name: net-hippie
63
+ name: nokogiri
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0.3'
68
+ version: '1.10'
69
69
  type: :runtime
70
70
  prerelease: false
71
71
  version_requirements: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '0.3'
75
+ version: '1.10'
76
76
  - !ruby/object:Gem::Dependency
77
- name: nokogiri
77
+ name: oj
78
78
  requirement: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '1.10'
82
+ version: '3.10'
83
83
  type: :runtime
84
84
  prerelease: false
85
85
  version_requirements: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '1.10'
89
+ version: '3.10'
90
90
  - !ruby/object:Gem::Dependency
91
91
  name: parslet
92
92
  requirement: !ruby/object:Gem::Requirement
@@ -130,19 +130,19 @@ dependencies:
130
130
  - !ruby/object:Gem::Version
131
131
  version: '0'
132
132
  - !ruby/object:Gem::Dependency
133
- name: tty-screen
133
+ name: tty-spinner
134
134
  requirement: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '0.7'
138
+ version: '0.9'
139
139
  type: :runtime
140
140
  prerelease: false
141
141
  version_requirements: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: '0.7'
145
+ version: '0.9'
146
146
  - !ruby/object:Gem::Dependency
147
147
  name: zeitwerk
148
148
  requirement: !ruby/object:Gem::Requirement
@@ -317,14 +317,14 @@ dependencies:
317
317
  requirements:
318
318
  - - "~>"
319
319
  - !ruby/object:Gem::Version
320
- version: '5.0'
320
+ version: '6.0'
321
321
  type: :development
322
322
  prerelease: false
323
323
  version_requirements: !ruby/object:Gem::Requirement
324
324
  requirements:
325
325
  - - "~>"
326
326
  - !ruby/object:Gem::Version
327
- version: '5.0'
327
+ version: '6.0'
328
328
  - !ruby/object:Gem::Dependency
329
329
  name: webmock
330
330
  requirement: !ruby/object:Gem::Requirement
@@ -367,6 +367,10 @@ files:
367
367
  - lib/spandx/cli/commands/pull.rb
368
368
  - lib/spandx/cli/commands/scan.rb
369
369
  - lib/spandx/cli/main.rb
370
+ - lib/spandx/cli/printer.rb
371
+ - lib/spandx/cli/printers/csv.rb
372
+ - lib/spandx/cli/printers/json.rb
373
+ - lib/spandx/cli/printers/table.rb
370
374
  - lib/spandx/core/cache.rb
371
375
  - lib/spandx/core/circuit.rb
372
376
  - lib/spandx/core/content.rb
@@ -383,9 +387,8 @@ files:
383
387
  - lib/spandx/core/plugin.rb
384
388
  - lib/spandx/core/registerable.rb
385
389
  - lib/spandx/core/relation.rb
386
- - lib/spandx/core/report.rb
387
390
  - lib/spandx/core/score.rb
388
- - lib/spandx/core/spinner.rb
391
+ - lib/spandx/core/thread_pool.rb
389
392
  - lib/spandx/dotnet/index.rb
390
393
  - lib/spandx/dotnet/nuget_gateway.rb
391
394
  - lib/spandx/dotnet/package_reference.rb
@@ -401,6 +404,8 @@ files:
401
404
  - lib/spandx/js/parsers/yarn.rb
402
405
  - lib/spandx/js/yarn_lock.rb
403
406
  - lib/spandx/js/yarn_pkg.rb
407
+ - lib/spandx/os/parsers/apk.rb
408
+ - lib/spandx/os/parsers/dpkg.rb
404
409
  - lib/spandx/php/packagist_gateway.rb
405
410
  - lib/spandx/php/parsers/composer.rb
406
411
  - lib/spandx/python/index.rb
@@ -422,7 +427,7 @@ licenses:
422
427
  metadata:
423
428
  homepage_uri: https://spandx.github.io/
424
429
  source_code_uri: https://github.com/spandx/spandx
425
- changelog_uri: https://github.com/spandx/spandx/blob/master/CHANGELOG.md
430
+ changelog_uri: https://github.com/spandx/spandx/blob/main/CHANGELOG.md
426
431
  post_install_message:
427
432
  rdoc_options: []
428
433
  require_paths:
@@ -438,7 +443,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
438
443
  - !ruby/object:Gem::Version
439
444
  version: '0'
440
445
  requirements: []
441
- rubygems_version: 3.1.3
446
+ rubygems_version: 3.1.4
442
447
  signing_key:
443
448
  specification_version: 4
444
449
  summary: A ruby interface to the SPDX catalogue.
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Spandx
4
- module Core
5
- class Report
6
- attr_reader :dependencies
7
-
8
- FORMATS = {
9
- csv: :to_csv,
10
- hash: :to_h,
11
- json: :to_json,
12
- table: :to_table,
13
- }.freeze
14
-
15
- def initialize
16
- @dependencies = SortedSet.new
17
- end
18
-
19
- def add(dependency)
20
- @dependencies << dependency
21
- end
22
-
23
- def to(format, formats: FORMATS)
24
- public_send(formats.fetch(format&.to_sym, :to_json))
25
- end
26
-
27
- def to_table
28
- Terminal::Table.new(headings: ['Name', 'Version', 'Licenses', 'Location']) do |t|
29
- dependencies.each do |d|
30
- t.add_row d.to_a
31
- end
32
- end
33
- end
34
-
35
- def to_h
36
- { version: '1.0', dependencies: [] }.tap do |report|
37
- dependencies.each do |dependency|
38
- report[:dependencies].push(dependency.to_h)
39
- end
40
- end
41
- end
42
-
43
- def to_json(*_args)
44
- JSON.pretty_generate(to_h)
45
- end
46
-
47
- def to_csv
48
- dependencies.map do |dependency|
49
- CSV.generate_line(dependency.to_a)
50
- end
51
- end
52
- end
53
- end
54
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Spandx
4
- module Core
5
- class Spinner
6
- NULL = Class.new do
7
- def self.spin(*args); end
8
-
9
- def self.stop(*args); end
10
- end
11
-
12
- attr_reader :columns, :spinner
13
-
14
- def initialize(columns: TTY::Screen.columns, output: $stderr)
15
- @columns = columns
16
- @spinner = Nanospinner.new(output)
17
- @queue = Queue.new
18
- @thread = Thread.new { work }
19
- end
20
-
21
- def spin(message)
22
- @queue.enq(justify(message))
23
- yield if block_given?
24
- end
25
-
26
- def stop
27
- @queue.clear
28
- @queue.enq(:stop)
29
- @thread.join
30
- end
31
-
32
- private
33
-
34
- def justify(message)
35
- message.to_s.ljust(columns - 3)
36
- end
37
-
38
- def work
39
- last_message = justify('')
40
- loop do
41
- message = @queue.empty? ? last_message : @queue.deq
42
- break if message == :stop
43
-
44
- spinner.spin(message)
45
- last_message = message
46
- sleep 0.1
47
- end
48
- end
49
- end
50
- end
51
- end