From 129bf4caa21f10f02121f0c51bb5b06f959b83de Mon Sep 17 00:00:00 2001 From: janstenpickle Date: Fri, 25 Sep 2015 15:43:52 +0100 Subject: [PATCH] Shanty plugin-config - add methods to resolve plugin name in the Plugin class - add the name of the plugin as the default plugin tag - add a command line task to display the enabled plugin names - add a global command line option to imput global configuration - refactor env so configuration is in a separate class - make the config class return OpenStructs instead of a hash --- lib/shanty/cli.rb | 17 +++++++ lib/shanty/config.rb | 51 +++++++++++++++++++ lib/shanty/env.rb | 11 +++-- lib/shanty/plugin.rb | 22 ++++++++- lib/shanty/plugins/bundler_plugin.rb | 1 - lib/shanty/plugins/cucumber_plugin.rb | 1 - lib/shanty/plugins/rspec_plugin.rb | 1 - lib/shanty/plugins/rubocop_plugin.rb | 1 - lib/shanty/plugins/rubygem_plugin.rb | 1 - lib/shanty/plugins/shantyfile_plugin.rb | 1 - lib/shanty/task_sets/basic_task_set.rb | 6 +++ shanty.gemspec | 1 + spec/lib/shanty/cli_spec.rb | 26 ++++++++++ spec/lib/shanty/config_spec.rb | 65 +++++++++++++++++++++++++ spec/lib/shanty/env_spec.rb | 17 +++---- translations/en.yml | 2 + 16 files changed, 203 insertions(+), 21 deletions(-) create mode 100644 lib/shanty/config.rb create mode 100644 spec/lib/shanty/config_spec.rb diff --git a/lib/shanty/cli.rb b/lib/shanty/cli.rb index 72f6298..c4ace15 100644 --- a/lib/shanty/cli.rb +++ b/lib/shanty/cli.rb @@ -2,6 +2,9 @@ require 'i18n' require 'shanty/info' require 'shanty/task_set' +require 'shanty/env' +require 'shenanigans/hash/to_ostruct' +require 'deep_merge' module Shanty # Public: Handle the CLI interface between the user and the registered tasks @@ -11,6 +14,8 @@ class Cli attr_reader :task_sets + CONFIG_FORMAT = '[plugin]:[key] [value]' + def initialize(task_sets) @task_sets = task_sets end @@ -28,11 +33,23 @@ def run program(:description, Info::DESCRIPTION) setup_tasks + global_config run! end private + def global_config + global_option('-c', '--config [CONFIG]', "Add config via command line in the format #{CONFIG_FORMAT}") do |config| + match = config.match(/(?\S+):(?\S+)\s+(?\S+)/) + if match + Env.config.merge!(match[:plugin] => { match[:key] => match[:value] }) + else + abort("Invalid config format \"#{config}\" should be #{CONFIG_FORMAT}") + end + end + end + def setup_tasks tasks.each do |name, task| setup_task(name, task) diff --git a/lib/shanty/config.rb b/lib/shanty/config.rb new file mode 100644 index 0000000..abade94 --- /dev/null +++ b/lib/shanty/config.rb @@ -0,0 +1,51 @@ +require 'shenanigans/hash/to_ostruct' +require 'deep_merge' + +module Shanty + # Public: Configuration class for shanty + class Config + CONFIG_FILE = '.shanty.yml' + + def initialize(root, environment) + @root = root + @environment = environment + end + + def merge!(new_config) + @config = config_hash.clone.deep_merge(new_config) + end + + def [](key) + to_ostruct[key] + end + + def method_missing(m) + os_resp = to_ostruct.send(m) + return os_resp unless os_resp.nil? + OpenStruct.new + end + + def respond_to?(method_sym, include_private = false) + to_ostruct.send.respond_to?(method_sym, include_private) + end + + def to_ostruct + config_hash.to_ostruct + end + + private + + def config_hash + return @config unless @config.nil? + return @config = {} unless File.exist?(config_path) + config = YAML.load_file(config_path) || {} + @config = config[@environment] || {} + rescue RuntimeError + {} + end + + def config_path + "#{@root}/#{CONFIG_FILE}" + end + end +end diff --git a/lib/shanty/env.rb b/lib/shanty/env.rb index d44759d..ec050c5 100644 --- a/lib/shanty/env.rb +++ b/lib/shanty/env.rb @@ -2,7 +2,9 @@ require 'logger' require 'pathname' require 'yaml' +require 'shenanigans/hash/to_ostruct' +require 'shanty/config' require 'shanty/project_tree' module Shanty @@ -24,7 +26,7 @@ def clear! def require! Dir.chdir(root) do - (config['require'] || {}).each do |path| + (config.require || []).each do |path| requires_in_path(path).each { |f| require File.join(root, f) } end end @@ -58,10 +60,9 @@ def project_tree end def config - return @config unless @config.nil? - return @config = {} unless File.exist?(config_path) - config = YAML.load_file(config_path) || {} - @config = config[environment] || {} + @config ||= Config.new(root, environment) + rescue RuntimeError + @config ||= Config.new(nil, environment) end private diff --git a/lib/shanty/plugin.rb b/lib/shanty/plugin.rb index 36c5d93..20bec05 100644 --- a/lib/shanty/plugin.rb +++ b/lib/shanty/plugin.rb @@ -13,6 +13,10 @@ def self.inherited(plugin_class) @plugins << plugin_class.new end + def self.plugins + (@plugins || []).map(&:name) + end + def self.all_projects (@plugins || []).flat_map(&:projects).uniq end @@ -22,10 +26,18 @@ def self.all_with_graph(graph) end def self.tags(*args) - @tags ||= [] + @tags ||= [name] @tags.concat(args) end + def self.option(option, default: nil) + config[name][option] = default if config[name][option].nil? + end + + def self.options + config[name] + end + def self.projects(*globs_or_syms) @project_matchers ||= [] @project_matchers.concat(globs_or_syms) @@ -36,6 +48,14 @@ def self.with_graph(&block) @with_graph_callbacks << block end + def self.name + to_s.split('::').last.downcase.gsub('plugin', '').to_sym + end + + def name + self.class.name + end + def projects project_matchers = self.class.instance_variable_get(:@project_matchers) return [] if project_matchers.nil? || project_matchers.empty? diff --git a/lib/shanty/plugins/bundler_plugin.rb b/lib/shanty/plugins/bundler_plugin.rb index 6499941..20c27bb 100644 --- a/lib/shanty/plugins/bundler_plugin.rb +++ b/lib/shanty/plugins/bundler_plugin.rb @@ -4,7 +4,6 @@ module Shanty # Public: Bundler plugin for building ruby projects. class BundlerPlugin < Plugin - tags :bundler projects '**/Gemfile' subscribe :build, :bundle_install diff --git a/lib/shanty/plugins/cucumber_plugin.rb b/lib/shanty/plugins/cucumber_plugin.rb index 72a2d65..96671b5 100644 --- a/lib/shanty/plugins/cucumber_plugin.rb +++ b/lib/shanty/plugins/cucumber_plugin.rb @@ -3,7 +3,6 @@ module Shanty # Public: Cucumber plugin for testing ruby projects. class CucumberPlugin < Plugin - tags :cucumber subscribe :test, :cucumber # By default, we'll detect Cucumber in a project if has a dependency on it in a Gemfile or *.gemspec file. If you # don't use these files, you'll need to import the plugin manually using a Shantyfile as we can't tell if RSpec is diff --git a/lib/shanty/plugins/rspec_plugin.rb b/lib/shanty/plugins/rspec_plugin.rb index 6fd6366..d55408a 100644 --- a/lib/shanty/plugins/rspec_plugin.rb +++ b/lib/shanty/plugins/rspec_plugin.rb @@ -3,7 +3,6 @@ module Shanty # Public: Rspec plugin for testing ruby projects. class RspecPlugin < Plugin - tags :rspec subscribe :test, :rspec # By default, we'll detect RSpec in a project if has a dependency on it in a Gemfile or *.gemspec file. If you don't # use these files, you'll need to import the plugin manually using a Shantyfile as we can't tell if RSpec is being diff --git a/lib/shanty/plugins/rubocop_plugin.rb b/lib/shanty/plugins/rubocop_plugin.rb index 1c7241c..5a501e6 100644 --- a/lib/shanty/plugins/rubocop_plugin.rb +++ b/lib/shanty/plugins/rubocop_plugin.rb @@ -3,7 +3,6 @@ module Shanty # Public: Rubocop plugin for style checking ruby projects. class RubocopPlugin < Plugin - tags :rubocop projects '**/.rubocop.yml' subscribe :test, :rubocop diff --git a/lib/shanty/plugins/rubygem_plugin.rb b/lib/shanty/plugins/rubygem_plugin.rb index 44519b9..434deeb 100644 --- a/lib/shanty/plugins/rubygem_plugin.rb +++ b/lib/shanty/plugins/rubygem_plugin.rb @@ -3,7 +3,6 @@ module Shanty # Public: Rubygem plugin for buildin gems. class RubygemPlugin < Plugin - tags :rubygem projects '**/*.gemspec' subscribe :build, :build_gem diff --git a/lib/shanty/plugins/shantyfile_plugin.rb b/lib/shanty/plugins/shantyfile_plugin.rb index e83128c..f5cb226 100644 --- a/lib/shanty/plugins/shantyfile_plugin.rb +++ b/lib/shanty/plugins/shantyfile_plugin.rb @@ -3,7 +3,6 @@ module Shanty # Public: Plugin for finding all directories marked with a Shantyfile. class ShantyfilePlugin < Plugin - tags :shantyfile projects :shantyfile_projects def shantyfile_projects diff --git a/lib/shanty/task_sets/basic_task_set.rb b/lib/shanty/task_sets/basic_task_set.rb index 02f620e..f18a69a 100644 --- a/lib/shanty/task_sets/basic_task_set.rb +++ b/lib/shanty/task_sets/basic_task_set.rb @@ -1,6 +1,7 @@ require 'fileutils' require 'i18n' require 'shanty/task_set' +require 'shanty/plugin' module Shanty # Public: A set of basic tasks that can be applied to all projects and that @@ -11,6 +12,11 @@ def init FileUtils.touch(File.join(Dir.pwd, '.shanty.yml')) end + desc 'plugins', 'tasks.plugins.desc' + def plugins(*) + puts Plugin.plugins + end + desc 'projects [--tags TAG,TAG,...]', 'tasks.projects.desc' option :tags, type: :array, desc: 'tasks.common.options.tags' def projects(options) diff --git a/shanty.gemspec b/shanty.gemspec index fb5bc1d..9417370 100644 --- a/shanty.gemspec +++ b/shanty.gemspec @@ -29,6 +29,7 @@ Gem::Specification.new do |gem| gem.add_dependency 'gitignore_rb', '~>0.2.2' gem.add_dependency 'i18n', '~>0.7' gem.add_dependency 'shenanigans', '~>1.0' + gem.add_dependency 'deep_merge', '~>1.0' gem.add_development_dependency 'coveralls', '~>0.8' gem.add_development_dependency 'cucumber', '~>2.1' diff --git a/spec/lib/shanty/cli_spec.rb b/spec/lib/shanty/cli_spec.rb index bb0ceed..99cc880 100644 --- a/spec/lib/shanty/cli_spec.rb +++ b/spec/lib/shanty/cli_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' require 'shanty/cli' require 'shanty/info' +require 'shanty/env' +require 'shenanigans/hash/to_ostruct' require_fixture 'test_task_set' # All classes referenced belong to the shanty project @@ -112,6 +114,30 @@ module Shanty subject.run end + + it('fails to run a command with config options if config is in incorrect format') do + expect(subject).to receive(:abort).with('Invalid config format "nic" should be [plugin]:[key] [value]') + + ARGV.concat(%w(-c nic foo)) + + subject.run + end + + it('runs a command with a config option') do + ARGV.concat(['-c nic:kim cage']) + + subject.run + + expect(Env.config.nic).to eql({ kim: 'cage' }.to_ostruct) + end + + it('runs a command with multiple config options') do + ARGV.concat(['-c nic:kim cage', '-c nic:copolla cage']) + + subject.run + + expect(Env.config.nic).to eql({ kim: 'cage', copolla: 'cage' }.to_ostruct) + end end end end diff --git a/spec/lib/shanty/config_spec.rb b/spec/lib/shanty/config_spec.rb new file mode 100644 index 0000000..cfe3b93 --- /dev/null +++ b/spec/lib/shanty/config_spec.rb @@ -0,0 +1,65 @@ +require 'shanty/config' +require 'shenanigans/hash/to_ostruct' +require 'deep_merge' + +# All classes referenced belong to the shanty project +module Shanty + RSpec.describe(Config) do + subject { Config.new('nic', 'test') } + + let(:subconfig) { { 'kim' => 'cage' } } + let(:config) { { 'nic' => subconfig } } + let(:env_config) { { 'test' => config } } + + before do + allow(File).to receive(:exist?) { true } + allow(YAML).to receive(:load_file) { env_config } + end + + describe(Config) do + it('can respond like an OpenStruct') do + expect(subject.nic).to eql(subconfig.to_ostruct) + end + + it('handles no config file existing') do + allow(File).to receive(:exist?) { false } + expect(subject.nic).to eql(OpenStruct.new) + end + + it('handles no data in the config file') do + allow(YAML).to receive(:load_file) { false } + expect(subject.nic).to eql(OpenStruct.new) + end + + it('loads config from a file') do + allow(File).to receive(:exist?).and_call_original + allow(YAML).to receive(:load_file).and_call_original + Dir.mktmpdir('shanty-tests') do |tmp_path| + Dir.chdir(tmp_path) { FileUtils.touch('.shanty.yml') } + expect(Config.new(tmp_path, 'test').nic).to eql(OpenStruct.new) + end + end + + it('returns nothing if .shanty.yml does not exist') do + allow(File).to receive(:exist?).and_call_original + allow(YAML).to receive(:load_file).and_call_original + expect(subject.nic).to eql(OpenStruct.new) + end + end + + describe('#[]') do + it('can respond like a hash') do + expect(subject['nic']).to eql(subconfig.to_ostruct) + end + end + + describe('merge!') do + it('can merge in new config') do + added_config = { 'copolla' => 'cage' } + subject.merge!('nic' => added_config) + + expect(subject['nic']).to eql(subconfig.deep_merge(added_config).to_ostruct) + end + end + end +end diff --git a/spec/lib/shanty/env_spec.rb b/spec/lib/shanty/env_spec.rb index a232bb5..7407ab0 100644 --- a/spec/lib/shanty/env_spec.rb +++ b/spec/lib/shanty/env_spec.rb @@ -3,6 +3,8 @@ require 'i18n' require 'tmpdir' require 'shanty/env' +require 'shenanigans/hash/to_ostruct' +require 'deep_merge' # All classes referenced belong to the shanty project module Shanty @@ -109,27 +111,24 @@ module Shanty end describe('#config') do - let(:env_config) { { 'foo' => 'bar' } } - let(:config) { { 'stray_cats' => env_config } } - before do ENV['SHANTY_ENV'] = 'stray_cats' - allow(YAML).to receive(:load_file) { config } - end - - it('has all the keys from the config file for the current env') do - expect(subject.config).to eql(env_config) end it('handles no config file existing') do allow(File).to receive(:exist?) { false } - expect(subject.config).to eql({}) + expect(subject.config.nic).to eql(OpenStruct.new) end it('handles no data in the config file') do allow(YAML).to receive(:load_file) { false } expect(subject.config) end + + it('returns nothing if .shanty.yml does not exist') do + FileUtils.rm('.shanty.yml') + expect(subject.config.nic).to eql(OpenStruct.new) + end end end end diff --git a/translations/en.yml b/translations/en.yml index 294aff6..d05d892 100644 --- a/translations/en.yml +++ b/translations/en.yml @@ -5,6 +5,8 @@ en: common: options: tags: An optional list of tags to filter by. By default, all projects are used. Note that the tags are ANDed together, eg. passing "foo" and "bar" will only find projects with both those tags. + plugins: + desc: Lists all plugins projects: desc: Lists all projects. build: