From 5af0537e56ef7bc76c8fabc371fd5c3af5c6eb23 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Aug 2010 10:58:36 -0700 Subject: [PATCH] Command::GroupBase for creating subcommands for Thor. --- lib/vagrant.rb | 3 +- lib/vagrant/cli.rb | 6 ++-- lib/vagrant/command/base.rb | 13 +++++--- lib/vagrant/command/box.rb | 12 +++++++ lib/vagrant/command/group_base.rb | 37 ++++++++++++++++++++++ test/vagrant/cli_test.rb | 7 +++++ test/vagrant/command/base_test.rb | 7 +++++ test/vagrant/command/group_base_test.rb | 42 +++++++++++++++++++++++++ 8 files changed, 119 insertions(+), 8 deletions(-) create mode 100644 lib/vagrant/command/box.rb create mode 100644 lib/vagrant/command/group_base.rb create mode 100644 test/vagrant/command/group_base_test.rb diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 691cc088d..fca27effb 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -9,7 +9,8 @@ module Vagrant autoload :CLI, 'vagrant/cli' module Command - autoload :Base, 'vagrant/command/base' + autoload :Base, 'vagrant/command/base' + autoload :GroupBase, 'vagrant/command/group_base' end class << self diff --git a/lib/vagrant/cli.rb b/lib/vagrant/cli.rb index dad6784e7..0ef75f9b7 100644 --- a/lib/vagrant/cli.rb +++ b/lib/vagrant/cli.rb @@ -8,13 +8,13 @@ module Vagrant # Registers the given class with the CLI so it can be accessed. # The class must be a subclass of either {Command} or {GroupCommand}. def self.register(klass, name, usage, description) - if klass <= Thor # TODO: make Command::GroupBase - # A subclass of Thor is a subcommand, since it contains + if klass <= Command::GroupBase + # A subclass of GroupBase is a subcommand, since it contains # many smaller commands within it. desc usage, description subcommand name, klass elsif klass <= Command::Base - # A subclass of Thor::Group is a single command, since it + # A subclass of Base is a single command, since it # is invoked as a whole. desc usage, description define_method(name) { |*args| invoke klass, args } diff --git a/lib/vagrant/command/base.rb b/lib/vagrant/command/base.rb index b544d8452..a3cadc932 100644 --- a/lib/vagrant/command/base.rb +++ b/lib/vagrant/command/base.rb @@ -19,6 +19,8 @@ module Vagrant class Base < Thor::Group include Thor::Actions + attr_reader :env + # Register the command with the main Vagrant CLI under the # given name. The name will be used for accessing it from the CLI, # so if you name it "lamp", then the command to invoke this @@ -27,10 +29,13 @@ module Vagrant # The description added to the class via the `desc` method will be # used as a description for the command. def self.register(usage) - # Extracts the name out of the usage string. So `init [foo] [bar]` - # becomes "init" - _, name = /^([a-zA-Z0-9]+)(\s+(.+?))?$/.match(usage).to_a - CLI.register(self, name, usage, desc) + CLI.register(self, extract_name_from_usage(usage), usage, desc) + end + + # Extracts the name of the command from a usage string. Example: + # `init [box_name] [box_url]` becomes just `init`. + def self.extract_name_from_usage(usage) + /^([a-zA-Z0-9]+)(\s+(.+?))?$/.match(usage).to_a[1] end def initialize(args=[], options={}, config={}) diff --git a/lib/vagrant/command/box.rb b/lib/vagrant/command/box.rb new file mode 100644 index 000000000..5c80381af --- /dev/null +++ b/lib/vagrant/command/box.rb @@ -0,0 +1,12 @@ +module Vagrant + module Command + class BoxCommand < GroupBase + register "box", "Commands to manage system boxes" + + desc "add NAME URI", "Add a box to the system" + def add(name, uri) + Box.add(env, name, uri) + end + end + end +end diff --git a/lib/vagrant/command/group_base.rb b/lib/vagrant/command/group_base.rb new file mode 100644 index 000000000..c3a741f07 --- /dev/null +++ b/lib/vagrant/command/group_base.rb @@ -0,0 +1,37 @@ +require 'thor' +require 'thor/actions' + +module Vagrant + module Command + # A {GroupBase} is the subclass which should be used if you're + # creating a CLI command which has subcommands such as `vagrant box`, + # which has subcommands such as `add`, `remove`, `list`. If you're + # creating a simple command which has no subcommands, such as `vagrant up`, + # then use {Base} instead. + class GroupBase < Thor + include Thor::Actions + + attr_reader :env + + # Register the command with the main Vagrant CLI under the given + # usage. The usage will be used for accessing it from the CLI, + # so if you give it a usage of `lamp [subcommand]`, then the command + # to invoke this will be `vagrant lamp` (with a subcommand). + # + # Additionally, unlike {Base}, a description must be specified to + # this register command, since there is no class-wide description. + def self.register(usage, description) + CLI.register(self, Base.extract_name_from_usage(usage), usage, description) + end + + def initialize(args=[], options={}, config={}) + super + + # The last argument must _always_ be a Vagrant Environment class. + raise CLIMissingEnvironment.new("This command requires that a Vagrant environment be properly passed in as the last parameter.") if !config[:env] + @env = config[:env] + @env.ui = UI::Shell.new(shell) if !@env.ui.is_a?(UI::Shell) + end + end + end +end diff --git a/test/vagrant/cli_test.rb b/test/vagrant/cli_test.rb index 5f52316fb..c7826875b 100644 --- a/test/vagrant/cli_test.rb +++ b/test/vagrant/cli_test.rb @@ -12,5 +12,12 @@ class CLITest < Test::Unit::TestCase @klass.register(base, name, name, "A description") assert @klass.tasks[name] end + + should "register a group base as a subcommand" do + base = Class.new(Vagrant::Command::GroupBase) + name = "_test_registering_single_group" + @klass.register(base, name, name, "A description") + assert @klass.subcommands.include?(name) + end end end diff --git a/test/vagrant/command/base_test.rb b/test/vagrant/command/base_test.rb index 4bd56a8e7..20f0e3178 100644 --- a/test/vagrant/command/base_test.rb +++ b/test/vagrant/command/base_test.rb @@ -6,6 +6,13 @@ class CommandBaseTest < Test::Unit::TestCase @env = mock_environment end + context "extracting a name from a usage string" do + should "extract properly" do + assert_equal "init", @klass.extract_name_from_usage("init") + assert_equal "init", @klass.extract_name_from_usage("init [foo] [bar]") + end + end + context "setting up a UI" do setup do @env.ui = nil diff --git a/test/vagrant/command/group_base_test.rb b/test/vagrant/command/group_base_test.rb new file mode 100644 index 000000000..8b9be9a48 --- /dev/null +++ b/test/vagrant/command/group_base_test.rb @@ -0,0 +1,42 @@ +require "test_helper" + +class CommandGroupBaseTest < Test::Unit::TestCase + setup do + @klass = Vagrant::Command::GroupBase + @env = mock_environment + end + + context "setting up a UI" do + setup do + @env.ui = nil + end + + should "setup a shell UI" do + silence_stream(STDOUT) { @klass.start([], :env => @env) } + assert @env.ui.is_a?(Vagrant::UI::Shell) + end + + should "setup a shell UI only once" do + silence_stream(STDOUT) { @klass.start([], :env => @env) } + ui = @env.ui + silence_stream(STDOUT) { @klass.start([], :env => @env) } + assert @env.ui.equal?(ui) + end + end + + context "requiring an environment" do + should "raise an exception if the environment is not sent in" do + assert_raises(Vagrant::CLIMissingEnvironment) { + @klass.start([]) + } + end + + should "not raise an exception if the environment is properly sent in" do + silence_stream(STDOUT) do + assert_nothing_raised { + @klass.start([], :env => @env) + } + end + end + end +end