spandx 0.3.0 → 0.4.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: 14b8433caf34da68102cdf2db650684c9dbccc2e32509a63a44021978a8d1f6e
4
- data.tar.gz: c7c342040a9c5a666708096f30228823a815256587ddd99b9b77fcb7f6856259
3
+ metadata.gz: 60e89d9935bbf82bb28b848858df9b48a736613600808721b6feddf511ed2f8a
4
+ data.tar.gz: a3e0396a012747f80b97376c1afbccb646f4e8a7bc379998fb89fed35d1a188b
5
5
  SHA512:
6
- metadata.gz: 9d916195c87c1162ec60ace64a7c36c5b693c51445361cf11242ca9ceb32c9a8b932d1a2bd65026757c56e556cdc677a738bb42f6dd266652c897737ce2da281
7
- data.tar.gz: 92df5d8346276b1998f28bfad73ef26fd6e9bf300589997e5d6ad517e7044974b52b659082acae52e496ea51ff77e3f7bd8d4faa5f4d14d39c20c44a8fb529af
6
+ metadata.gz: c27a27d68f51fcecd579edbcfd2a1ce591f2d2ca9d647df94280c78aa78e0b5a4af7dfc2b08ca0cccb93d3f512a3989dfc6d11846540d69bd814974df2e55d44
7
+ data.tar.gz: b01f6403eff92f6438f707806b4b976e651c0765d3b9dae5beb46fd801aeab982e7ca1c3e7315102660006f023fb67b8a66e8984533c58678408613c8fd5c051
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- Version 0.3.0
1
+ Version 0.4.0
2
2
 
3
3
  # Changelog
4
4
 
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.4.0] - 2020-02-02
13
+ ### Added
14
+ - Add command to build offline index of nuget packages and their licenses.
15
+
12
16
  ## [0.3.0] - 2020-01-29
13
17
  ### Added
14
18
  - Add `pom.xml` parser
@@ -57,7 +61,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
57
61
  ### Added
58
62
  - Provide ruby API to the latest SPDX catalogue.
59
63
 
60
- [Unreleased]: https://github.com/mokhan/spandx/compare/v0.3.0...HEAD
64
+ [Unreleased]: https://github.com/mokhan/spandx/compare/v0.4.0...HEAD
65
+ [0.4.0]: https://github.com/mokhan/spandx/compare/v0.3.0...v0.4.0
61
66
  [0.3.0]: https://github.com/mokhan/spandx/compare/v0.2.0...v0.3.0
62
67
  [0.2.0]: https://github.com/mokhan/spandx/compare/v0.1.7...v0.2.0
63
68
  [0.1.7]: https://github.com/mokhan/spandx/compare/v0.1.6...v0.1.7
data/lib/spandx/cli.rb CHANGED
@@ -4,6 +4,7 @@ require 'thor'
4
4
 
5
5
  require 'spandx'
6
6
  require 'spandx/command'
7
+ require 'spandx/commands/build'
7
8
  require 'spandx/commands/scan'
8
9
 
9
10
  module Spandx
@@ -16,7 +17,21 @@ module Spandx
16
17
  end
17
18
  map %w[--version -v] => :version
18
19
 
19
- desc 'scan LOCKFILE', 'Command description...'
20
+ desc 'build', 'Build a package index'
21
+ method_option :help, aliases: '-h', type: :boolean,
22
+ desc: 'Display usage information'
23
+ method_option :directory, aliases: '-d', type: :string,
24
+ desc: 'Directory to build index in'
25
+ def build(*)
26
+ if options[:help]
27
+ invoke :help, ['build']
28
+ else
29
+ require_relative 'commands/build'
30
+ Spandx::Commands::Build.new(options).execute
31
+ end
32
+ end
33
+
34
+ desc 'scan LOCKFILE', 'Scan a lockfile and list dependencies/licenses'
20
35
  method_option :help, aliases: '-h', type: :boolean,
21
36
  desc: 'Display usage information'
