spdx 1.4.3 → 2.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -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