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 +4 -4
- data/CHANGELOG.md +35 -2
- data/exe/spandx +0 -1
- data/ext/spandx/spandx.c +2 -0
- data/lib/spandx.rb +2 -2
- data/lib/spandx/cli.rb +2 -2
- data/lib/spandx/cli/commands/pull.rb +43 -4
- data/lib/spandx/cli/commands/scan.rb +18 -20
- data/lib/spandx/cli/main.rb +2 -2
- 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/data_file.rb +4 -0
- data/lib/spandx/core/dependency.rb +1 -0
- data/lib/spandx/core/git.rb +13 -11
- data/lib/spandx/core/http.rb +6 -6
- data/lib/spandx/core/license_plugin.rb +1 -1
- 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/js/parsers/npm.rb +2 -2
- 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 +1 -1
- data/lib/spandx/python/parsers/pipfile_lock.rb +1 -1
- 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/version.rb +1 -1
- data/spandx.gemspec +5 -5
- metadata +25 -20
- data/lib/spandx/core/report.rb +0 -54
- data/lib/spandx/core/spinner.rb +0 -51
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 252cdcff5bb25e6699751e6f842159e46a1a19485e889dca067d2120063bd11b
|
|
4
|
+
data.tar.gz: 4a1c66430fd54d64d8fa18a4a015dbe49d94454bd2800369d384b09e1c5fc984
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3699f961272816332c5c1ec0ab97fc5f769cfeafc80d8058bb03671f8f3eaee89a3ac831ba8c40304e5d63a59c37436ca30472da4eec8bf69496f3875fb793ee
|
|
7
|
+
data.tar.gz: 8b06322b0d3103eadd726a77cf8dfd43759c94d67c628710e50247946112dba479bd0f317c45639f2bf99aed083c665edde36f0c06e2860dc97c6afe1f08cb1d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Version 0.
|
|
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.
|
|
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
data/ext/spandx/spandx.c
CHANGED
data/lib/spandx.rb
CHANGED
|
@@ -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
|
data/lib/spandx/cli.rb
CHANGED
|
@@ -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: $
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
+
PathTraversal
|
|
33
28
|
.new(scan_path, recursive: @options[:recursive])
|
|
34
29
|
.each { |file| yield file }
|
|
35
30
|
end
|
|
36
31
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
.parse(file)
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
49
|
-
::Spandx::
|
|
50
|
-
|
|
51
|
-
|
|
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
|
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. (table, csv, json
|
|
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
|
|
@@ -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
|
|
data/lib/spandx/core/git.rb
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
32
|
+
root.join('.git').directory?
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def clone!
|
|
36
|
-
system('
|
|
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', '
|
|
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
|
data/lib/spandx/core/http.rb
CHANGED
|
@@ -36,12 +36,12 @@ module Spandx
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def self.default_driver
|
|
39
|
-
@default_driver ||= Net::Hippie::Client.new
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
data/lib/spandx/core/plugin.rb
CHANGED
|
@@ -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
|
|
@@ -18,8 +18,8 @@ module Spandx
|
|
|
18
18
|
|
|
19
19
|
private
|
|
20
20
|
|
|
21
|
-
def each_metadata(
|
|
22
|
-
package_lock =
|
|
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
|
data/lib/spandx/js/yarn_pkg.rb
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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
|
data/lib/spandx/python/pypi.rb
CHANGED
|
@@ -49,19 +49,26 @@ module Spandx
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def version_from(url)
|
|
52
|
-
path =
|
|
53
|
-
memo.gsub(item, '')
|
|
54
|
-
end
|
|
55
|
-
|
|
52
|
+
path = cleanup(url)
|
|
56
53
|
return if path.rindex('-').nil?
|
|
57
54
|
|
|
58
|
-
path.scan(/-\d+\..*/)
|
|
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
|
-
|
|
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
|
data/lib/spandx/python/source.rb
CHANGED
data/lib/spandx/ruby/gateway.rb
CHANGED
data/lib/spandx/version.rb
CHANGED
data/spandx.gemspec
CHANGED
|
@@ -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/
|
|
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 '
|
|
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-
|
|
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', '~>
|
|
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.
|
|
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-
|
|
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:
|
|
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
|
|
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
|
|
61
|
+
version: '1.0'
|
|
62
62
|
- !ruby/object:Gem::Dependency
|
|
63
|
-
name:
|
|
63
|
+
name: nokogiri
|
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
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: '
|
|
75
|
+
version: '1.10'
|
|
76
76
|
- !ruby/object:Gem::Dependency
|
|
77
|
-
name:
|
|
77
|
+
name: oj
|
|
78
78
|
requirement: !ruby/object:Gem::Requirement
|
|
79
79
|
requirements:
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
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: '
|
|
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-
|
|
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.
|
|
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.
|
|
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: '
|
|
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: '
|
|
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/
|
|
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/
|
|
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.
|
|
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.
|
data/lib/spandx/core/report.rb
DELETED
|
@@ -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
|
data/lib/spandx/core/spinner.rb
DELETED
|
@@ -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
|