22
37
  def scan(lockfile = nil)
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../command'
4
+
5
+ module Spandx
6
+ module Commands
7
+ class Build < Spandx::Command
8
+ def initialize(options)
9
+ @options = options
10
+ end
11
+
12
+ def execute(output: $stdout)
13
+ index = Spandx::Index.new(directory: @options[:directory])
14
+ gateways.each do |gateway|
15
+ gateway.update!(index)
16
+ end
17
+ output.puts 'OK'
18
+ end
19
+
20
+ private
21
+
22
+ def catalogue
23
+ Spandx::Catalogue.from_git
24
+ end
25
+
26
+ def gateways
27
+ [
28
+ Spandx::Gateways::Nuget.new(catalogue: catalogue)
29
+ ]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -11,7 +11,7 @@ module Spandx
11
11
 
12
12
  def get(uri, default: nil)
13
13
  driver.with_retry do |client|
14
- client.get(uri)
14
+ client.get(Addressable::URI.escape(uri))
15
15
  end
16
16
  rescue *Net::Hippie::CONNECTION_ERRORS
17
17
  default
@@ -6,10 +6,25 @@ module Spandx
6
6
  # https://api.nuget.org/v3-flatcontainer/#{package.name}/index.json
7
7
  # https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource
8
8
  class Nuget
9
+ attr_reader :host
10
+
9
11
  def initialize(http: Spandx.http, catalogue:)
10
12
  @http = http
11
13
  @catalogue = catalogue
12
14
  @guess = Guess.new(catalogue)
15
+ @host = 'api.nuget.org'
16
+ end
17
+
18
+ def update!(index, limit: nil)
19
+ counter = Concurrent::AtomicFixnum.new(0)
20
+ each do |spec|
21
+ upsert_into!(index, spec)
22
+
23
+ if limit
24
+ counter.increment
25
+ break if counter.value > limit
26
+ end
27
+ end
13
28
  end
14
29
 
15
30
  def licenses_for(name, version)
@@ -23,19 +38,34 @@ module Spandx
23
38
 
24
39
  attr_reader :http, :catalogue, :guess
25
40
 
41
+ def each
42
+ each_page do |page|
43
+ items_from(page).each do |item|
44
+ yield fetch_json(item['@id'])
45
+ end
46
+ end
47
+ end
48
+
49
+ def each_page
50
+ url = "https://#{host}/v3/catalog0/index.json"
51
+ items_from(fetch_json(url)).each do |page|
52
+ yield fetch_json(page['@id'])
53
+ end
54
+ end
55
+
26
56
  def nuspec_url_for(name, version)
27
- "https://api.nuget.org/v3-flatcontainer/#{name}/#{version}/#{name}.nuspec"
57
+ "https://#{host}/v3-flatcontainer/#{name}/#{version}/#{name}.nuspec"
28
58
  end
29
59
 
30
60
  def nuspec_for(name, version)
31
- response = http.get(nuspec_url_for(name, version))
32
- from_xml(response.body) if http.ok?(response)
61
+ fetch_xml(nuspec_url_for(name, version))
33
62
  end
34
63
 
35
64
  def from_xml(xml)
36
65
  Nokogiri::XML(xml).tap(&:remove_namespaces!)
37
66
  end
38
67
 
68
+ # TODO: Fix parsing https://github.com/NuGet/Home/wiki/Packaging-License-within-the-nupkg#license
39
69
  def extract_licenses_from(document)
40
70
  licenses = document.search('//package/metadata/license')
41
71
  licenses.map(&:text) if licenses.any?
@@ -53,6 +83,32 @@ module Spandx
53
83
 
54
84
  guess.license_for(response.body) if http.ok?(response)
55
85
  end
86
+
87
+ def fetch_json(url)
88
+ response = http.get(url)
89
+ http.ok?(response) ? JSON.parse(response.body) : {}
90
+ end
91
+
92
+ def fetch_xml(url)
93
+ response = http.get(url)
94
+ http.ok?(response) ? from_xml(response.body) : from_xml('<empty />')
95
+ end
96
+
97
+ def items_from(page)
98
+ page['items']
99
+ .sort_by { |x| x['commitTimeStamp'] }
100
+ .reverse
101
+ end
102
+
103
+ def upsert_into!(index, spec)
104
+ key = [host, spec['id'], spec['version']]
105
+ return if index.indexed?(key)
106
+
107
+ if (license = spec['licenseExpression'])
108
+ index.write(key, [license])
109
+ end
110
+ puts [license, key].inspect
111
+ end
56
112
  end
57
113
  end
58
114
  end
@@ -17,7 +17,7 @@ module Spandx
17
17
  end
18
18
 
