spandx 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 694c16875566407ff41f6d791d7499de4a4686231e8f24b3c0393d14039d764c
4
- data.tar.gz: 49873d42c52d18fe55e95a32f98b1f5b115a46aa12ebd8df0247a78173953c60
3
+ metadata.gz: da243ee84b54d710a15eeb5182f7b70084cbc69cb86beaa93c25c13dc650fe2a
4
+ data.tar.gz: 904eff3844f540311828386dde54ad70054f5c4fe90e456bb0af702efc9f7cc3
5
5
  SHA512:
6
- metadata.gz: a0bce586e02b70dd48a48819970c6b51198048a8ee7b6310f5959e56b13772ada9925524f1718add10e392e0a52b64907a39eeeffedb6a8cd22486e15daf84c4
7
- data.tar.gz: ddb03108be865151c7c39e78a57f7988c49333bd086ca333f4b54ecd13a55b6d8f82fe0ac0a64a869ea96c0b28bca910c3558bd783dc193ffdc650dd0649b0cb
6
+ metadata.gz: 67135d0cff701f454ce5d127a7f4ec7fd061d63bc832d5eb7df20bb26efa6e45bc90e28bc22eceaa2c135437858aa63337db8ff898245d870fe7acfcae14413d
7
+ data.tar.gz: a4086a92383a8c334c3488004b6be02a0ce80a79b9c7386eea5948d3d52d031d46e742d9e290afa45e306c4d3d1e4bbb7c2849cdd1d2d3dc98b5aba74fd13ed8
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- Version 0.10.1
1
+ Version 0.11.0
2
2
 
3
3
  # Changelog
4
4
 
@@ -8,6 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8
8
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
9
 
10
10
  ## [Unreleased]
11
+
12
+ ## [0.11.0] - 2020-03-20
13
+ ### Added
14
+ - Add `--format` option to scan command.
15
+ - Read from offline `nuget` cache.
16
+
11
17
  ## [0.10.1] - 2020-03-16
12
18
  ### Fixed
13
19
  - Update location of `rubygems` index data
@@ -21,7 +21,8 @@ module Spandx
21
21
 
22
22
  def indexes
23
23
  [
24
- Spandx::Dotnet::Index.new(directory: @options[:directory])
24
+ Spandx::Dotnet::Index.new(directory: @options[:directory]),
25
+ Spandx::Java::Index.new(directory: @options[:directory]),
25
26
  ]
26
27
  end
27
28
  end
@@ -18,7 +18,7 @@ module Spandx
18
18
  report.add(dependency)
19
19
  end
20
20
  end
21
- output.puts report.to_json
21
+ output.puts report.to(@options[:format])
22
22
  end
23
23
 
24
24
  private
data/lib/spandx/cli.rb CHANGED
@@ -23,6 +23,7 @@ module Spandx
23
23
  method_option :recursive, aliases: '-r', type: :boolean, desc: 'Perform recursive scan', default: false
24
24
  method_option :airgap, aliases: '-a', type: :boolean, desc: 'Disable network connections', default: false
25
25
  method_option :logfile, aliases: '-l', type: :string, desc: 'Path to a logfile', default: '/dev/null'
26
+ method_option :format, aliases: '-f', type: :string, desc: 'Format of report', default: 'json'
26
27
  def scan(lockfile)
27
28
  Spandx.airgap = options[:airgap]
28
29
  Spandx.logger = Logger.new(options[:logfile])
@@ -1,16 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spandx
4
- module Rubygems
5
- class OfflineIndex
6
- attr_reader :db
4
+ module Core
5
+ class Cache
6
+ attr_reader :db, :package_manager
7
7
 
8
- def initialize(package_manager)
9
- @db = ::Spandx::Core::Database.new(url: "https://github.com/mokhan/spandx-#{package_manager}.git")
8
+ def initialize(package_manager, url:)
9
+ @package_manager = package_manager
10
+ @db = ::Spandx::Core::Database.new(url: url)
11
+ @cache = {}
12
+ @lines = {}
10
13
  end
11
14
 
12
15
  def licenses_for(name:, version:)
13
16
  found = search(name: name, version: version)
17
+ Spandx.logger.debug("Cache miss: #{name}-#{version}") if found.nil?
14
18
  found ? found[2].split('-|-') : []
15
19
  end
16
20
 
@@ -25,8 +29,9 @@ module Spandx
25
29
  end
26
30
 
27
31
  def search(name:, version:)
28
- db.open(datafile_for(name)) do |io|
29
- search_for("#{name}-#{version}", io, lines_in(io))
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) })
30
35
  end
31
36
  rescue Errno::ENOENT => error
32
37
  Spandx.logger.error(error)
