diff options
| author | mo khan <mo.khan@gmail.com> | 2020-05-07 17:25:56 -0600 |
|---|---|---|
| committer | mo khan <mo.khan@gmail.com> | 2020-05-07 17:25:56 -0600 |
| commit | cc9eda11425e6f6ec3243fbcd768c4e2a37adf2a (patch) | |
| tree | 52745bf01649b5f7b3be20ce6f68f173c65ca921 | |
| parent | 99dc28a123b59d5052db3e20ec428c023091ee13 (diff) | |
Build a composite license that traverses and expression tree
| -rw-r--r-- | lib/spandx/core/guess.rb | 9 | ||||
| -rw-r--r-- | lib/spandx/spdx/composite_license.rb | 59 | ||||
| -rw-r--r-- | spec/unit/core/guess_spec.rb | 22 | ||||
| -rw-r--r-- | spec/unit/spdx/composite_license_spec.rb | 47 |
4 files changed, 119 insertions, 18 deletions
diff --git a/lib/spandx/core/guess.rb b/lib/spandx/core/guess.rb index 4383374..62967ce 100644 --- a/lib/spandx/core/guess.rb +++ b/lib/spandx/core/guess.rb @@ -7,7 +7,6 @@ module Spandx def initialize(catalogue) @catalogue = catalogue - @expression_cache = {} end def license_for(raw) @@ -79,12 +78,8 @@ module Spandx end def from_expression(content) - @expression_cache.fetch(content.raw) do - tree = Spdx::Expression.new.parse(content.raw) - @expression_cache[content.raw] = catalogue[tree[0][:left].to_s] - end - rescue Parslet::ParseFailed - nil + Spandx::Spdx::CompositeLicense + .from_expression(content.raw, catalogue) end end end diff --git a/lib/spandx/spdx/composite_license.rb b/lib/spandx/spdx/composite_license.rb new file mode 100644 index 0000000..e429778 --- /dev/null +++ b/lib/spandx/spdx/composite_license.rb @@ -0,0 +1,59 @@ + +module Spandx + module Spdx + class CompositeLicense < License + def self.from_expression(expression, catalogue) + tree = Spdx::Expression.new.parse(expression) + new(tree[0], catalogue) + rescue Parslet::ParseFailed + nil + end + + def initialize(tree, catalogue) + @catalogue = catalogue + @tree = tree + super({ }) + end + + def id + if right + ["(#{left.id}", operator, "#{right.id})"].compact.join(' ').squeeze(' ').strip + else + "#{left.id}" + end + end + + def name + if right + [left.name, operator, right.name].compact.join(' ').squeeze(' ').strip + else + left.name + end + end + + private + + def left + node_for(@tree[:left]) + end + + def operator + @tree[:op].to_s.upcase + end + + def right + node_for(@tree[:right]) + end + + def node_for(item) + return if item.nil? + + if item.is_a?(Hash) + self.class.new(item, @catalogue) + else + @catalogue[item.to_s] || License.unknown(item.to_s) + end + end + end + end +end diff --git a/spec/unit/core/guess_spec.rb b/spec/unit/core/guess_spec.rb index f19aa18..63f0bf2 100644 --- a/spec/unit/core/guess_spec.rb +++ b/spec/unit/core/guess_spec.rb @@ -44,20 +44,20 @@ RSpec.describe Spandx::Core::Guess do specify { expect(subject.license_for(content)&.id).to eql('MIT') } end - specify { expect(subject.license_for('(0BSD OR MIT)')&.id).to eql('0BSD') } - specify { expect(subject.license_for('(BSD-2-Clause OR MIT OR Apache-2.0)')&.id).to eql('BSD-2-Clause') } - specify { expect(subject.license_for('(BSD-3-Clause OR GPL-2.0)')&.id).to eql('BSD-3-Clause') } - specify { expect(subject.license_for('(MIT AND CC-BY-3.0)')&.id).to eql('MIT') } - specify { expect(subject.license_for('(MIT AND Zlib)')&.id).to eql('MIT') } - specify { expect(subject.license_for('(MIT OR Apache-2.0)')&.id).to eql('MIT') } - specify { expect(subject.license_for('(MIT OR CC0-1.0)')&.id).to eql('MIT') } - specify { expect(subject.license_for('(MIT OR GPL-3.0)')&.id).to eql('MIT') } - specify { expect(subject.license_for('(WTFPL OR MIT)')&.id).to eql('WTFPL') } + specify { expect(subject.license_for('(0BSD OR MIT)')&.id).to eql('(0BSD OR MIT)') } + specify { expect(subject.license_for('(BSD-2-Clause OR MIT OR Apache-2.0)')&.id).to eql('(BSD-2-Clause OR MIT OR Apache-2.0)') } + specify { expect(subject.license_for('(BSD-3-Clause OR GPL-2.0)')&.id).to eql('(BSD-3-Clause OR GPL-2.0)') } + specify { expect(subject.license_for('(MIT AND CC-BY-3.0)')&.id).to eql('(MIT AND CC-BY-3.0)') } + specify { expect(subject.license_for('(MIT AND Zlib)')&.id).to eql('(MIT AND Zlib)') } + specify { expect(subject.license_for('(MIT OR Apache-2.0)')&.id).to eql('(MIT OR Apache-2.0)') } + specify { expect(subject.license_for('(MIT OR CC0-1.0)')&.id).to eql('(MIT OR CC0-1.0)') } + specify { expect(subject.license_for('(MIT OR GPL-3.0)')&.id).to eql('(MIT OR GPL-3.0)') } + specify { expect(subject.license_for('(WTFPL OR MIT)')&.id).to eql('(WTFPL OR MIT)') } specify { expect(subject.license_for('Apache 2.0')&.id).to eql('Apache-2.0') } - specify { expect(subject.license_for('BSD-3-Clause OR MIT')&.id).to eql('BSD-3-Clause') } + specify { expect(subject.license_for('BSD-3-Clause OR MIT')&.id).to eql('(BSD-3-Clause OR MIT)') } specify { expect(subject.license_for('BSD-like')&.id).to eql('Nonstandard') } specify { expect(subject.license_for('Common Public License Version 1.0')&.id).to eql('CPL-1.0') } - specify { expect(subject.license_for('MIT or GPLv3')&.id).to eql('MIT') } + specify { expect(subject.license_for('MIT or GPLv3')&.id).to eql('(MIT OR Nonstandard)') } pending { expect(subject.license_for('MIT/X11')&.id).to eql('X11') } end end diff --git a/spec/unit/spdx/composite_license_spec.rb b/spec/unit/spdx/composite_license_spec.rb new file mode 100644 index 0000000..84dca0c --- /dev/null +++ b/spec/unit/spdx/composite_license_spec.rb @@ -0,0 +1,47 @@ +RSpec.describe Spandx::Spdx::CompositeLicense do + context ".from_expression" do + subject { described_class.from_expression(expression, catalogue) } + let(:catalogue) { ::Spandx::Spdx::Catalogue.from_file(fixture_file('spdx/json/licenses.json')) } + + before do + puts subject.instance_variable_get(:@tree).inspect + end + + context 'when parsing a simple binary expression' do + let(:expression) { '(0BSD OR MIT)' } + + specify { expect(subject.id).to eql(expression) } + specify { expect(subject.name).to eql("#{catalogue['0BSD'].name} OR #{catalogue['MIT'].name}") } + specify { expect(subject).to be_kind_of(::Spandx::Spdx::License) } + end + + context 'when parsing an expression with a valid and an invalid license id' do + let(:expression) { 'MIT or GPLv3' } + + specify { expect(subject.id).to eql("(MIT OR Nonstandard)") } + specify { expect(subject.name).to eql("#{catalogue['MIT'].name} OR GPLv3") } + specify { expect(subject).to be_kind_of(::Spandx::Spdx::License) } + end + + context 'when parsing a license name' do + let(:expression) { 'Common Public License Version 1.0' } + + specify { expect(subject).to be_nil } + end + + context 'when parsing an invalid expression' do + let(:expression) { 'BSD-like' } + + specify { expect(subject.id).to eql('Nonstandard') } + specify { expect(subject.name).to eql('BSD-like') } + end + + context 'when parsing a binary expression with an id that is similar to another' do + let(:expression) { '(MIT OR CC0-1.0)' } + + specify { expect(subject.id).to eql(expression) } + specify { expect(subject.name).to eql("#{catalogue['MIT'].name} OR #{catalogue['CC0-1.0'].name}") } + specify { expect(subject).to be_kind_of(::Spandx::Spdx::License) } + end + end +end |
