require 'optparse' module Capistrano class CLI module Options def self.included(base) base.extend(ClassMethods) end module ClassMethods # Return a new CLI instance with the given arguments pre-parsed and # ready for execution. def parse(args) cli = new(args) cli.parse_options! cli end end # The hash of (parsed) command-line options attr_reader :options # Return an OptionParser instance that defines the acceptable command # line switches for Capistrano, and what their corresponding behaviors # are. def option_parser #:nodoc: @logger = Logger.new @option_parser ||= OptionParser.new do |opts| opts.banner = "Usage: #{File.basename($0)} [options] action ..." opts.on("-d", "--debug", "Prompts before each remote command execution." ) { |value| options[:debug] = true } opts.on("-e", "--explain TASK", "Displays help (if available) for the task." ) { |value| options[:explain] = value } opts.on("-F", "--default-config", "Always use default config, even with -f." ) { options[:default_config] = true } opts.on("-f", "--file FILE", "A recipe file to load. May be given more than once." ) { |value| options[:recipes] << value } opts.on("-H", "--long-help", "Explain these options and environment variables.") do long_help exit end opts.on("-h", "--help", "Display this help message.") do puts opts exit end opts.on("-l", "--logger [STDERR|STDOUT|file]", "Choose logger method. STDERR used by default." ) do |value| options[:output] = if value.nil? || value.upcase == 'STDERR' # Using default logger. nil elsif value.upcase == 'STDOUT' $stdout else value end end opts.on("-n", "--dry-run", "Prints out commands without running them." ) { |value| options[:dry_run] = true } opts.on("-p", "--password", "Immediately prompt for the password." ) { options[:password] = nil } opts.on("-q", "--quiet", "Make the output as quiet as possible." ) { options[:verbose] = 0 } opts.on("-r", "--preserve-roles", "Preserve task roles" ) { options[:preserve_roles] = true } opts.on("-S", "--set-before NAME=VALUE", "Set a variable before the recipes are loaded." ) do |pair| name, value = pair.split(/=/, 2) options[:pre_vars][name.to_sym] = value end opts.on("-s", "--set NAME=VALUE", "Set a variable after the recipes are loaded." ) do |pair| name, value = pair.split(/=/, 2) options[:vars][name.to_sym] = value end opts.on("-T", "--tasks [PATTERN]", "List all tasks (matching optional PATTERN) in the loaded recipe files." ) do |value| options[:tasks] = if value value else true end options[:verbose] ||= 0 end opts.on("-t", "--tool", "Abbreviates the output of -T for tool integration." ) { options[:tool] = true } opts.on("-V", "--version", "Display the Capistrano version, and exit." ) do require 'capistrano/version' puts "Capistrano v#{Capistrano::Version}" exit end opts.on("-v", "--verbose", "Be more verbose. May be given more than once." ) do options[:verbose] ||= 0 options[:verbose] += 1 end opts.on("-X", "--skip-system-config", "Don't load the system config file (capistrano.conf)" ) { options.delete(:sysconf) } opts.on("-x", "--skip-user-config", "Don't load the user config file (.caprc)" ) { options.delete(:dotfile) } end end # If the arguments to the command are empty, this will print the # allowed options and exit. Otherwise, it will parse the command # line and set up any default options. def parse_options! #:nodoc: @options = { :recipes => [], :actions => [], :vars => {}, :pre_vars => {}, :sysconf => default_sysconf, :dotfile => default_dotfile } if args.empty? warn "Please specify at least one action to execute." warn option_parser exit end option_parser.parse!(args) coerce_variable_types! # if no verbosity has been specified, be verbose options[:verbose] = 3 if !options.has_key?(:verbose) look_for_default_recipe_file! if options[:default_config] || options[:recipes].empty? extract_environment_variables! options[:actions].concat(args) password = options.has_key?(:password) options[:password] = Proc.new { self.class.password_prompt } options[:password] = options[:password].call if password end # Extracts name=value pairs from the remaining command-line arguments # and assigns them as environment variables. def extract_environment_variables! #:nodoc: args.delete_if do |arg| next unless arg.match(/^(\w+)=(.*)$/) ENV[$1] = $2 end end # Looks for a default recipe file in the current directory. def look_for_default_recipe_file! #:nodoc: current = Dir.pwd loop do %w(Capfile capfile).each do |file| if File.file?(file) options[:recipes] << file @logger.info "Using recipes from #{File.join(current,file)}" return end end pwd = Dir.pwd Dir.chdir("..") break if pwd == Dir.pwd # if changing the directory made no difference, then we're at the top end Dir.chdir(current) end def default_sysconf #:nodoc: File.join(sysconf_directory, "capistrano.conf") end def default_dotfile #:nodoc: File.join(home_directory, ".caprc") end def sysconf_directory #:nodoc: # TODO if anyone cares, feel free to submit a patch that uses a more # appropriate location for this file in Windows. ENV["SystemRoot"] || '/etc' end def home_directory #:nodoc: ENV["HOME"] || (ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") || "/" end def coerce_variable_types! [:pre_vars, :vars].each do |collection| options[collection].keys.each do |key| options[collection][key] = coerce_variable(options[collection][key]) end end end def coerce_variable(value) case value when /^"(.*)"$/ then $1 when /^'(.*)'$/ then $1 when /^\d+$/ then value.to_i when /^\d+\.\d*$/ then value.to_f when "true" then true when "false" then false when "nil" then nil else value end end end end end