spandx 0.12.3 → 0.13.4

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -25
  3. data/README.md +11 -7
  4. data/exe/spandx +1 -2
  5. data/ext/spandx/extconf.rb +5 -0
  6. data/ext/spandx/spandx.c +55 -0
  7. data/ext/spandx/spandx.h +6 -0
  8. data/lib/spandx.rb +6 -3
  9. data/lib/spandx/cli.rb +2 -0
  10. data/lib/spandx/cli/commands/build.rb +13 -2
  11. data/lib/spandx/cli/commands/scan.rb +11 -20
  12. data/lib/spandx/cli/main.rb +3 -2
  13. data/lib/spandx/core/cache.rb +38 -51
  14. data/lib/spandx/core/content.rb +5 -23
  15. data/lib/spandx/core/data_file.rb +66 -0
  16. data/lib/spandx/core/dependency.rb +47 -13
  17. data/lib/spandx/core/git.rb +8 -32
  18. data/lib/spandx/core/guess.rb +48 -40
  19. data/lib/spandx/core/http.rb +7 -2
  20. data/lib/spandx/core/index_file.rb +103 -0
  21. data/lib/spandx/core/license_plugin.rb +15 -4
  22. data/lib/spandx/core/parser.rb +10 -3
  23. data/lib/spandx/core/path_traversal.rb +35 -0
  24. data/lib/spandx/core/relation.rb +38 -0
  25. data/lib/spandx/core/report.rb +6 -12
  26. data/lib/spandx/core/spinner.rb +51 -0
  27. data/lib/spandx/dotnet/index.rb +21 -79
  28. data/lib/spandx/dotnet/parsers/csproj.rb +7 -7
  29. data/lib/spandx/dotnet/parsers/packages_config.rb +7 -7
  30. data/lib/spandx/dotnet/parsers/sln.rb +10 -13
  31. data/lib/spandx/dotnet/project_file.rb +3 -3
  32. data/lib/spandx/java/index.rb +5 -2
  33. data/lib/spandx/java/parsers/maven.rb +7 -7
  34. data/lib/spandx/js/parsers/npm.rb +6 -6
  35. data/lib/spandx/js/parsers/yarn.rb +7 -7
  36. data/lib/spandx/php/parsers/composer.rb +7 -7
  37. data/lib/spandx/python/index.rb +4 -33
  38. data/lib/spandx/python/parsers/pipfile_lock.rb +4 -4
  39. data/lib/spandx/python/pypi.rb +0 -2
  40. data/lib/spandx/python/source.rb +12 -0
  41. data/lib/spandx/ruby/parsers/gemfile_lock.rb +10 -9
  42. data/lib/spandx/spdx/catalogue.rb +5 -1
  43. data/lib/spandx/spdx/composite_license.rb +60 -0
  44. data/lib/spandx/spdx/expression.rb +114 -0
  45. data/lib/spandx/spdx/license.rb +4 -14
  46. data/lib/spandx/version.rb +1 -1
  47. data/spandx.gemspec +16 -10
  48. metadata +100 -30
  49. data/lib/spandx/core/null_gateway.rb +0 -11
  50. data/lib/spandx/core/table.rb +0 -29
  51. data/lib/spandx/core/thread_pool.rb +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03ab3629ac636e8be88e27061cbce6941672dfd0a99bce30a9a638a095ebddab
4
- data.tar.gz: 3e0d7eb62b59887c2770ac9dfbef7b0037fcf3031e73f2f380a8ed1ba93d2cca
3
+ metadata.gz: ef9e117562bb153d2bf7a7aa8561244f50f7dcbca8a81300e10279efec45c674
4
+ data.tar.gz: '069f96ae764417f3b005ebd8e7fee919c66eee38a63649918f7dea3209a9bc34'
5
5
  SHA512:
