Skip to content

Commit

Permalink
Merge pull request #735 from forelabs/feat/decision-inheritance
Browse files Browse the repository at this point in the history
Feature: Decision inheritance
  • Loading branch information
xtreme-shane-lattanzio authored May 20, 2020
2 parents 75b2f45 + c3afcc5 commit 3453feb
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 20 deletions.
1 change: 1 addition & 0 deletions lib/license_finder/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module CLI
require 'license_finder/cli/patched_thor'
require 'license_finder/cli/base'
require 'license_finder/cli/makes_decisions'
require 'license_finder/cli/inherited_decisions'
require 'license_finder/cli/permitted_licenses'
require 'license_finder/cli/restricted_licenses'
require 'license_finder/cli/dependencies'
Expand Down
32 changes: 32 additions & 0 deletions lib/license_finder/cli/inherited_decisions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

module LicenseFinder
module CLI
class InheritedDecisions < Base
extend Subcommand
include MakesDecisions

desc 'list', 'List all the inherited decision files'
def list
say 'Inherited Decision Files:', :blue
say_each(decisions.inherited_decisions)
end

auditable
desc 'add DECISION_FILE...', 'Add one or more decision files to the inherited decisions'
def add(*decision_files)
assert_some decision_files
modifying { decision_files.each { |filepath| decisions.inherit_from(filepath) } }
say "Added #{decision_files.join(', ')} to the inherited decisions"
end

auditable
desc 'remove DECISION_FILE...', 'Remove one or more decision files from the inherited decisions'
def remove(*decision_files)
assert_some decision_files
modifying { decision_files.each { |filepath| decisions.remove_inheritance(filepath) } }
say "Removed #{decision_files.join(', ')} from the inherited decisions"
end
end
end
end
1 change: 1 addition & 0 deletions lib/license_finder/cli/main.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ def diff(file1, file2)
subcommand 'permitted_licenses', PermittedLicenses, 'Automatically approve any dependency that has a permitted license'
subcommand 'restricted_licenses', RestrictedLicenses, 'Forbid approval of any dependency whose licenses are all restricted'
subcommand 'project_name', ProjectName, 'Set the project name, for display in reports'
subcommand 'inherited_decisions', InheritedDecisions, 'Add or remove decision files you want to inherit from'

private

Expand Down
83 changes: 63 additions & 20 deletions lib/license_finder/decisions.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# frozen_string_literal: true

require 'open-uri'

module LicenseFinder
class Decisions
######
# READ
######

attr_reader :packages, :permitted, :restricted, :ignored, :ignored_groups, :project_name
attr_reader :packages, :permitted, :restricted, :ignored, :ignored_groups, :project_name, :inherited_decisions

def licenses_of(name)
@licenses[name]
Expand Down Expand Up @@ -72,40 +74,41 @@ def initialize
@restricted = Set.new
@ignored = Set.new
@ignored_groups = Set.new
@inherited_decisions = Set.new
end

def add_package(name, version, txn = {})
@decisions << [:add_package, name, version, txn]
add_decision [:add_package, name, version, txn]
@packages << ManualPackage.new(name, version)
self
end

def remove_package(name, txn = {})
@decisions << [:remove_package, name, txn]
add_decision [:remove_package, name, txn]
@packages.delete(ManualPackage.new(name))
self
end

def license(name, lic, txn = {})
@decisions << [:license, name, lic, txn]
add_decision [:license, name, lic, txn]
@licenses[name] << License.find_by_name(lic)
self
end

def unlicense(name, lic, txn = {})
@decisions << [:unlicense, name, lic, txn]
add_decision [:unlicense, name, lic, txn]
@licenses[name].delete(License.find_by_name(lic))
self
end

def homepage(name, homepage, txn = {})
@decisions << [:homepage, name, homepage, txn]
add_decision [:homepage, name, homepage, txn]
@homepages[name] = homepage
self
end

def approve(name, txn = {})
@decisions << [:approve, name, txn]
add_decision [:approve, name, txn]

versions = []
versions = @approvals[name][:safe_versions] if @approvals.key?(name)
Expand All @@ -115,71 +118,112 @@ def approve(name, txn = {})
end

def unapprove(name, txn = {})
@decisions << [:unapprove, name, txn]
add_decision [:unapprove, name, txn]
@approvals.delete(name)
self
end

def permit(lic, txn = {})
@decisions << [:permit, lic, txn]
add_decision [:permit, lic, txn]
@permitted << License.find_by_name(lic)
self
end

def unpermit(lic, txn = {})
@decisions << [:unpermit, lic, txn]
add_decision [:unpermit, lic, txn]
@permitted.delete(License.find_by_name(lic))
self
end

