From 27491b554c0f5c8b6137c563cfc5d167f11ee1b3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 16 Dec 2011 23:34:30 -0800 Subject: [PATCH] Remove thor from requirements. Start revamping Vagrant::CLI --- bin/vagrant | 4 +- lib/vagrant.rb | 2 +- lib/vagrant/cli.rb | 60 ++--------- lib/vagrant/command.rb | 20 ---- lib/vagrant/command/base.rb | 139 ++++++++----------------- lib/vagrant/environment.rb | 2 +- test/unit/vagrant/command/base_test.rb | 32 ++++++ vagrant.gemspec | 1 - 8 files changed, 88 insertions(+), 172 deletions(-) create mode 100644 test/unit/vagrant/command/base_test.rb diff --git a/bin/vagrant b/bin/vagrant index a86d6581e..564cbd947 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -33,8 +33,8 @@ begin logger.debug("Loading environment") env.load! - # Kick start the CLI - Vagrant::CLI.start(ARGV, :env => env) + # Execute the CLI interface + env.cli(ARGV) rescue Vagrant::Errors::VagrantError => e logger.error("Vagrant experienced an error! Details:") logger.error(e.inspect) diff --git a/lib/vagrant.rb b/lib/vagrant.rb index bcce7b6bb..5c9b88e5f 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -23,6 +23,7 @@ module Vagrant autoload :Box, 'vagrant/box' autoload :BoxCollection, 'vagrant/box_collection' autoload :CLI, 'vagrant/cli' + autoload :Command, 'vagrant/command' autoload :Config, 'vagrant/config' autoload :DataStore, 'vagrant/data_store' autoload :Downloaders, 'vagrant/downloaders' @@ -122,6 +123,5 @@ Vagrant.config_keys.register(:linux) { Vagrant::Guest::Linux::LinuxConfig } Vagrant.config_keys.register(:solaris) { Vagrant::Guest::Solaris::SolarisConfig } # Load the things which must be loaded before anything else. -require 'vagrant/command' require 'vagrant/version' Vagrant::Plugin.load! diff --git a/lib/vagrant/cli.rb b/lib/vagrant/cli.rb index 40decec4c..54058e0ae 100644 --- a/lib/vagrant/cli.rb +++ b/lib/vagrant/cli.rb @@ -1,54 +1,16 @@ -require 'thor' - module Vagrant - # Entrypoint for the Vagrant CLI. This class should never be - # initialized directly (like a typical Thor class). Instead, - # use {Environment#cli} to invoke the CLI. - # - # # Defining Custom CLI Commands - # - # If you're looking to define custom CLI commands, then look at - # one of the two following classes: - # - # * {Command::Base} - Implementing a single command such as `vagrant up`, e.g. - # one without subcommands. Also take a look at {Command::NamedBase}. - # * {Command::GroupBase} - Implementing a command with subcommands, such as - # `vagrant box`, which has the `list`, `add`, etc. subcommands. - # - # The above linked classes contain the main documentation for each - # type of command. - class CLI < Thor - # Registers the given class with the CLI so it can be accessed. - # The class must be a subclass of either {Command::Base} or {Command::GroupBase}. - # Don't call this method directly, instead call the {Command::Base.register} - # or {Command::GroupBase.register} methods. - # - # @param [Class] klass Command class - # @param [String] name Command name, accessed at `vagrant NAME` - # @param [String] usage Command usage, such as "vagrant NAME [--option]" - # @param [String] description Description of the command shown during the - # command listing. - # @param [Hash] opts Other options (not gone into detail here, look at - # the source instead). - def self.register(klass, name, usage, description, opts=nil) - opts ||= {} + # Manages the command line interface to Vagrant. + class CLI < Command::Base + def initialize(argv, env) + @env = env + @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv) + end - if klass <= Command::GroupBase - # A subclass of GroupBase is a subcommand, since it contains - # many smaller commands within it. - desc usage, description, opts - subcommand name, klass - elsif klass <= Command::Base - # A subclass of Base is a single command, since it - # is invoked as a whole (as Thor::Group) - desc usage, description, opts - define_method(name) { |*args| invoke klass, args } - end - - if opts[:alias] - # Alises are defined for this command, so properly alias the - # newly defined method/subcommand: - map opts[:alias] => name + def execute + if @main_args.include?("-v") || @main_args.include?("--version") + @env.ui.info(I18n.t("vagrant.commands.version.output", + :version => Vagrant::VERSION), + :prefix => false) end end end diff --git a/lib/vagrant/command.rb b/lib/vagrant/command.rb index bf15b1b85..6d1f5f87d 100644 --- a/lib/vagrant/command.rb +++ b/lib/vagrant/command.rb @@ -1,25 +1,5 @@ module Vagrant module Command autoload :Base, 'vagrant/command/base' - autoload :GroupBase, 'vagrant/command/group_base' - autoload :Helpers, 'vagrant/command/helpers' - autoload :NamedBase, 'vagrant/command/named_base' end end - -# The built-in commands must always be loaded -require 'vagrant/command/box' -require 'vagrant/command/destroy' -require 'vagrant/command/halt' -require 'vagrant/command/init' -require 'vagrant/command/package' -require 'vagrant/command/provision' -require 'vagrant/command/reload' -require 'vagrant/command/resume' -require 'vagrant/command/ssh' -require 'vagrant/command/ssh_config' -require 'vagrant/command/status' -require 'vagrant/command/suspend' -require 'vagrant/command/up' -require 'vagrant/command/upgrade_to_060' -require 'vagrant/command/version' diff --git a/lib/vagrant/command/base.rb b/lib/vagrant/command/base.rb index ece584370..8058a7fea 100644 --- a/lib/vagrant/command/base.rb +++ b/lib/vagrant/command/base.rb @@ -1,105 +1,48 @@ -require 'thor/group' -require 'thor/actions' - module Vagrant module Command - # A {Base} is the superclass for all commands which are single - # commands, e.g. `vagrant init`, `vagrant up`. Not commands like - # `vagrant box add`. For commands which have more subcommands, use - # a {GroupBase}. - # - # A {Base} is a subclass of `Thor::Group`, so view the documentation - # there on how to add arguments, descriptions etc. The important note - # about this is that when invoked, _all public methods_ will be called - # in the order they are defined. If you don't want a method called when - # the command is invoked, it must be made `protected` or `private`. - # - # The best way to get examples of how to create your own command is to - # view the various Vagrant commands, which are relatively simple, and - # can be found in the Vagrant source tree at `lib/vagrant/command/`. - # - # # Defining a New Command - # - # To define a new single command, create a new class which inherits - # from this class, then call {register} to register the command. That's - # it! When the command is invoked, _all public methods_ will be called. - # Below is an example `SayHello` class: - # - # class SayHello < Vagrant::Command::Base - # register "hello", "Says hello" - # - # def hello - # env.ui.info "Hello" - # end - # end - # - # In this case, the above class is invokable via `vagrant hello`. To give - # this a try, just copy and paste the above into a Vagrantfile somewhere. - # The command will be available for that project! - # - # Also note that the above example uses `env.ui` to output. It is recommended - # you use this instead of raw "puts" since it is configurable and provides - # additional functionality, such as colors and asking for user input. See - # the {UI} class for more information. - # - # ## Defining Command-line Options - # - # Most command line actions won't be as simple as `vagrant hello`, and will - # probably require parameters or switches. Luckily, Thor makes adding these - # easy: - # - # class SayHello < Vagrant::Command::Base - # register "hello", "Says hello" - # argument :name, :type => :string - # - # def hello - # env.ui.info "Hello, #{name}" - # end - # end - # - # Then, the above can be invoked with `vagrant hello Mitchell` which would - # output "Hello, Mitchell." If instead you're looking for switches, such as - # "--name Mitchell", then take a look at `class_option`, an example of which - # can be found in the {PackageCommand}. - class Base < Thor::Group - include Thor::Actions - include Helpers - - 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 - # will be `vagrant lamp`. - # - # The description is used when the help is listed, and is meant to be - # a brief (one sentence) description of what the command does. - # - # Some additional options may be passed in as the last parameter: - # - # * `:alias` - If given as an array or string, these will be aliases - # for the same command. For example, `vagrant version` is also - # `vagrant --version` and `vagrant -v` - # - # @param [String] usage - # @param [String] description - # @param [Hash] opts - def self.register(usage, description, opts=nil) - desc description - CLI.register(self, extract_name_from_usage(usage), usage, desc, opts) - end - - def initialize(*args) - super - initialize_environment(*args) - end - + class Base protected - # 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] + # This method will split the argv given into three parts: the + # flags to this command, the subcommand, and the flags to the + # subcommand. For example: + # + # -v status -h -v + # + # The above would yield 3 parts: + # + # ["-v"] + # "status" + # ["-h", "-v"] + # + # These parts are useful because the first is a list of arguments + # given to the current command, the second is a subcommand, and the + # third are the commands given to the subcommand. + # + # @return [Array] The three parts. + def split_main_and_subcommand(argv) + # Initialize return variables + main_args = nil + sub_command = nil + sub_args = [] + + # We split the arguments into two: One set containing any + # flags before a word, and then the rest. The rest are what + # get actually sent on to the subcommand. + argv.each_index do |i| + if !argv[i].start_with?("-") + # We found the beginning of the sub command. Split the + # args up. + main_args = argv[0, i] + sub_command = argv[i] + sub_args = argv[i + 1, argv.length - i + 1] + end + end + + # Handle the case that argv was empty or didn't contain any subcommand + main_args = argv.dup if main_args.nil? + + return [main_args, sub_command, sub_args] end end end diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 7b52b336e..61fe584b5 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -172,7 +172,7 @@ module Vagrant # env.cli("package", "--vagrantfile", "Vagrantfile") # def cli(*args) - CLI.start(args.flatten, :env => self) + CLI.new(args.flatten, self).execute end # Returns the host object associated with this environment. diff --git a/test/unit/vagrant/command/base_test.rb b/test/unit/vagrant/command/base_test.rb new file mode 100644 index 000000000..25eda35d1 --- /dev/null +++ b/test/unit/vagrant/command/base_test.rb @@ -0,0 +1,32 @@ +require File.expand_path("../../../base", __FILE__) + +describe Vagrant::Command::Base do + describe "splitting the main and subcommand args" do + let(:instance) do + Class.new(described_class) do + # Make the method public since it is normal protected + public :split_main_and_subcommand + end.new + end + + it "should work when given all 3 parts" do + result = instance.split_main_and_subcommand(["-v", "status", "-h", "-v"]) + result.should == [["-v"], "status", ["-h", "-v"]] + end + + it "should work when given only a subcommand and args" do + result = instance.split_main_and_subcommand(["status", "-h"]) + result.should == [[], "status", ["-h"]] + end + + it "should work when given only main flags" do + result = instance.split_main_and_subcommand(["-v", "-h"]) + result.should == [["-v", "-h"], nil, []] + end + + it "should work when given only a subcommand" do + result = instance.split_main_and_subcommand(["status"]) + result.should == [[], "status", []] + end + end +end diff --git a/vagrant.gemspec b/vagrant.gemspec index 3ab1e7142..471def75a 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -21,7 +21,6 @@ Gem::Specification.new do |s| s.add_dependency "net-ssh", "~> 2.1.4" s.add_dependency "net-scp", "~> 1.0.4" s.add_dependency "i18n", "~> 0.6.0" - s.add_dependency "thor", "~> 0.14.6" s.add_dependency "virtualbox", "~> 0.9.1" s.add_development_dependency "rake"