6
- metadata.gz: c6962a430329a9d3ed3f274485a55a396cea66b9bcd8f664caf803d74c5768508aa9eee499a7ae99ab06bd840a6bedb67b85812cab193c1e5f6f725458c05fbd
7
- data.tar.gz: a42dd5adeeb36e374a1f4a2582a881d5d4e28f2959d0044e653162edd0835895ade882261379bc5d47d3a7955b536c7f0ddc53b4a489ce8e68e6b6a3ad0c9731
6
+ metadata.gz: 67ec66d00236b0c4a98bc770e0b33698ecabb6e868b4e1b309c735a0d3cf5288a49393280f0cff2829d8ab5d1005920b16a877bdabdb2a0b63c88ac9f7af7df9
7
+ data.tar.gz: 9260aeb08a495fb4f8bd1ba4c2b17fed8e34c2e60e96d5c826c79f7c51d83a8686b4194e060fa77e3b898f1beb17404006dd5370b727d2a7ce98d87ddce41507
@@ -1,4 +1,4 @@
1
- Version 0.12.3
1
+ Version 0.13.4
2
2
 
3
3
  # Changelog
4
4
 
@@ -9,6 +9,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.13.4] - 2020-05-26
13
+ ### Added
14
+ - Add detected file path to report output.
15
+
16
+ ### Changed
17
+ - Use `Pathname` instead of `String` to represent file paths.
18
+ - Scan current directory when a path is not specified.
19
+
20
+ ## [0.13.3] - 2020-05-19
21
+ ### Fixed
22
+ - Ignore invalid URLs during scan.
23
+
24
+ ## [0.13.2] - 2020-05-17
25
+ ### Fixed
26
+ - Detect licenses when provided as an array.
27
+ - Skip empty lockfiles.
28
+
29
+ ## [0.13.1] - 2020-05-16
30
+ ### Fixed
31
+ - Add `ext/**/*.c` and `ext/**/*.h` to list of files.
32
+
33
+ ## [0.13.0] - 2020-05-12
34
+ ### Added
35
+ - Add progress bar
36
+ - Add SPDX expression parser.
37
+ - Add index for each cache.
38
+ - Update cache paths to point to Spandx organization.
39
+ - Add optimized CSV parser.
40
+ - Fetch dependency data concurrently.
41
+ - Add profiling and benchmarking tools.
42
+
43
+ ### Changed
44
+ - Update git pull command to fetch master branch with a depth of 1.
45
+ - Update Nuget and PyPI cache builders to use same API for writing to cache.
46
+ - Update license lookup to parse SPDX expressions.
47
+
48
+ ### Removed
49
+ - Drop Ruby 2.4 support.
50
+ - Drop Jaro Winkler algorithm.
51
+ - Drop Levenshtein algorithm.
52
+
53
+ ### Fixed
54
+ - Fix bug in spawning worker threads in thread pool.
55
+ - Reset `http` global before each test to remove leakage between tests.
56
+
12
57
  ## [0.12.3] - 2020-04-19
13
58
  ### Fixed
14
59
  - Ignore nuget entries with missing `items`.
@@ -144,27 +189,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
144
189
  ### Added
145
190
  - Provide ruby API to the latest SPDX catalogue.
146
191
 