@@ -34,7 +39,7 @@ module Spandx
34
39
  end
35
40
 
36
41
  def datafile_for(name)
37
- ".index/#{key_for(name)}/rubygems"
42
+ ".index/#{key_for(name)}/#{package_manager}"
38
43
  end
39
44
 
40
45
  def lines_in(io)
@@ -49,10 +54,14 @@ module Spandx
49
54
 
50
55
  def search_for(term, io, lines)
51
56
  return if lines.empty?
57
+ return @cache[term] if @cache.key?(term)
52
58
 
53
59
  mid = lines.size == 1 ? 0 : lines.size / 2
54
60
  io.seek(lines[mid])
55
- comparison = matches?(term, parse_row(io)) { |row| return row }
61
+ comparison = matches?(term, parse_row(io)) do |row|
62
+ return row
63
+ end
64
+
56
65
  search_for(term, io, partition(comparison, mid, lines))
57
66
  end
58
67
 
@@ -68,7 +77,9 @@ module Spandx
68
77
  end
69
78
 
70
79
  def parse_row(io)
71
- CSV.parse(io.readline)[0]
80
+ row = CSV.parse(io.readline)[0]
81
+ @cache["#{row[0]}-#{row[1]}"] = row
82
+ row
72
83
  end
73
84
  end
74
85
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spandx
4
- module Gateways
4
+ module Core
5
5
  class Http
6
6
  attr_reader :driver
7
7
 
@@ -3,16 +3,29 @@
3
3
  module Spandx
4
4
  module Core
5
5
  class Report
6
- def initialize(report: { version: '1.0', packages: [] })
7
- @report = report
6
+ FORMATS = {
7
+ json: :to_json,
8
+ hash: :to_h,
9
+ }.freeze
10
+
11
+ def initialize
12
+ @dependencies = []
8
13
  end
9
14
 
10
15
  def add(dependency)
11
- @report[:packages].push(dependency.to_h)
16
+ @dependencies.push(dependency)
17
+ end
18
+
19
+ def to(format)
20
+ public_send(FORMATS.fetch(format&.to_sym, :to_json))
12
21
  end
13
22
 
14
23
  def to_h
15
- @report
24
+ { version: '1.0', dependencies: [] }.tap do |report|
25
+ @dependencies.each do |dependency|
26
+ report[:dependencies].push(dependency.to_h)
27
+ end
28
+ end
16
29
  end
17
30
 
18
31
  def to_json(*_args)
@@ -15,6 +15,9 @@ module Spandx
15
15
  end
16
16
 
17
17
  def licenses_for(name, version)
18
+ found = cache.licenses_for(name: name, version: version)
19
+ return found if found.any?
20
+
18
21
  document = nuspec_for(name, version)
19
22
 
20
23
  extract_licenses_from(document) ||
@@ -33,6 +36,10 @@ module Spandx
33
36
 
34
37
  attr_reader :http, :guess
35
38
 
39
+ def cache
40
+ @cache ||= ::Spandx::Core::Cache.new(:nuget, url: 'https://github.com/mokhan/spandx-index.git')
41
+ end
42
+
36
43
  def each_page(start_page:)
37
44
  url = "https://#{host}/v3/catalog0/index.json"
