spandx 0.3.0 → 0.4.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: 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