spdx 1.4.3 → 2.0.11

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.
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spdx
4
+ class Exception
5
+ attr_reader :id, :name, :deprecated
6
+ alias deprecated? deprecated
7
+
8
+ def initialize(id, name, deprecated)
9
+ @id = id
10
+ @name = name
11
+ @deprecated = deprecated
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spdx
4
+ class License
5
+ attr_reader :id, :name, :osi_approved
6
+ alias osi_approved? osi_approved
7
+
8
+ def initialize(id, name, osi_approved)
9
+ @id = id
10
+ @name = name
11
+ @osi_approved = osi_approved
12
+ end
13
+ end
14
+ end
@@ -1,9 +1,14 @@
1
- require 'spdx/version'
2
- require 'spdx-licenses'
3
- require 'fuzzy_match'
1
+ # frozen_string_literal: true
2
+
3
+ require "spdx/version"
4
+ require "fuzzy_match"
5
+ require "spdx_parser"
6
+ require "json"
7
+ require_relative "exception"
8
+ require_relative "license"
4
9
 
5
10
  # Fuzzy matcher for licenses to SPDX standard licenses
6
- module Spdx # rubocop:disable Metrics/ModuleLength
11
+ module Spdx
7
12
  def self.find(name)
8
13
  name = name.strip
9
14
  return nil if commercial?(name)
@@ -19,23 +24,23 @@ module Spdx # rubocop:disable Metrics/ModuleLength
19
24
  end
20
25
 
21
26
  def self.commercial?(name)
22
- name.casecmp('commercial').zero?
27
+ name.casecmp("commercial").zero?
23
28
  end
24
29
 
25
30
  def self.non_spdx?(name)
26
- ['standard pil license'].include? name.downcase
31
+ ["standard pil license"].include? name.downcase
27
32
  end
28
33
 
29
34
  def self.lookup(name)
30
35
  return false if name.nil?
31
- return SpdxLicenses[name] if SpdxLicenses.exist?(name)
36
+ return lookup_license(name) if license_exists?(name)
32
37
 
33
- lowercase = SpdxLicenses.data.keys.sort.find { |k| k.casecmp(name).zero? }
34
- SpdxLicenses[lowercase] if lowercase
38
+ lowercase = licenses.keys.sort.find { |k| k.casecmp(name).zero? }
39
+ lookup_license(lowercase) if lowercase
35
40
  end
36
41
 
37
42
  def self.closest(name)