38
45
  items_from(fetch_json(url))
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ module Java
5
+ class Index
6
+ def initialize(directory:)
7
+ @directory = directory
8
+ end
9
+
10
+ def update!(catalogue:, output:); end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ module Python
5
+ module Parsers
6
+ class PipfileLock < ::Spandx::Core::Parser
7
+ def self.matches?(filename)
8
+ filename.match?(/Pipfile.*\.lock/)
9
+ end
10
+
11
+ def parse(lockfile)
12
+ results = []
13
+ dependencies_from(lockfile) do |x|
14
+ results << ::Spandx::Core::Dependency.new(
15
+ name: x[:name],
16
+ version: x[:version],
17
+ licenses: x[:licenses]
18
+ )
19
+ end
20
+ results
21
+ end
22
+
23
+ private
24
+
25
+ def dependencies_from(lockfile)
26
+ json = JSON.parse(IO.read(lockfile))
27
+ each_dependency(pypi_for(json), json) do |name, version, definition|
28
+ yield({ name: name, version: version, licenses: [catalogue[definition['license']]] })
29
+ end
30
+ end
31
+
32
+ def each_dependency(pypi, json, groups: %w[default develop])
33
+ groups.each do |group|
34
+ json[group].each do |name, value|
35
+ version = canonicalize(value['version'])
36
+ yield name, version, pypi.definition_for(name, version)
37
+ end
38
+ end
39
+ end
40
+
41
+ def canonicalize(version)
42
+ version.gsub(/==/, '')
43
+ end
44
+
45
+ def pypi_for(json)
46
+ PyPI.new(sources: Source.sources_from(json))
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ module Python
5
+ class PyPI
6
+ def initialize(sources: [Source.default])
7
+ @sources = sources
8
+ end
9
+
10
+ def definition_for(name, version)
11
+ @sources.each do |source|
12
+ response = source.lookup(name, version)
13
+ return JSON.parse(response.body).fetch('info', {}) if response
14
+ end
15
+ {}
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ module Python
5
+ class Source
6
+ attr_reader :name, :uri, :verify_ssl
7
+
8
+ def initialize(source)
9
+ @name = source['name']
10
+ @uri = URI.parse(source['url'])
11
+ @verify_ssl = source['verify_ssl']
12
+ end
13
+
14
+ def host
15
+ @uri.host
16
+ end
17
+
18
+ def uri_for(name, version)
19
+ "https://#{host}/pypi/#{name}/#{version}/json"
20
+ end
21
+
22
+ def lookup(name, version, http: Spandx.http)
23
+ response = http.get(uri_for(name, version))
24
+ response if http.ok?(response)
25
+ end
26
+
27
+ class << self
28
+ def sources_from(json)
29
+ meta = json['_meta']
30
+ meta['sources'].map do |hash|
31
+ new(hash)
32
+ end
33
+ end
34
+
35
+ def default
36
+ new(
37
+ 'name' => 'pypi',
38
+ 'url' => 'https://pypi.org/simple',
39
+ 'verify_ssl' => true
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -9,7 +9,7 @@ module Spandx
9
9
  end
10
10
 
11
11
  def licenses_for(name, version)
12
- found = index.licenses_for(name: name, version: version)
12
+ found = cache.licenses_for(name: name, version: version)
13
13
  found.any? ? found : details_on(name, version)['licenses'] || []
14
14
  end
15
15
 
@@ -17,12 +17,11 @@ module Spandx
17
17
 
18
18
  attr_reader :http
19
19
 
20
- def index
21
- @index ||= OfflineIndex.new(:rubygems)
20
+ def cache
21
+ @cache ||= ::Spandx::Core::Cache.new(:rubygems, url: 'https://github.com/mokhan/spandx-rubygems.git')
22
22
  end
23
23
 
24
24
  def details_on(name, version)
25
- Spandx.logger.debug("Cache miss: #{name}-#{version}")
26
25
  url = "https://rubygems.org/api/v2/rubygems/#{name}/versions/#{version}.json"
27
26
  response = http.get(url, default: {})
28
27
  http.ok?(response) ? parse(response.body) : {}
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spandx
4
- VERSION = '0.10.1'
4
+ VERSION = '0.11.0'
5
5
  end
data/lib/spandx.rb CHANGED
@@ -10,10 +10,12 @@ require 'net/hippie'
10
10
  require 'nokogiri'
11
11
  require 'pathname'
12
12
 
13
+ require 'spandx/core/cache'
13
14
  require 'spandx/core/content'
14
15
  require 'spandx/core/database'
15
16
  require 'spandx/core/dependency'
16
17
  require 'spandx/core/guess'
18
+ require 'spandx/core/http'
17
19
  require 'spandx/core/parser'
18
20
  require 'spandx/core/report'
19
21
  require 'spandx/core/score'
@@ -24,13 +26,13 @@ require 'spandx/dotnet/parsers/csproj'
24
26
  require 'spandx/dotnet/parsers/packages_config'
25
27
  require 'spandx/dotnet/parsers/sln'
26
28
  require 'spandx/dotnet/project_file'
27
- require 'spandx/gateways/http'
28
- require 'spandx/gateways/pypi'
29
+ require 'spandx/java/index'
29
30
  require 'spandx/java/metadata'
30
31
  require 'spandx/java/parsers/maven'
31
- require 'spandx/parsers/pipfile_lock'
32
+ require 'spandx/python/parsers/pipfile_lock'
33
+ require 'spandx/python/pypi'
34
+ require 'spandx/python/source'
32
35
  require 'spandx/rubygems/gateway'
33
- require 'spandx/rubygems/offline_index'
34
36
  require 'spandx/rubygems/parsers/gemfile_lock'
35
37
  require 'spandx/spdx/catalogue'
36
38
  require 'spandx/spdx/gateway'
@@ -52,7 +54,7 @@ module Spandx
52
54
  end
53
55
 
54
56
  def http
55
- @http ||= Spandx::Gateways::Http.new
57
+ @http ||= Spandx::Core::Http.new
56
58
  end
57
59
 
58
60
  def logger
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spandx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - mo khan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-16 00:00:00.000000000 Z
11
+ date: 2020-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -260,10 +260,12 @@ files:
260
260
  - lib/spandx/cli/commands/index/build.rb
261
261
  - lib/spandx/cli/commands/index/update.rb
262
262
  - lib/spandx/cli/commands/scan.rb
263
+ - lib/spandx/core/cache.rb
263
264
  - lib/spandx/core/content.rb
264
265
  - lib/spandx/core/database.rb
265
266
  - lib/spandx/core/dependency.rb
266
267
  - lib/spandx/core/guess.rb
268
+ - lib/spandx/core/http.rb
267
269
  - lib/spandx/core/parser.rb
268
270
  - lib/spandx/core/report.rb
269
271
  - lib/spandx/core/score.rb
@@ -274,13 +276,13 @@ files:
274
276
  - lib/spandx/dotnet/parsers/packages_config.rb
275
277
  - lib/spandx/dotnet/parsers/sln.rb
276
278
  - lib/spandx/dotnet/project_file.rb
277
- - lib/spandx/gateways/http.rb
278
- - lib/spandx/gateways/pypi.rb
279
+ - lib/spandx/java/index.rb
279
280
  - lib/spandx/java/metadata.rb
280
281
  - lib/spandx/java/parsers/maven.rb
281
- - lib/spandx/parsers/pipfile_lock.rb
282
+ - lib/spandx/python/parsers/pipfile_lock.rb
283
+ - lib/spandx/python/pypi.rb
284
+ - lib/spandx/python/source.rb
282
285
  - lib/spandx/rubygems/gateway.rb
283
- - lib/spandx/rubygems/offline_index.rb
284
286
  - lib/spandx/rubygems/parsers/gemfile_lock.rb
285
287
  - lib/spandx/spdx/catalogue.rb
286
288
  - lib/spandx/spdx/gateway.rb
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Spandx
4
- module Gateways
5
- class PyPI
6
- class Source
7
- attr_reader :name, :uri, :verify_ssl
8
-
9
- def initialize(source)
10
- @name = source['name']
11
- @uri = URI.parse(source['url'])
12
- @verify_ssl = source['verify_ssl']
13
- end
14
-
15
- def host
16
- @uri.host
17
- end
18
-
19
- def uri_for(name, version)
20
- "https://#{host}/pypi/#{name}/#{version}/json"
21
- end
22
-
23
- def lookup(name, version, http: Spandx.http)
24
- response = http.get(uri_for(name, version))
25
- response if http.ok?(response)
26
- end
27
-
28
- class << self
29
- def sources_from(json)
30
- meta = json['_meta']
31
- meta['sources'].map do |hash|
32
- Gateways::PyPI::Source.new(hash)
33
- end
34
- end
35
-
36
- def default
37
- new(
38
- 'name' => 'pypi',
39
- 'url' => 'https://pypi.org/simple',
40
- 'verify_ssl' => true
41
- )
42
- end
43
- end
44
- end
45
-
46
- def initialize(sources: [Source.default])
47
- @sources = sources
48
- end
49
-
50
- def definition_for(name, version)
51
- @sources.each do |source|
52
- response = source.lookup(name, version)
53
- return JSON.parse(response.body).fetch('info', {}) if response
54
- end
55
- {}
56
- end
57
- end
58
- end
59
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Spandx
4
- module Parsers
5
- class PipfileLock < ::Spandx::Core::Parser
6
- def self.matches?(filename)
7
- filename.match?(/Pipfile.*\.lock/)
8
- end
9
-
10
- def parse(lockfile)
11
- results = []
12
- dependencies_from(lockfile) do |x|
13
- results << ::Spandx::Core::Dependency.new(
14
- name: x[:name],
15
- version: x[:version],
16
- licenses: x[:licenses]
17
- )
18
- end
19
- results
20
- end
21
-
22
- private
23
-
24
- def dependencies_from(lockfile)
25
- json = JSON.parse(IO.read(lockfile))
26
- each_dependency(pypi_for(json), json) do |name, version, definition|
27
- yield({ name: name, version: version, licenses: [catalogue[definition['license']]] })
28
- end
29
- end
30
-
31
- def each_dependency(pypi, json, groups: %w[default develop])
32
- groups.each do |group|
33
- json[group].each do |name, value|
34
- version = canonicalize(value['version'])
35
- yield name, version, pypi.definition_for(name, version)
36
- end
37
- end
38
- end
39
-
40
- def canonicalize(version)
41
- version.gsub(/==/, '')
42
- end
43
-
44
- def pypi_for(json)
45
- Gateways::PyPI.new(
46
- sources: Gateways::PyPI::Source.sources_from(json)
47
- )
48
- end
49
- end
50
- end
51
- end