spandx 0.13.4 → 0.16.0

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