spandx 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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