147
- [Unreleased]: https://github.com/mokhan/spandx/compare/v0.12.3...HEAD
148
- [0.12.3]: https://github.com/mokhan/spandx/compare/v0.12.2...v0.12.3
149
- [0.12.2]: https://github.com/mokhan/spandx/compare/v0.12.1...v0.12.2
150
- [0.12.1]: https://github.com/mokhan/spandx/compare/v0.12.0...v0.12.1
151
- [0.12.0]: https://github.com/mokhan/spandx/compare/v0.11.0...v0.12.0
152
- [0.11.0]: https://github.com/mokhan/spandx/compare/v0.10.1...v0.11.0
153
- [0.10.1]: https://github.com/mokhan/spandx/compare/v0.10.0...v0.10.1
154
- [0.10.0]: https://github.com/mokhan/spandx/compare/v0.9.0...v0.10.0
155
- [0.9.0]: https://github.com/mokhan/spandx/compare/v0.8.0...v0.9.0
156
- [0.8.0]: https://github.com/mokhan/spandx/compare/v0.7.0...v0.8.0
157
- [0.7.0]: https://github.com/mokhan/spandx/compare/v0.6.0...v0.7.0
158
- [0.6.0]: https://github.com/mokhan/spandx/compare/v0.5.0...v0.6.0
159
- [0.5.0]: https://github.com/mokhan/spandx/compare/v0.4.1...v0.5.0
160
- [0.4.1]: https://github.com/mokhan/spandx/compare/v0.4.0...v0.4.1
161
- [0.4.0]: https://github.com/mokhan/spandx/compare/v0.3.0...v0.4.0
162
- [0.3.0]: https://github.com/mokhan/spandx/compare/v0.2.0...v0.3.0
163
- [0.2.0]: https://github.com/mokhan/spandx/compare/v0.1.7...v0.2.0
164
- [0.1.7]: https://github.com/mokhan/spandx/compare/v0.1.6...v0.1.7
165
- [0.1.6]: https://github.com/mokhan/spandx/compare/v0.1.5...v0.1.6
166
- [0.1.5]: https://github.com/mokhan/spandx/compare/v0.1.4...v0.1.5
167
- [0.1.4]: https://github.com/mokhan/spandx/compare/v0.1.3...v0.1.4
168
- [0.1.3]: https://github.com/mokhan/spandx/compare/v0.1.2...v0.1.3
169
- [0.1.2]: https://github.com/mokhan/spandx/compare/v0.1.1...v0.1.2
170
- [0.1.1]: https://github.com/mokhan/spandx/compare/v0.1.0...v0.1.1
192
+ [Unreleased]: https://github.com/spandx/spandx/compare/v0.13.3...HEAD
193
+ [0.13.3]: https://github.com/spandx/spandx/compare/v0.13.2...v0.13.3
194
+ [0.13.2]: https://github.com/spandx/spandx/compare/v0.13.1...v0.13.2
195
+ [0.13.1]: https://github.com/spandx/spandx/compare/v0.13.0...v0.13.1
196
+ [0.13.0]: https://github.com/spandx/spandx/compare/v0.12.3...v0.13.0
197
+ [0.12.3]: https://github.com/spandx/spandx/compare/v0.12.2...v0.12.3
198
+ [0.12.2]: https://github.com/spandx/spandx/compare/v0.12.1...v0.12.2
199
+ [0.12.1]: https://github.com/spandx/spandx/compare/v0.12.0...v0.12.1
200
+ [0.12.0]: https://github.com/spandx/spandx/compare/v0.11.0...v0.12.0
201
+ [0.11.0]: https://github.com/spandx/spandx/compare/v0.10.1...v0.11.0
202
+ [0.10.1]: https://github.com/spandx/spandx/compare/v0.10.0...v0.10.1
203
+ [0.10.0]: https://github.com/spandx/spandx/compare/v0.9.0...v0.10.0
204
+ [0.9.0]: https://github.com/spandx/spandx/compare/v0.8.0...v0.9.0
205
+ [0.8.0]: https://github.com/spandx/spandx/compare/v0.7.0...v0.8.0
206
+ [0.7.0]: https://github.com/spandx/spandx/compare/v0.6.0...v0.7.0
207
+ [0.6.0]: https://github.com/spandx/spandx/compare/v0.5.0...v0.6.0
208
+ [0.5.0]: https://github.com/spandx/spandx/compare/v0.4.1...v0.5.0
209
+ [0.4.1]: https://github.com/spandx/spandx/compare/v0.4.0...v0.4.1
210
+ [0.4.0]: https://github.com/spandx/spandx/compare/v0.3.0...v0.4.0
211
+ [0.3.0]: https://github.com/spandx/spandx/compare/v0.2.0...v0.3.0
212
+ [0.2.0]: https://github.com/spandx/spandx/compare/v0.1.7...v0.2.0
213
+ [0.1.7]: https://github.com/spandx/spandx/compare/v0.1.6...v0.1.7
214
+ [0.1.6]: https://github.com/spandx/spandx/compare/v0.1.5...v0.1.6
215
+ [0.1.5]: https://github.com/spandx/spandx/compare/v0.1.4...v0.1.5
216
+ [0.1.4]: https://github.com/spandx/spandx/compare/v0.1.3...v0.1.4
217
+ [0.1.3]: https://github.com/spandx/spandx/compare/v0.1.2...v0.1.3
218
+ [0.1.2]: https://github.com/spandx/spandx/compare/v0.1.1...v0.1.2
219
+ [0.1.1]: https://github.com/spandx/spandx/compare/v0.1.0...v0.1.1
data/README.md CHANGED
@@ -1,10 +1,14 @@
1
- # Spandx ![badge](https://github.com/mokhan/spandx/workflows/ci/badge.svg)
1
+ ![Spandx](logo.gif)
2
+
3
+ *Logo courtesy of [@speasley](https://github.com/speasley)*
4
+
5
+ # Spandx ![badge](https://github.com/spandx/spandx/workflows/ci/badge.svg)
2
6
 
3
7
  A ruby API for interacting with the https://spdx.org software license catalogue.
4
8
  This gem includes a command line interface to scan a software project for the
5
9
  software licenses that are associated with each dependency in the project.
6
10
  `spandx` leverages an offline cache of software licenses for known dependencies.
7
- The offline cache allows spandx to perform a truly airgap friendly scan of software
11
+ The offline cache allows Spandx to perform an air gap friendly scan of software
8
12
  projects.
9
13
 
10
14
  ### Supported project types
@@ -42,7 +46,7 @@ Or install it yourself as:
42
46
 
43
47
  ### Command line interface
44
48
 
45
- The command line interface supports operations to build and fetch the latest offline index.
49
+ The command line interface supports operations to fetch the latest pre-built cache.
46
50
  See the help for each subcommand for more information on how to use the command.
47
51
 
48
52
  ```bash
@@ -62,19 +66,19 @@ To scan a specific project file use the `scan` command:
62
66
  モ spandx scan ruby/Gemfile.lock
63
67
  ```
64
68
 
65
- To activate airgap mode use the `--airgap` option:
69
+ To activate air gap mode use the `--airgap` option:
66
70
 
67
71
  ```bash
68
72
  モ spandx scan dotnet/application.sln --airgap
69
73
  モ spandx scan ruby/Gemfile.lock --airgap
70
74
  ```
71
75
 
72
- Airgap mode assumes that an offline cache has been placed in `$HOME/.local/share/`.
76
+ Air gap mode assumes that an offline cache has been placed in `$HOME/.local/share/`.
73
77
 
74
78
  To fetch the latest offline cache:
75
79
 
76
80
  ```bash
77
- モ spandx index update
81
+ モ spandx pull
78
82
  ```
79
83
 
80
84
  ### Ruby API
@@ -106,7 +110,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
106
110
 
107
111
  ## Contributing
108
112
 
109
- Bug reports and pull requests are welcome on GitHub at https://github.com/mokhan/spandx.
113
+ Bug reports and pull requests are welcome on GitHub at https://github.com/spandx/spandx.
110
114
 
111
115
  ## License
112
116
 
data/exe/spandx CHANGED
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- lib_path = File.expand_path('../lib', __dir__)
5
- require "#{lib_path}/spandx"
4
+ require 'spandx'
6
5
 
7
6
  Signal.trap('INT') do
8
7
  warn("\n#{caller.join("\n")}: interrupted")
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mkmf'
4
+
5
+ create_makefile('spandx/spandx')
@@ -0,0 +1,55 @@
1
+ #include "spandx.h"
2
+
3
+ #define NEWLINE 10
4
+
5
+ VALUE rb_mSpandx;
6
+ VALUE rb_mCore;
7
+ VALUE rb_mCsvParser;
8
+
9
+ // "name","version","license"
10
+ // "name","version","license"\n
11
+ // "name","version","license"\r
12
+ // "name","version","license"\r\n
13
+ // "name","version",""\r\n
14
+ VALUE parse(VALUE self, VALUE line)
15
+ {
16
+ if (NIL_P(line)) return Qnil;
17
+
18
+ char *p;
19
+
20
+ p = RSTRING_PTR(line);
21
+ if (*p != '"') return Qnil;
22
+
23
+ const VALUE items = rb_ary_new2(3);
24
+ const char *s, *n;
25
+ const long len = RSTRING_LEN(line);
26
+ enum { open, closed } state = closed;
27
+
28
+ for (int i = 0; i < len && *p; i++) {
29
+ if (*p == '"') {
30
+ n = p;
31
+ if (i < (len - 1)) *n++;
32
+
33
+ if (state == closed) {
34
+ s = n;
35
+ state = open;
36
+ } else if (state == open) {
37
+ if (!*n || n == p || *n == ',' || *n == NEWLINE) {
38
+ rb_ary_push(items, rb_str_new(s, p - s));
39
+ state = closed;
40
+ }
41
+ }
42
+ }
43
+ *(p++);
44
+ }
45
+
46
+ return items;
47
+ }
48
+
49
+ void Init_spandx(void)
50
+ {
51
+ rb_mSpandx = rb_define_module("Spandx");
52
+ rb_mCore = rb_define_module_under(rb_mSpandx, "Core");
53
+ rb_mCsvParser = rb_define_module_under(rb_mCore, "CsvParser");
54
+ rb_define_module_function(rb_mCsvParser, "parse", parse, 1);
55
+ }
@@ -0,0 +1,6 @@
1
+ #ifndef SPANDX_H
2
+ #define SPANDX_H 1
3
+
4
+ #include "ruby.h"
5
+
6
+ #endif /* SPANDX_H */
@@ -8,9 +8,12 @@ require 'json'
8
8
  require 'logger'
9
9
  require 'net/hippie'
10
10
  require 'nokogiri'
11
+ require 'parslet'
11
12
  require 'pathname'
12
13
  require 'yaml'
13
14
  require 'zeitwerk'
15
+ require 'terminal-table'
16
+ require 'spandx/spandx'
14
17
 
15
18
  loader = Zeitwerk::Loader.for_gem
16
19
  loader.setup # ready!
@@ -20,7 +23,7 @@ module Spandx
20
23
  Rubygems = Ruby
21
24
 
22
25
  class << self
23
- attr_writer :airgap, :logger
26
+ attr_writer :airgap, :logger, :http, :git
24
27
 
25
28
  def root
26
29
  Pathname.new(File.dirname(__FILE__)).join('../..')
@@ -40,8 +43,8 @@ module Spandx
40
43
 
41
44
  def git
42
45
  @git ||= {
43
- cache: ::Spandx::Core::Git.new(url: 'https://github.com/mokhan/spandx-index.git'),
44
- rubygems: ::Spandx::Core::Git.new(url: 'https://github.com/mokhan/spandx-rubygems.git'),
46
+ cache: ::Spandx::Core::Git.new(url: 'https://github.com/spandx/cache.git'),
47
+ rubygems: ::Spandx::Core::Git.new(url: 'https://github.com/spandx/rubygems-cache.git'),
45
48
  spdx: ::Spandx::Core::Git.new(url: 'https://github.com/spdx/license-list-data.git'),
46
49
  }
47
50
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'nanospinner'
3
4
  require 'thor'
5
+ require 'tty-screen'
4
6
 
5
7
  module Spandx
6
8
  module Cli
@@ -17,6 +17,7 @@ module Spandx
17
17
 
18
18
  def execute(output: $stdout)
19
19
  catalogue = Spandx::Spdx::Catalogue.from_git
20
+ build_buckets
20
21
  indexes.each do |index|
21
22
  output.puts index.name
22
23
  index.update!(catalogue: catalogue, output: output)
@@ -30,9 +31,19 @@ module Spandx
30
31
  index = INDEXES[@options[:index]&.to_sym]
31
32
 
32
33
  if index.nil?
33
- INDEXES.values.uniq.map { |x| x.new(directory: @options[:directory]) }
34
+ INDEXES.values.uniq.map { |x| x.new(directory: directory) }
34
35
  else
35
- [index.new(directory: @options[:directory])]
36
+ [index.new(directory: directory)]
37
+ end
38
+ end
39
+
40
+ def directory
41
+ @options.fetch(:directory, File.join(Dir.pwd, '.index'))
42
+ end
43
+
44
+ def build_buckets
45
+ (0x00..0xFF).map { |x| x.to_s(16).rjust(2, '0').downcase }.each do |hex|
46
+ FileUtils.mkdir_p(File.join(directory, hex))
36
47
  end
37
48
  end
38
49
  end
@@ -4,50 +4,41 @@ module Spandx
4
4
  module Cli
5
5
  module Commands
6
6
  class Scan
7
- attr_reader :scan_path
7
+ attr_reader :scan_path, :spinner
8
8
 
9
9
  def initialize(scan_path, options)
10
10
  @scan_path = ::Pathname.new(scan_path)
11
11
  @options = options
12
+ @spinner = options[:show_progress] ? ::Spandx::Core::Spinner.new : ::Spandx::Core::Spinner::NULL
12
13
  require(options[:require]) if options[:require]
13
14
  end
14
15
 
15
16
  def execute(output: $stdout)
16
17
  report = ::Spandx::Core::Report.new
17
- each_file_in(scan_path) do |file|
18
+ each_file do |file|
19
+ spinner.spin(file)
18
20
  each_dependency_from(file) do |dependency|
21
+ spinner.spin(file)
19
22
  report.add(dependency)
20
23
  end
21
24
  end
25
+ spinner.stop
22
26
  output.puts(format(report.to(@options[:format])))
23
27
  end
24
28
 
25
29
  private
26
30
 
27
- def recursive?
28
- @options['recursive']
29
- end
30
-
31
- def each_file_in(dir, &block)
32
- files = File.directory?(dir) ? Dir.glob(File.join(dir, '*')) : [dir]
33
- files.each do |file|
34
- if File.directory?(file)
35
- each_file_in(file, &block) if recursive?
36
- else
37
- Spandx.logger.debug(file)
38
- block.call(file)
39
- end
40
- end
31
+ def each_file
32
+ Spandx::Core::PathTraversal
33
+ .new(scan_path, recursive: @options[:recursive])
34
+ .each { |file| yield file }
41
35
  end
42
36
 
43
37
  def each_dependency_from(file)
44
38
  ::Spandx::Core::Parser
45
- .for(file)
46
39
  .parse(file)
47
- .map { |dependency| enhance(dependency) }
40
+ .map { |x| enhance(x) }
48
41
  .each { |dependency| yield dependency }
49
- rescue StandardError => error
50
- Spandx.logger.error(error)
51
42
  end
52
43
 
53
44
  def format(output)
@@ -8,10 +8,11 @@ module Spandx
8
8
  method_option :recursive, aliases: '-R', type: :boolean, desc: 'Perform recursive scan', default: false
9
9
  method_option :airgap, aliases: '-a', type: :boolean, desc: 'Disable network connections', default: false
10
10
  method_option :logfile, aliases: '-l', type: :string, desc: 'Path to a logfile', default: '/dev/null'
11
- method_option :format, aliases: '-f', type: :string, desc: 'Format of report', default: 'table'
11
+ method_option :format, aliases: '-f', type: :string, desc: 'Format of report. (table, csv, json, hash)', 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
- def scan(lockfile)
14
+ method_option :show_progress, aliases: '-sp', type: :boolean, desc: 'Shows a progress bar', default: true
15
+ def scan(lockfile = Pathname.pwd)
15
16
  if options[:help]
16
17
  invoke :help, ['scan']
17
18
  else
@@ -3,83 +3,70 @@
3
3
  module Spandx
4
4
  module Core
5
5
  class Cache
6
- attr_reader :db, :package_manager
6
+ include Enumerable
7
7
 
8
- def initialize(package_manager, db: Spandx.git[:cache])
9
- @package_manager = package_manager
10
- @db = db
11
- @cache = {}
12
- @lines = {}
8
+ attr_reader :package_manager, :root
9
+
10
+ def initialize(package_manager, root:)
11
+ @package_manager = package_manager.to_s
12
+ @root = root
13
13
  end
14
14
 
15
15
  def licenses_for(name, version)
16
- found = search(name: name, version: version)
17
- Spandx.logger.debug("Cache miss: #{name}-#{version}") if found.nil?
16
+ return [] if name.nil? || name.empty?
17
+
18
+ found = datafile_for(name).search(name: name, version: version)
19
+ Spandx.logger.debug { "Cache miss: #{name}-#{version}" } unless found
18
20
  found ? found[2].split('-|-') : []
19
21
  end
20
22
 
21
- private
22
-
23
- def digest_for(components)
24
- Digest::SHA1.hexdigest(Array(components).join('/'))
23
+ def each
24
+ datafiles.each do |_hex, datafile|
25
+ datafile.each do |item|
26
+ yield item
27
+ end
28
+ end
25
29
  end
26
30
 
27
- def key_for(name)
28
- digest_for(name)[0...2]
31
+ def insert(name, version, licenses)
32
+ return if name.nil? || name.empty?
33
+
34
+ datafile_for(name).insert(name, version, licenses)
29
35
  end
30
36
 
31
- def search(name:, version:)
32
- datafile = datafile_for(name)
33
- db.open(datafile) do |io|
34
- search_for("#{name}-#{version}", io, @lines.fetch(datafile) { |key| @lines[key] = lines_in(io) })
35
- end
36
- rescue Errno::ENOENT => error
37
- Spandx.logger.error(error)
38
- nil
37
+ def insert!(*args)
38
+ insert(*args)
39
+ rebuild_index
39
40
  end
40
41
 
41
42
  def datafile_for(name)
42
- ".index/#{key_for(name)}/#{package_manager}"
43
+ datafiles.fetch(key_for(name))
43
44
  end
44
45
 
45
- def lines_in(io)
46
- lines = []
47
- io.seek(0)
48
- until io.eof?
49
- lines << io.pos
50
- io.gets
46
+ def rebuild_index
47
+ datafiles.each do |_hex, datafile|
48
+ datafile.index.update!
51
49
  end
52
- lines
53
50
  end
54
51
 
55
- def search_for(term, io, lines)
56
- return if lines.empty?
57
- return @cache[term] if @cache.key?(term)
58
-
59
- mid = lines.size == 1 ? 0 : lines.size / 2
60
- io.seek(lines[mid])
61
- comparison = matches?(term, parse_row(io)) do |row|
62
- return row
63
- end
52
+ private
64
53
 
65
- search_for(term, io, partition(comparison, mid, lines))
54
+ def digest_for(name)
55
+ Digest::SHA1.hexdigest(name)
66
56
  end
67
57
 
68
- def matches?(term, row)
69
- (term <=> "#{row[0]}-#{row[1]}").tap do |comparison|
70
- yield row if comparison.zero?
71
- end
58
+ def key_for(name)
59
+ digest_for(name)[0...2]
72
60
  end
73
61
 
74
- def partition(comparison, mid, lines)
75
- min, max = comparison.positive? ? [mid + 1, lines.length] : [0, mid]
76
- lines.slice(min, max)
62
+ def datafiles
63
+ @datafiles ||= candidate_keys.each_with_object({}) do |key, memo|
64
+ memo[key] = DataFile.new(File.join(root, key, package_manager))
65
+ end
77
66
  end
78
67
 
79
- def parse_row(io)
80
- row = CSV.parse(io.readline)[0]
81
- @cache["#{row[0]}-#{row[1]}"] = row
82
- row
68
+ def candidate_keys
69
+ (0x00..0xFF).map { |x| x.to_s(16).upcase.rjust(2, '0').downcase }
83
70
  end
84
71
  end
85
72
  end