lib/license_finder/decisions.rb
# frozen_string_literal: true
require 'open-uri'
require 'license_finder/license'
require 'license_finder/manual_licenses'
module LicenseFinder
class Decisions
######
# READ
######
attr_reader :packages, :permitted, :restricted, :ignored, :ignored_groups, :project_name, :inherited_decisions
def licenses_of(name, version = nil)
@manual_licenses.licenses_of(name, version)
end
def homepage_of(name)
@homepages[name]
end
def approval_of(name, version = nil)
if !@approvals.key?(name)
nil
elsif !version.nil?
@approvals[name] if @approvals[name][:safe_versions].empty? || @approvals[name][:safe_versions].include?(version)
elsif @approvals[name][:safe_versions].empty?
@approvals[name]
end
end
def approved?(name, version = nil)
if !@approvals.key?(name)
nil
elsif !version.nil?
@approvals.key?(name) && @approvals[name][:safe_versions].empty? || @approvals[name][:safe_versions].include?(version)
else
@approvals.key?(name)
end
end
def permitted?(lic)
if @permitted.include?(lic)
true
elsif lic.is_a?(OrLicense)
lic.sub_licenses.any? { |sub_lic| @permitted.include?(sub_lic) }
elsif lic.is_a?(AndLicense)
lic.sub_licenses.all? { |sub_lic| @permitted.include?(sub_lic) }
else
false
end
end
def restricted?(lic)
@restricted.include?(lic)
end
def ignored?(name)
@ignored.include?(name)
end
def ignored_group?(name)
@ignored_groups.include?(name)
end
#######
# WRITE
#######
TXN = Struct.new(:who, :why, :safe_when, :safe_versions) do
def self.from_hash(txn, versions)
new(txn[:who], txn[:why], txn[:when], versions || [])
end
end
def initialize
@decisions = []
@packages = Set.new
@manual_licenses = ManualLicenses.new
@homepages = {}
@approvals = {}
@permitted = Set.new
@restricted = Set.new
@ignored = Set.new
@ignored_groups = Set.new
@inherited_decisions = Set.new
end
def add_package(name, version, txn = {})
add_decision [:add_package, name, version, txn]
@packages << ManualPackage.new(name, version)
self
end
def remove_package(name, txn = {})
add_decision [:remove_package, name, txn]
@packages.delete(ManualPackage.new(name))
self
end
def license(name, lic, txn = {})
add_decision [:license, name, lic, txn]
versions = txn[:versions]
if versions.nil? || versions.empty?
@manual_licenses.assign_to_all_versions(name, lic)
else
@manual_licenses.assign_to_specific_versions(name, lic, versions)
end
self
end
def unlicense(name, lic, txn = {})
add_decision [:unlicense, name, lic, txn]
versions = txn[:versions]
if versions.nil? || versions.empty?
@manual_licenses.unassign_from_all_versions(name, lic)
else
@manual_licenses.unassign_from_specific_versions(name, lic, versions)
end
self
end
def homepage(name, homepage, txn = {})
add_decision [:homepage, name, homepage, txn]
@homepages[name] = homepage
self
end
def approve(name, txn = {})
add_decision [:approve, name, txn]
versions = []
versions = @approvals[name][:safe_versions] if @approvals.key?(name)
@approvals[name] = TXN.from_hash(txn, versions)
@approvals[name][:safe_versions].concat(txn[:versions]) unless txn[:versions].nil?
self
end
def unapprove(name, txn = {})
add_decision [:unapprove, name, txn]
@approvals.delete(name)
self
end
def permit(lic, txn = {})
add_decision [:permit, lic, txn]
@permitted << License.find_by_name(lic)
self
end
def unpermit(lic, txn = {})
add_decision [:unpermit, lic, txn]
@permitted.delete(License.find_by_name(lic))
self
end
def restrict(lic, txn = {})
add_decision [:restrict, lic, txn]
@restricted << License.find_by_name(lic)
self
end
def unrestrict(lic, txn = {})
add_decision [:unrestrict, lic, txn]
@restricted.delete(License.find_by_name(lic))
self
end
def ignore(name, txn = {})
add_decision [:ignore, name, txn]
@ignored << name
self
end
def heed(name, txn = {})
add_decision [:heed, name, txn]
@ignored.delete(name)
self
end
def ignore_group(name, txn = {})
add_decision [:ignore_group, name, txn]
@ignored_groups << name
self
end
def heed_group(name, txn = {})
add_decision [:heed_group, name, txn]
@ignored_groups.delete(name)
self
end
def name_project(name, txn = {})
add_decision [:name_project, name, txn]
@project_name = name
self
end
def unname_project(txn = {})
add_decision [:unname_project, txn]
@project_name = nil
self
end
def inherit_from(filepath_info)
decisions =
case filepath_info
when Hash
resolve_inheritance(filepath_info)
when %r{^https?://}
open_uri(filepath_info).read
else
Pathname(filepath_info).read
end
add_decision [:inherit_from, filepath_info]
@inherited_decisions << filepath_info
restore_inheritance(decisions)
end
def resolve_inheritance(filepath_info)
if (gem_name = filepath_info['gem'])
Pathname(gem_config_path(gem_name, filepath_info['path'])).read
else
open_uri(filepath_info['url'], filepath_info['authorization']).read
end
end
def gem_config_path(gem_name, relative_config_path)
spec = Gem::Specification.find_by_name(gem_name)
File.join(spec.gem_dir, relative_config_path)
rescue Gem::LoadError => e
raise Gem::LoadError,
"Unable to find gem #{gem_name}; is the gem installed? #{e}"
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)
previous_value = @inherited
@inherited = true
self.class.restore(decisions, self)
@inherited = previous_value
self
end
def open_uri(uri, auth = nil)
header = {}
auth_header = resolve_authorization(auth)
header['Authorization'] = auth_header if auth_header
# ruby < 2.5.0 URI.open is private
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5.0')
open(uri, header)
else
URI.open(uri, header)
end
end
def resolve_authorization(auth)
return unless auth
token_env = auth.match(/\$(\S.*)/)
return auth unless token_env
token = ENV[token_env[1]]
auth.sub(token_env[0], token)
end
#########
# PERSIST
#########
def self.fetch_saved(file)
restore(read!(file))
end
def save!(file)
write!(persist, file)
end
def self.restore(persisted, result = new)
return result unless persisted
# From https://makandracards.com/makandra/465149-ruby-the-yaml-safe_load-method-hides-some-pitfalls
actions = if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1')
YAML.safe_load(persisted, permitted_classes: [Symbol, Time], aliases: true)
else
YAML.safe_load(persisted, [Symbol, Time], [], true)
end
list_of_actions = (actions || []).map(&:first)
if (list_of_actions & %i[whitelist blacklist]).any?
raise 'The decisions file seems to have whitelist/blacklist keys which are deprecated. Please replace them with permit/restrict respectively and try again! More info - https://github.com/pivotal/LicenseFinder/commit/a40b22fda11b3a0efbb3c0a021381534bc998dd9'
end
(actions || []).each do |action, *args|
result.send(action, *args)
end
result
end
def persist
YAML.dump(@decisions)
end
def self.read!(file)
file.read if file.exist?
end
def write!(value, file)
file.dirname.mkpath
file.open('w+') do |f|
f.print value
end
end
end
end