From 794bdddf49b5a19a4347b81910d4db71dad6b5ac Mon Sep 17 00:00:00 2001 From: Les Hill Date: Sat, 2 Nov 2013 12:15:25 -0700 Subject: [PATCH] Load subcommand plug-ins at runtime We use rubygems to load a subcommand plug-in gem with a file that can be loaded at /plugin.rb The `command-path` is the namespace of the command converted to a path. For example `Pod::Command` becomes `pod/command`. The `plugin.rb` file should either define the subcommand(s) directly or require the files for the subcommand(s). For example [open_pod_bay](https://github.com/leshill/open_pod_bay) has the following `plugin.rb` require 'pod/command/open' --- lib/claide/command.rb | 34 ++++++++++++++++++++++++++ spec/command_spec.rb | 33 +++++++++++++++++++++++++ spec/fixture/command/demo_plugin.rb | 18 ++++++++++++++ spec/fixture/command/plugin_fixture.rb | 3 +++ 4 files changed, 88 insertions(+) create mode 100644 spec/fixture/command/demo_plugin.rb create mode 100644 spec/fixture/command/plugin_fixture.rb diff --git a/lib/claide/command.rb b/lib/claide/command.rb index 9b0df95..cb2b0f8 100644 --- a/lib/claide/command.rb +++ b/lib/claide/command.rb @@ -201,6 +201,7 @@ def parse(argv) # @return [void] # def run(argv) + load_plugins command = parse(argv) command.validate! command.run @@ -261,6 +262,39 @@ def banner(colorize = false) Banner.new(self, colorize).formatted_banner end + # Load additional plugins via rubygems looking for: + # + # /plugin.rb + # + # where is the namespace of the Command converted to a + # path, for example: + # + # Pod::Command + # + # maps to + # + # pod/command + # + def load_plugins + if Gem.respond_to? :find_latest_files + Gem.find_latest_files("#{underscore(name)}/plugin").each {|path| require path } + else + Gem.find_files("#{underscore(name)}/plugin").each {|path| require path } + end + end + + private + + # Cribbed from ActiveSupport + # https://github.com/rails/rails/blob/master/activesupport/MIT-LICENSE + def underscore(str) + underscored = str.to_s.dup + underscored.gsub!(/::/, '/') + underscored.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') + underscored.gsub!(/([a-z\d])([A-Z])/,'\1_\2') + underscored.tr!("-", "_") + underscored.downcase! + end end #-------------------------------------------------------------------------# diff --git a/spec/command_spec.rb b/spec/command_spec.rb index d797389..6d327b9 100644 --- a/spec/command_spec.rb +++ b/spec/command_spec.rb @@ -16,6 +16,39 @@ module CLAide Fixture::Command.parse(%w{ spec-file --verbose lint }).should.be.instance_of Fixture::Command::SpecFile::Lint #Fixture::Command.parse(%w{ spec-file lint --help repo }).should.be.instance_of Fixture::Command::SpecFile::Lint::Repo end + + describe "plugins" do + describe "when the plugin is at /plugin.rb" do + PLUGIN_FIXTURE = Pathname.new(ROOT) + 'spec/fixture/command/plugin_fixture.rb' + PLUGIN = Pathname.new(ROOT) + 'spec/fixture/command/plugin.rb' + + before do + FileUtils.copy PLUGIN_FIXTURE, PLUGIN + end + + after do + FileUtils.remove_file PLUGIN + end + + it "loads the plugin" do + Fixture::Command.subcommands.find {|subcmd| subcmd.command == 'demo-plugin'}.should.be.nil + Fixture::Command.load_plugins + plugin_class = Fixture::Command.subcommands.find {|subcmd| subcmd.command == 'demo-plugin'} + plugin_class.ancestors.should.include Fixture::Command + plugin_class.description.should =~ /plugins/ + end + + it "is available for help" do + Fixture::Command.load_plugins + CLAide::Command::Banner.new(Fixture::Command, false).formatted_banner.should =~ /demo-plugin/ + end + end + + it "fails normally if there is no plugin" do + Fixture::Command.load_plugins + Fixture::Command.subcommands.find {|subcmd| subcmd.name == 'demo-plugin' }.should.be.nil + end + end end #-------------------------------------------------------------------------# diff --git a/spec/fixture/command/demo_plugin.rb b/spec/fixture/command/demo_plugin.rb new file mode 100644 index 0000000..f9d3a9a --- /dev/null +++ b/spec/fixture/command/demo_plugin.rb @@ -0,0 +1,18 @@ +# encoding: utf-8 +module Fixture + class Command + class DemoPlugin < Command + self.summary = 'Plugins!' + self.description = <<-DESC + Let’s add plugins to CLAide and CocoaPods. + DESC + self.arguments = '[NAME]' + + attr_reader :name + def initialize(argv) + @name = argv.shift_argument + super + end + end + end +end diff --git a/spec/fixture/command/plugin_fixture.rb b/spec/fixture/command/plugin_fixture.rb new file mode 100644 index 0000000..48f3bfe --- /dev/null +++ b/spec/fixture/command/plugin_fixture.rb @@ -0,0 +1,3 @@ +# Load a CLAide plugin by requiring it in your `plugin.rb` or by +# defining it directly in the file. +require 'fixture/command/demo_plugin'