spandx 0.12.3 → 0.13.4

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