def restrict(lic, txn = {})
@decisions << [:restrict, lic, txn]
add_decision [:restrict, lic, txn]
@restricted << License.find_by_name(lic)
self
end

def unrestrict(lic, txn = {})
@decisions << [:unrestrict, lic, txn]
add_decision [:unrestrict, lic, txn]
@restricted.delete(License.find_by_name(lic))
self
end

def ignore(name, txn = {})
@decisions << [:ignore, name, txn]
add_decision [:ignore, name, txn]
@ignored << name
self
end

def heed(name, txn = {})
@decisions << [:heed, name, txn]
add_decision [:heed, name, txn]
@ignored.delete(name)
self
end

def ignore_group(name, txn = {})
@decisions << [:ignore_group, name, txn]
add_decision [:ignore_group, name, txn]
@ignored_groups << name
self
end

def heed_group(name, txn = {})
@decisions << [:heed_group, name, txn]
add_decision [:heed_group, name, txn]
@ignored_groups.delete(name)
self
end

def name_project(name, txn = {})
@decisions << [:name_project, name, txn]
add_decision [:name_project, name, txn]
@project_name = name
self
end

def unname_project(txn = {})
@decisions << [:unname_project, txn]
add_decision [:unname_project, txn]
@project_name = nil
self
end

def inherit_from(filepath)
decisions =
if filepath =~ %r{^https?://}
open_uri(filepath).read
else
Pathname(filepath).read
end

add_decision [:inherit_from, filepath]
@inherited_decisions << filepath
restore_inheritance(decisions)
end

def remove_inheritance(filepath)
@decisions -= [[:inherit_from, filepath]]
@inherited_decisions.delete(filepath)
self
end

def add_decision(decision)
@decisions << decision unless @inherited
end

def restore_inheritance(decisions)
@inherited = true
self.class.restore(decisions, self)
@inherited = false
self
end

def open_uri(uri)
# ruby < 2.5.0 URI.open is private
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5.0')
# rubocop:disable Security/Open
open(uri)
# rubocop:enable Security/Open
else
URI.open(uri)
end
end

#########
# PERSIST
#########
Expand All @@ -192,8 +236,7 @@ def save!(file)
write!(persist, file)
end

def self.restore(persisted)
result = new
def self.restore(persisted, result = new)
return result unless persisted

actions = YAML.load(persisted)
Expand Down
42 changes: 42 additions & 0 deletions spec/lib/license_finder/decisions_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,33 @@ module LicenseFinder
end
end

describe '.inherit_from' do
let(:yml) { YAML.dump([[:permit, 'MIT']]) }

it 'inheritates rules from local decision file' do
allow_any_instance_of(Pathname).to receive(:read).and_return(yml)
decisions = subject.inherit_from('./config/inherit.yml')
expect(decisions).to be_permitted(License.find_by_name('MIT'))
end

it 'inheritates rules from remote decision file' do
stub_request(:get, 'https://example.com/config/inherit.yml').to_return(status: 200, body: yml, headers: {})
decisions = subject.inherit_from('https://example.com/config/inherit.yml')
expect(decisions).to be_permitted(License.find_by_name('MIT'))
end
end

describe '.remove_inheritance' do
it 'reports inheritanced decisions' do
allow_any_instance_of(Pathname).to receive(:read).and_return('---')
decisions = subject.inherit_from('./config/inherit.yml')
expect(decisions.inherited_decisions).to include('./config/inherit.yml')

decisions = subject.remove_inheritance('./config/inherit.yml')
expect(decisions.inherited_decisions).to be_empty
end
end

describe 'persistence' do
def roundtrip(decisions)
described_class.restore(decisions.persist)
Expand Down Expand Up @@ -446,6 +473,21 @@ def roundtrip(decisions)
expect(decisions.project_name).to be_nil
end

it 'can restore inherited decisions' do
allow_any_instance_of(Pathname).to receive(:read).and_return(YAML.dump([[:permit, 'MIT']]))
decisions = roundtrip(
subject
.inherit_from('./config/inherit.yml')
)
expect(decisions.inherited_decisions).to include('./config/inherit.yml')
end

it 'does not store decisions from inheritance' do
allow_any_instance_of(Pathname).to receive(:read).and_return(YAML.dump([[:permit, 'MIT']]))
decisions = subject.inherit_from('./config/inherit.yml')
expect(decisions.persist).to eql(YAML.dump([[:inherit_from, './config/inherit.yml']]))
end

it 'ignores empty or missing persisted decisions' do
described_class.restore('')
described_class.restore(nil)
Expand Down

0 comments on commit 3453feb

Please sign in to comment.