Remove thor from requirements. Start revamping Vagrant::CLI

This commit is contained in:
Mitchell Hashimoto 2011-12-16 23:34:30 -08:00
parent 5f567f30d8
commit 27491b554c
8 changed files with 88 additions and 172 deletions

View File

@ -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)

View File

@ -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!

View File

@ -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 ||= {}
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 }
# 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 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

View File

@ -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'

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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"