19
19
  def uri_for(name, version)
20
- URI.parse("https://#{host}/pypi/#{name}/#{version}/json")
20
+ "https://#{host}/pypi/#{name}/#{version}/json"
21
21
  end
22
22
 
23
23
  def lookup(name, version, http: Spandx.http)
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spandx
4
+ class Index
5
+ DEFAULT_DIR = File.expand_path(File.join(Dir.home, '.local', 'share', 'spandx'))
6
+ attr_reader :directory
7
+
8
+ def initialize(directory: DEFAULT_DIR)
9
+ @directory = directory ? File.expand_path(directory) : DEFAULT_DIR
10
+ end
11
+
12
+ def indexed?(key)
13
+ File.exist?(data_file_for(digest_for(key)))
14
+ end
15
+
16
+ def read(key)
17
+ open_data(digest_for(key), mode: 'r', &:read)
18
+ end
19
+
20
+ def write(key, data)
21
+ return if data.nil? || data.empty?
22
+
23
+ open_data(digest_for(key)) do |x|
24
+ x.write(data)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def digest_for(components)
31
+ Digest::SHA1.hexdigest(Array(components).join('/'))
32
+ end
33
+
34
+ def open_data(key, mode: 'w')
35
+ FileUtils.mkdir_p(data_dir_for(key))
36
+ File.open(data_file_for(key), mode) do |file|
37
+ yield file
38
+ end
39
+ end
40
+
41
+ def data_dir_for(index_key)
42
+ File.join(directory, *index_key.scan(/../))
43
+ end
44
+
45
+ def data_file_for(key)
46
+ File.join(data_dir_for(key), 'data')
47
+ end
48
+ end
49
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spandx
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/spandx.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'addressable/uri'
3
4
  require 'bundler'
5
+ require 'concurrent'
4
6
  require 'forwardable'
5
7
  require 'json'
6
8
  require 'net/hippie'
@@ -18,6 +20,7 @@ require 'spandx/gateways/pypi'
18
20
  require 'spandx/gateways/rubygems'
19
21
  require 'spandx/gateways/spdx'
20
22
  require 'spandx/guess'
23
+ require 'spandx/index'
21
24
  require 'spandx/license'
22
25
  require 'spandx/parsers'
23
26
  require 'spandx/report'
data/spandx.gemspec CHANGED
@@ -30,7 +30,9 @@ Gem::Specification.new do |spec|
30
30
  spec.require_paths = ['lib']
31
31
 
32
32
  spec.required_ruby_version = '>= 2.4.0'
33
+ spec.add_dependency 'addressable', '~> 2.7'
33
34
  spec.add_dependency 'bundler', '>= 1.16', '< 3.0.0'
35
+ spec.add_dependency 'concurrent-ruby-ext', '~> 1.1'
34
36
  spec.add_dependency 'net-hippie', '~> 0.3'
35
37
  spec.add_dependency 'nokogiri', '~> 1.10'
36
38
  spec.add_dependency 'text', '~> 1.3'
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spandx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.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-01-29 00:00:00.000000000 Z
11
+ date: 2020-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: addressable
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.7'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -30,6 +44,20 @@ dependencies:
30
44
  - - "<"
31
45
  - !ruby/object:Gem::Version
32
46
  version: 3.0.0
47
+ - !ruby/object:Gem::Dependency
48
+ name: concurrent-ruby-ext
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.1'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.1'
33
61
  - !ruby/object:Gem::Dependency
34
62
  name: net-hippie
35
63
  requirement: !ruby/object:Gem::Requirement
@@ -214,6 +242,7 @@ files:
214
242
  - lib/spandx/catalogue.rb
215
243
  - lib/spandx/cli.rb
216
244
  - lib/spandx/command.rb
245
+ - lib/spandx/commands/build.rb
217
246
  - lib/spandx/commands/scan.rb
218
247
  - lib/spandx/content.rb
219
248
  - lib/spandx/database.rb
@@ -224,6 +253,7 @@ files:
224
253
  - lib/spandx/gateways/rubygems.rb
225
254
  - lib/spandx/gateways/spdx.rb
226
255
  - lib/spandx/guess.rb
256
+ - lib/spandx/index.rb
227
257
  - lib/spandx/license.rb
228
258
  - lib/spandx/parsers.rb
229
259
  - lib/spandx/parsers/base.rb