38
- name.gsub!(/#{stop_words.join('|')}/i, '')
43
+ name.gsub!(/#{stop_words.join('|')}/i, "")
39
44
  name.gsub!(/(\d)/, ' \1 ')
40
45
  best_match = fuzzy_match(name)
41
46
  return nil unless best_match
@@ -45,8 +50,8 @@ module Spdx # rubocop:disable Metrics/ModuleLength
45
50
 
46
51
  def self.matches(name, max_distance = 40)
47
52
  names.map { |key| [key, Text::Levenshtein.distance(name, key)] }
48
- .select { |arr| arr[1] <= max_distance }
49
- .sort_by { |arr| arr[1] }
53
+ .select { |arr| arr[1] <= max_distance }
54
+ .sort_by { |arr| arr[1] }
50
55
  end
51
56
 
52
57
  def self.fuzzy_match(name)
@@ -58,7 +63,7 @@ module Spdx # rubocop:disable Metrics/ModuleLength
58
63
  end
59
64
 
60
65
  def self.find_by_name(name)
61
- match = SpdxLicenses.data.find { |_k, v| v['name'] == name }
66
+ match = licenses.find { |_k, v| v["name"] == name }
62
67
  lookup(match[0]) if match
63
68
  end
64
69
 
@@ -76,105 +81,163 @@ module Spdx # rubocop:disable Metrics/ModuleLength
76
81
  lookup "#{match[1]}GPL-#{match[2]}.#{match[3] || 0}#{match[4]}"
77
82
  end
78
83
 
79
- def self.special_cases # rubocop:disable Metrics/MethodLength
84
+ def self.special_cases
80
85
  {
81
- 'perl_5' => 'Artistic-1.0-Perl',
82
- 'bsd3' => 'BSD-3-Clause',
83
- 'bsd' => 'BSD-3-Clause',
84
- 'bsd license' => 'BSD-3-Clause',
85
- 'new bsd license' => 'BSD-3-Clause',
86
- 'gnu gpl v2' => 'GPL-2.0-only',
87
- 'gpl' => 'GPL-2.0+',
88
- 'gpl-2 | gpl-3 [expanded from: gpl (≥ 2.0)]' => 'GPL-2.0+',
89
- 'gpl-2 | gpl-3 [expanded from: gpl]' => 'GPL-2.0+',
90
- 'gpl-2 | gpl-3 [expanded from: gpl (≥ 2)]' => 'GPL-2.0+',
91
- 'gpl-2 | gpl-3' => 'GPL-2.0+',
92
- 'gplv2 or later' => 'GPL-2.0+',
93
- 'the gpl v3' => 'GPL-3.0',
94
- 'gpl (≥ 3)' => 'GPL-3.0+',
95
- 'mpl2.0' => 'mpl-2.0',
96
- 'mpl1' => 'mpl-1.0',
97
- 'mpl1.0' => 'mpl-1.0',
98
- 'mpl1.1' => 'mpl-1.1',
99
- 'mpl2' => 'mpl-2.0',
100
- 'gnu lesser general public license' => 'LGPL-2.1+',
101
- 'lgplv2 or later' => 'LGPL-2.1+',
102
- 'gpl2 w/ cpe' => 'GPL-2.0-with-classpath-exception',
103
- 'new bsd license (gpl-compatible)' => 'BSD-3-Clause',
104
- 'public domain' => 'Unlicense',
105
- 'cc0' => 'CC0-1.0',
106
- 'artistic_2' => 'Artistic-2.0',
107
- 'artistic_1' => 'Artistic-1.0',
108
- 'alv2' => 'Apache-2.0',
109
- 'asl 2.0' => 'Apache-2.0',
110
- 'mpl 2.0' => 'MPL-2.0',
111
- 'publicdomain' => 'Unlicense',
112
- 'unlicensed' => 'Unlicense',
113
- 'psfl' => 'Python-2.0',
114
- 'psf' => 'Python-2.0',
115
- 'asl2' => 'Apache-2.0',
116
- 'al2' => 'Apache-2.0',
117
- 'aslv2' => 'Apache-2.0',
118
- 'apache_2_0' => 'Apache-2.0',
119
- 'apache_v2' => 'Apache-2.0',
120
- 'zpl 1.1' => 'ZPL-1.1',
121
- 'zpl 2.0' => 'ZPL-2.0',
122
- 'zpl 2.1' => 'ZPL-2.1',
123
- 'lgpl_2_1' => 'LGPL-2.1',
124
- 'lgpl_v2_1' => 'LGPL-2.1',
125
- 'lgpl version 3' => 'LGPL-3.0',
126
- 'gnu lgpl v3+' => 'LGPL-3.0',
127
- 'gnu lgpl' => 'LGPL-2.1+',
128
- 'cc by-sa 4.0' => 'CC-BY-SA-4.0',
129
- 'cc by-nc-sa 3.0' => 'CC-BY-NC-SA-3.0',
130
- 'cc by-sa 3.0' => 'CC-BY-SA-3.0',
131
- 'mpl v2.0' => 'MPL-2.0',
132
- 'mplv2.0' => 'MPL-2.0',
133
- 'mplv2' => 'MPL-2.0',
134
- 'cpal v1.0' => 'CPAL-1.0',
135
- 'cddl 1.0' => 'CDDL-1.0',
136
- 'cddl 1.1' => 'CDDL-1.1',
137
- 'epl' => 'EPL-1.0',
138
- 'mit-license' => 'MIT',
139
- '(mit or x11)' => 'MIT',
140
- 'iscl' => 'ISC',
141
- 'wtf' => 'WTFPL',
142
- '2-clause bsdl' => 'BSD-2-clause',
143
- '3-clause bsdl' => 'BSD-3-clause',
144
- '2-clause bsd' => 'BSD-2-clause',
145
- '3-clause bsd' => 'BSD-3-clause',
146
- 'bsd 3-clause' => 'BSD-3-clause',
147
- 'bsd 2-clause' => 'BSD-2-clause',
148
- 'two-clause bsd-style license' => 'BSD-2-clause',
149
- 'bsd style' => 'BSD-3-clause',
150
- 'cc0 1.0 universal (cc0 1.0) public domain dedication' => 'CC0-1.0',
151
- 'common development and distribution license 1.0 (cddl-1.0)' => 'CDDL-1.0',
152
- 'european union public licence 1.0 (eupl 1.0)' => 'EUPL-1.0',
153
- 'european union public licence 1.1 (eupl 1.1)' => 'EUPL-1.1',
154
- 'european union public licence 1.2 (eupl 1.2)' => 'EUPL-1.2',
155
- 'vovida software license 1.0' => 'VSL-1.0',
156
- 'w3c license' => 'W3C',
157
- 'zlib/libpng license' => 'zlib-acknowledgement',
158
- 'gnu general public license (gpl)' => 'GPL-2.0+',
159
- 'gnu general public license v2 (gplv2)' => 'GPL-2.0',
160
- 'gnu general public license v2 or later (gplv2+)' => 'GPL-2.0+',
161
- 'gnu general public license v3 (gplv3)' => 'GPL-3.0',
162
- 'gnu general public license v3 or later (gplv3+)' => 'GPL-3.0+',
163
- 'gnu lesser general public license v2 (lgplv2)' => 'LGPL-2.0',
164
- 'gnu lesser general public license v2 or later (lgplv2+)' => 'LGPL-2.0+',
165
- 'gnu lesser general public license v3 (lgplv3)' => 'LGPL-3.0',
166
- 'gnu lesser general public license v3 or later (lgplv3+)' => 'LGPL-3.0+',
167
- 'gnu library or lesser general public license (lgpl)' => 'LGPL-2.0+',
168
- 'netscape public License (npl)' => 'NPL-1.1',
169
- 'apache software license' => 'Apache-2.0',
170
- 'academic free license (afl)' => 'AFL-3.0',
171
- 'gnu free documentation license (fdl)' => 'GFDL-1.3',
172
- 'sun industry standards source license (sissl)' => 'SISSL-1.2',
173
- 'zope public license' => 'ZPL-2.1'
86
+ "perl_5" => "Artistic-1.0-Perl",
87
+ "bsd3" => "BSD-3-Clause",
88
+ "bsd" => "BSD-3-Clause",
89
+ "bsd license" => "BSD-3-Clause",
90
+ "new bsd license" => "BSD-3-Clause",
91
+ "gnu gpl v2" => "GPL-2.0-only",
92
+ "gpl" => "GPL-2.0+",
93
+ "gpl-2 | gpl-3 [expanded from: gpl (≥ 2.0)]" => "GPL-2.0+",
94
+ "gpl-2 | gpl-3 [expanded from: gpl]" => "GPL-2.0+",
95
+ "gpl-2 | gpl-3 [expanded from: gpl (≥ 2)]" => "GPL-2.0+",
96
+ "gpl-2 | gpl-3" => "GPL-2.0+",
97
+ "gplv2 or later" => "GPL-2.0+",
98
+ "the gpl v3" => "GPL-3.0",
99
+ "gpl (≥ 3)" => "GPL-3.0+",
100
+ "mpl2.0" => "mpl-2.0",
101
+ "mpl1" => "mpl-1.0",
102
+ "mpl1.0" => "mpl-1.0",
103
+ "mpl1.1" => "mpl-1.1",
104
+ "mpl2" => "mpl-2.0",
105
+ "gnu lesser general public license" => "LGPL-2.1+",
106
+ "lgplv2 or later" => "LGPL-2.1+",
107
+ "gpl2 w/ cpe" => "GPL-2.0-with-classpath-exception",
108
+ "new bsd license (gpl-compatible)" => "BSD-3-Clause",
109
+ "public domain" => "Unlicense",
110
+ "cc0" => "CC0-1.0",
111
+ "artistic_2" => "Artistic-2.0",
112
+ "artistic_1" => "Artistic-1.0",
113
+ "alv2" => "Apache-2.0",
114
+ "asl" => "Apache-2.0",
115
+ "asl 2.0" => "Apache-2.0",
116
+ "mpl 2.0" => "MPL-2.0",
117
+ "publicdomain" => "Unlicense",
118
+ "unlicensed" => "Unlicense",
119
+ "psfl" => "Python-2.0",
120
+ "psf" => "Python-2.0",
121
+ "psf 2" => "Python-2.0",
122
+ "psf 2.0" => "Python-2.0",
123
+ "asl2" => "Apache-2.0",
124
+ "al2" => "Apache-2.0",
125
+ "aslv2" => "Apache-2.0",
126
+ "apache_2_0" => "Apache-2.0",
127
+ "apache_v2" => "Apache-2.0",
128
+ "zpl 1.1" => "ZPL-1.1",
129
+ "zpl 2.0" => "ZPL-2.0",
130
+ "zpl 2.1" => "ZPL-2.1",
131
+ "lgpl_2_1" => "LGPL-2.1",
132
+ "lgpl_v2_1" => "LGPL-2.1",
133
+ "lgpl version 3" => "LGPL-3.0",
134
+ "gnu lgpl v3+" => "LGPL-3.0",
135
+ "gnu lgpl" => "LGPL-2.1+",
136
+ "cc by-sa 4.0" => "CC-BY-SA-4.0",
137
+ "cc by-nc-sa 3.0" => "CC-BY-NC-SA-3.0",
138
+ "cc by-sa 3.0" => "CC-BY-SA-3.0",
139
+ "mpl v2.0" => "MPL-2.0",
140
+ "mplv2.0" => "MPL-2.0",
141
+ "mplv2" => "MPL-2.0",
142
+ "cpal v1.0" => "CPAL-1.0",
143
+ "cddl 1.0" => "CDDL-1.0",
144
+ "cddl 1.1" => "CDDL-1.1",
145
+ "epl" => "EPL-1.0",
146
+ "mit-license" => "MIT",
147
+ "(mit or x11)" => "MIT",
148
+ "iscl" => "ISC",
149
+ "wtf" => "WTFPL",
150
+ "2-clause bsdl" => "BSD-2-clause",
151
+ "3-clause bsdl" => "BSD-3-clause",
152
+ "2-clause bsd" => "BSD-2-clause",
153
+ "3-clause bsd" => "BSD-3-clause",
154
+ "bsd 3-clause" => "BSD-3-clause",
155
+ "bsd 2-clause" => "BSD-2-clause",
156
+ "two-clause bsd-style license" => "BSD-2-clause",
157
+ "bsd style" => "BSD-3-clause",
158
+ "cc0 1.0 universal (cc0 1.0) public domain dedication" => "CC0-1.0",
159
+ "common development and distribution license 1.0 (cddl-1.0)" => "CDDL-1.0",
160
+ "european union public licence 1.0 (eupl 1.0)" => "EUPL-1.0",
161
+ "european union public licence 1.1 (eupl 1.1)" => "EUPL-1.1",
162
+ "european union public licence 1.2 (eupl 1.2)" => "EUPL-1.2",
163
+ "vovida software license 1.0" => "VSL-1.0",
164
+ "w3c license" => "W3C",
165
+ "zlib/libpng license" => "zlib-acknowledgement",
166
+ "gnu general public license (gpl)" => "GPL-2.0+",
167
+ "gnu general public license v2 (gplv2)" => "GPL-2.0",
168
+ "gnu general public license v2 or later (gplv2+)" => "GPL-2.0+",
169
+ "gnu general public license v3 (gplv3)" => "GPL-3.0",
170
+ "gnu general public license v3 or later (gplv3+)" => "GPL-3.0+",
171
+ "gnu lesser general public license v2 (lgplv2)" => "LGPL-2.0",
172
+ "gnu lesser general public license v2 or later (lgplv2+)" => "LGPL-2.0+",
173
+ "gnu lesser general public license v3 (lgplv3)" => "LGPL-3.0",
174
+ "gnu lesser general public license v3 or later (lgplv3+)" => "LGPL-3.0+",
175
+ "gnu library or lesser general public license (lgpl)" => "LGPL-2.0+",
176
+ "netscape public License (npl)" => "NPL-1.1",
177
+ "apache software license" => "Apache-2.0",
178
+ "academic free license (afl)" => "AFL-3.0",
179
+ "gnu free documentation license (fdl)" => "GFDL-1.3",
180
+ "sun industry standards source license (sissl)" => "SISSL-1.2",
181
+ "zope public license" => "ZPL-2.1",
174
182
  }
175
183
  end
176
184
 
177
185
  def self.names
178
- (SpdxLicenses.data.keys + SpdxLicenses.data.map { |_k, v| v['name'] }).sort
186
+ (licenses.keys + licenses.map { |_k, v| v["name"] }).sort
187
+ end
188
+
189
+ def self.exceptions
190
+ unless defined?(@exceptions)
191
+ data = JSON.parse(File.read(File.expand_path("../exceptions.json", __dir__)))
192
+ @exceptions = {}
193
+ data["exceptions"].each do |details|
194
+ id = details.delete("licenseExceptionId")
195
+ @exceptions[id] = details
196
+ end
197
+ end
198
+ @exceptions
199
+ end
200
+
201
+ def self.license_exists?(id)
202
+ licenses.key?(id.to_s)
203
+ end
204
+
205
+ def self.lookup_license(id)
206
+ json = licenses[id.to_s]
207
+ Spdx::License.new(id.to_s, json["name"], json["isOsiApproved"]) if json
208
+ end
209
+
210
+ def self.lookup_exception(id)
211
+ json = exceptions[id.to_s]
212
+ Spdx::Exception.new(id.to_s, json["name"], json["isDeprecatedLicenseId"]) if json
213
+ end
214
+
215
+ def self.exception_exists?(id)
216
+ exceptions.key?(id.to_s)
217
+ end
218
+
219
+ def self.licenses
220
+ unless defined?(@licenses)
221
+ data = JSON.parse(File.read(File.expand_path("../licenses.json", __dir__)))
222
+ @licenses = {}
223
+ data["licenses"].each do |details|
224
+ id = details.delete("licenseId")
225
+ @licenses[id] = details
226
+ end
227
+ end
228
+ @licenses
229
+ end
230
+
231
+ def self.valid_spdx?(spdx_string)
232
+ return false unless spdx_string.is_a?(String)
233
+
234
+ SpdxParser.parse(spdx_string)
235
+ true
236
+ rescue SpdxGrammar::SpdxParseError
237
+ false
238
+ end
239
+
240
+ def self.parse_spdx(spdx_string)
241
+ SpdxParser.parse(spdx_string)
179
242
  end
180
243
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Spdx
2
- VERSION = '1.4.3'.freeze
4
+ VERSION = "2.0.11"
3
5
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpdxGrammar
4
+ class CompoundExpression < Treetop::Runtime::SyntaxNode
5
+ def licenses
6
+ elements[0].licenses
7
+ end
8
+ end
9
+
10
+ class LogicalOr < Treetop::Runtime::SyntaxNode
11
+ end
12
+
13
+ class LogicalAnd < Treetop::Runtime::SyntaxNode
14
+ end
15
+
16
+ class With < Treetop::Runtime::SyntaxNode
17
+ end
18
+
19
+ class None < Treetop::Runtime::SyntaxNode
20
+ def licenses
21
+ []
22
+ end
23
+ end
24
+
25
+ class NoAssertion < Treetop::Runtime::SyntaxNode
26
+ def licenses
27
+ []
28
+ end
29
+ end
30
+
31
+ class License < Treetop::Runtime::SyntaxNode
32
+ def licenses
33
+ text_value
34
+ end
35
+ end
36
+
37
+ class LicenseException < Treetop::Runtime::SyntaxNode
38
+ # TODO: actually do license exceptions
39
+ end
40
+
41
+ class Body < Treetop::Runtime::SyntaxNode
42
+ def licenses
43
+ elements.map { |node| node.licenses if node.respond_to?(:licenses) }.flatten.uniq.compact
44
+ end
45
+ end
46
+
47
+ class SpdxParseError < StandardError
48
+ end
49
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "treetop"
4
+ require "set"
5
+
6
+ require_relative "spdx_grammar"
7
+
8
+ class SpdxParser
9
+ Treetop.load(File.expand_path(File.join(File.dirname(__FILE__), "spdx_parser.treetop")))
10
+
11
+ SKIP_PARENS = ["NONE", "NOASSERTION", ""].freeze
12
+
13
+ def self.parse(data)
14
+ parse_tree(data)
15
+ end
16
+
17
+ def self.parse_licenses(data)
18
+ tree = parse_tree(data)
19
+ tree.get_licenses
20
+ end
21
+
22
+ private_class_method def self.parse_tree(data)
23
+ parser = SpdxGrammarParser.new # The generated grammar parser is not thread safe
24
+ # Couldn't figure out treetop to make parens optional
25
+ data = "(#{data})" unless SKIP_PARENS.include?(data)
26
+
27
+ tree = parser.parse(data)
28
+ raise SpdxGrammar::SpdxParseError, "Unable to parse expression '#{data}'. Parse error at offset: #{parser.index}" if tree.nil?
29
+
30
+ clean_tree(tree)
31
+ tree
32
+ end
33
+
34
+ private_class_method def self.clean_tree(root_node)
35
+ return if root_node.elements.nil?
36
+
37
+ root_node.elements.delete_if { |node| node.class.name == "Treetop::Runtime::SyntaxNode" }
38
+ root_node.elements.each { |node| clean_tree(node) }
39
+ end
40
+ end