From 76a46e58a7b5f4e326f37c94bed4962b18ee2e3f 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 +++-- spec/lib/shanty/plugin_spec.rb | 4 +- .../lib/shanty/plugins/bundler_plugin_spec.rb | 4 +- .../shanty/plugins/cucumber_plugin_spec.rb | 4 +- spec/lib/shanty/plugins/rspec_plugin_spec.rb | 4 +- .../lib/shanty/plugins/rubocop_plugin_spec.rb | 4 +- .../lib/shanty/plugins/rubygem_plugin_spec.rb | 4 +- .../shanty/plugins/shantyfile_plugin_spec.rb | 4 +- translations/en.yml | 2 + 23 files changed, 217 insertions(+), 35 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 fe3e990..aff1b53 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 @@ -28,7 +30,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 @@ -59,10 +61,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 dfcabbc..4796260 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,7 +26,15 @@ def self.all_with_graph(graph) end def self.tags(*args) - (@tags ||= []).concat(args.map(&:to_sym)) + (@tags ||= [name]).concat(args.map(&:to_sym)) + 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) @@ -33,6 +45,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 045798a..1496d8b 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 ca53658..9f18a9d 100644 --- a/shanty.gemspec +++ b/shanty.gemspec @@ -28,6 +28,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/spec/lib/shanty/plugin_spec.rb b/spec/lib/shanty/plugin_spec.rb index d128b61..a7987d6 100644 --- a/spec/lib/shanty/plugin_spec.rb +++ b/spec/lib/shanty/plugin_spec.rb @@ -50,13 +50,13 @@ def foo; end it('stores the given tags') do plugin_class.tags(:foo, :marbles) - expect(plugin_class.instance_variable_get(:@tags)).to match_array([:foo, :marbles]) + expect(plugin_class.instance_variable_get(:@tags)).to match_array([plugin_class.name.to_sym, :foo, :marbles]) end it('converts any given tags to symbols') do plugin_class.tags('bar', 'lux') - expect(plugin_class.instance_variable_get(:@tags)).to match_array([:bar, :lux]) + expect(plugin_class.instance_variable_get(:@tags)).to match_array([plugin_class.name.to_sym, :bar, :lux]) end end diff --git a/spec/lib/shanty/plugins/bundler_plugin_spec.rb b/spec/lib/shanty/plugins/bundler_plugin_spec.rb index 32c14b2..1ec8f72 100644 --- a/spec/lib/shanty/plugins/bundler_plugin_spec.rb +++ b/spec/lib/shanty/plugins/bundler_plugin_spec.rb @@ -6,8 +6,8 @@ module Shanty RSpec.describe(BundlerPlugin) do include_context('basics') - it('adds the bundler tag') do - expect(described_class).to add_tags(:bundler) + it('adds the bundler tag automatically') do + expect(described_class.tags).to match_array([:bundler]) end it('finds projects that have a Gemfile') do diff --git a/spec/lib/shanty/plugins/cucumber_plugin_spec.rb b/spec/lib/shanty/plugins/cucumber_plugin_spec.rb index 8f72584..0bfa1cf 100644 --- a/spec/lib/shanty/plugins/cucumber_plugin_spec.rb +++ b/spec/lib/shanty/plugins/cucumber_plugin_spec.rb @@ -6,8 +6,8 @@ module Shanty RSpec.describe(CucumberPlugin) do include_context('graph') - it('adds the cucumber tag') do - expect(described_class).to add_tags(:cucumber) + it('adds the cucumber tag automatically') do + expect(described_class.tags).to match_array([:cucumber]) end it('finds projects by calling a method to locate the ones that depend on Cucumber') do diff --git a/spec/lib/shanty/plugins/rspec_plugin_spec.rb b/spec/lib/shanty/plugins/rspec_plugin_spec.rb index 9e44846..0414b7a 100644 --- a/spec/lib/shanty/plugins/rspec_plugin_spec.rb +++ b/spec/lib/shanty/plugins/rspec_plugin_spec.rb @@ -7,8 +7,8 @@ module Shanty RSpec.describe(RspecPlugin) do include_context('graph') - it('adds the rspec tag') do - expect(described_class).to add_tags(:rspec) + it('adds the rspec tag automatically') do + expect(described_class.tags).to match_array([:rspec]) end it('finds projects by calling a method to locate the ones that depend on RSpec') do diff --git a/spec/lib/shanty/plugins/rubocop_plugin_spec.rb b/spec/lib/shanty/plugins/rubocop_plugin_spec.rb index e7f1507..cda17f0 100644 --- a/spec/lib/shanty/plugins/rubocop_plugin_spec.rb +++ b/spec/lib/shanty/plugins/rubocop_plugin_spec.rb @@ -6,8 +6,8 @@ module Shanty RSpec.describe(RubocopPlugin) do include_context('basics') - it('adds the rubocop tag') do - expect(described_class).to add_tags(:rubocop) + it('adds the rubocop tag automatically') do + expect(described_class.tags).to match_array([:rubocop]) end it('finds projects that have a .rubocop.yml file') do diff --git a/spec/lib/shanty/plugins/rubygem_plugin_spec.rb b/spec/lib/shanty/plugins/rubygem_plugin_spec.rb index 453e72e..3b487bd 100644 --- a/spec/lib/shanty/plugins/rubygem_plugin_spec.rb +++ b/spec/lib/shanty/plugins/rubygem_plugin_spec.rb @@ -6,8 +6,8 @@ module Shanty RSpec.describe(RubygemPlugin) do include_context('basics') - it('adds the rubygem tag') do - expect(described_class).to add_tags(:rubygem) + it('adds the rubygem tag automatically') do + expect(described_class.tags).to match_array([:rubygem]) end it('finds projects that have a *.gemspec file') do diff --git a/spec/lib/shanty/plugins/shantyfile_plugin_spec.rb b/spec/lib/shanty/plugins/shantyfile_plugin_spec.rb index a25faa0..47ffe57 100644 --- a/spec/lib/shanty/plugins/shantyfile_plugin_spec.rb +++ b/spec/lib/shanty/plugins/shantyfile_plugin_spec.rb @@ -6,8 +6,8 @@ module Shanty RSpec.describe(ShantyfilePlugin) do include_context('graph') - it('adds the shantyfile tag') do - expect(described_class).to add_tags(:shantyfile) + it('adds the shantyfile tag shantyfile') do + expect(described_class.tags).to match_array([:shantyfile]) end it('finds projects by calling a method to locate the ones that have a Shantyfile') do 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: