lib/licensed/configuration.rb in licensed-0.11.1 vs lib/licensed/configuration.rb in licensed-1.0.0

- old
+ new

@@ -1,79 +1,181 @@ +# frozen_string_literal: true require "pathname" module Licensed - class Configuration < Hash - attr_accessor :ui + class AppConfiguration < Hash + DEFAULT_CACHE_PATH = ".licenses".freeze + DEFAULT_CONFIG_FILES = [ + ".licensed.yml".freeze, + ".licensed.yaml".freeze, + ".licensed.json".freeze + ].freeze - def initialize(options = {}) + def initialize(options = {}, inherited_options = {}) super() - self.path = options["license-dir"] if options["license-dir"] - update config_path.exist? ? YAML.load_file(config_path) : {} + # update order: + # 1. anything inherited from root config + # 2. app defaults + # 3. explicitly configured app settings + update(inherited_options) + update(defaults_for(options, inherited_options)) + update(options) + self["sources"] ||= {} self["reviewed"] ||= {} self["ignored"] ||= {} - self["whitelist"] ||= [] + self["allowed"] ||= [] - @ui = Licensed::UI::Shell.new + verify_arg "source_path" + verify_arg "cache_path" end - def path - @path ||= Pathname.new("vendor/licenses") + # Returns the path to the app cache directory as a Pathname + def cache_path + Licensed::Git.repository_root.join(self["cache_path"]) end - def path=(value) - @path = Pathname.new(value) + # Returns the path to the app source directory as a Pathname + def source_path + Licensed::Git.repository_root.join(self["source_path"]) end - def config_path - path.join("config.yml") - end - def pwd - Pathname.new(Dir.pwd) + Pathname.pwd end + # Returns an array of enabled app sources def sources @sources ||= [ Source::Bundler.new(self), Source::Bower.new(self), Source::Cabal.new(self), Source::Go.new(self), Source::Manifest.new(self), - Source::NPM.new(self), - Source::Stack.new(self) + Source::NPM.new(self) ].select(&:enabled?) end + # Returns whether a source type is enabled def enabled?(source_type) self["sources"].fetch(source_type, true) end - # Is the given dependency approved? + # Is the given dependency reviewed? def reviewed?(dependency) Array(self["reviewed"][dependency["type"]]).include?(dependency["name"]) end # Is the given dependency ignored? def ignored?(dependency) Array(self["ignored"][dependency["type"]]).include?(dependency["name"]) end - # Is the license of the dependency whitelisted? - def whitelisted?(dependency) - Array(self["whitelist"]).include?(dependency["license"]) + # Is the license of the dependency allowed? + def allowed?(dependency) + Array(self["allowed"]).include?(dependency["license"]) end + # Ignore a dependency def ignore(dependency) (self["ignored"][dependency["type"]] ||= []) << dependency["name"] end + # Set a dependency as reviewed def review(dependency) (self["reviewed"][dependency["type"]] ||= []) << dependency["name"] end - def whitelist(license) - self["whitelist"] << license + # Set a license as explicitly allowed + def allow(license) + self["allowed"] << license + end + + def defaults_for(options, inherited_options) + name = options["name"] || File.basename(options["source_path"]) + cache_path = inherited_options["cache_path"] || DEFAULT_CACHE_PATH + { + "name" => name, + "cache_path" => File.join(cache_path, name) + } + end + + def verify_arg(property) + return if self[property] + raise Licensed::Configuration::LoadError, + "App #{self["name"]} is missing required property #{property}" + end + end + + class Configuration < AppConfiguration + class LoadError < StandardError; end + + attr_accessor :ui + + # Loads and returns a Licensed::Configuration object from the given path. + # The path can be relative or absolute, and can point at a file or directory. + # If the path given is a directory, the directory will be searched for a + # `config.yml` file. + def self.load_from(path) + config_path = Pathname.pwd.join(path) + config_path = find_config(config_path) if config_path.directory? + Configuration.new(parse_config(config_path)) + end + + def initialize(options = {}) + @ui = Licensed::UI::Shell.new + + apps = options.delete("apps") || [] + super(default_options.merge(options)) + + self["apps"] = apps.map { |app| AppConfiguration.new(app, options) } + end + + # Returns an array of the applications for this licensed configuration. + # If the configuration did not explicitly configure any applications, + # return self as an application configuration. + def apps + return [self] if self["apps"].empty? + self["apps"] + end + + private + + # Find a default configuration file in the given directory. + # File preference is given by the order of elements in DEFAULT_CONFIG_FILES + # + # Raises Licensed::Configuration::LoadError if a file isn't found + def self.find_config(directory) + config_file = DEFAULT_CONFIG_FILES.map { |file| directory.join(file) } + .find { |file| file.exist? } + + config_file || raise(LoadError, "Licensed configuration not found in #{directory}") + end + + # Parses the configuration given at `config_path` and returns the values + # as a Hash + # + # Raises Licensed::Configuration::LoadError if the file type isn't known + def self.parse_config(config_path) + return {} unless config_path.file? + + extension = config_path.extname.downcase.delete "." + case extension + when "json" + JSON.parse(File.read(config_path)) + when "yml", "yaml" + YAML.load_file(config_path) + else + raise LoadError, "Unknown file type #{extension} for #{config_path}" + end + end + + def default_options + # manually set a cache path without additional name + { + "source_path" => Dir.pwd, + "cache_path" => DEFAULT_CACHE_PATH + } end end end