## Author:: Adam Jacob (<adam@opscode.com>)# Copyright:: Copyright (c) 2008 Opscode, Inc.# License:: Apache License, Version 2.0## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.#require'optparse'moduleMixlib# == Mixlib::CLI# Adds a DSL for defining command line options and methods for parsing those# options to the including class.## Mixlib::CLI does some setup in #initialize, so the including class must# call `super()` if it defines a custom initializer.## === DSL# When included, Mixlib::CLI also extends the including class with its# ClassMethods, which define the DSL. The primary methods of the DSL are# ClassMethods#option, which defines a command line option, and# ClassMethods#banner, which defines the "usage" banner.## === Parsing# Command line options are parsed by calling the instance method# #parse_options. After calling this method, the attribute #config will# contain a hash of `:option_name => value` pairs.moduleCLImoduleClassMethods# When this setting is set to +true+, default values supplied to the# mixlib-cli DSL will be stored in a separate Hashdefuse_separate_default_options(true_or_false)@separate_default_options=true_or_falseenddefuse_separate_defaults?@separate_default_options||falseend# Add a command line option.## === Parameters# name<Symbol>:: The name of the option to add# args<Hash>:: A hash of arguments for the option, specifying how it should be parsed.# === Returns# true:: Always returns true.defoption(name,args)@options||={}raise(ArgumentError,"Option name must be a symbol")unlessname.kind_of?(Symbol)@options[name.to_sym]=argsend# Get the hash of current options.## === Returns# @options<Hash>:: The current options hash.defoptions@options||={}@optionsend# Set the current options hash## === Parameters# val<Hash>:: The hash to set the options to## === Returns# @options<Hash>:: The current options hash.defoptions=(val)raise(ArgumentError,"Options must recieve a hash")unlessval.kind_of?(Hash)@options=valend# Change the banner. Defaults to:# Usage: #{0} (options)## === Parameters# bstring<String>:: The string to set the banner to## === Returns# @banner<String>:: The current bannerdefbanner(bstring=nil)ifbstring@banner=bstringelse@banner||="Usage: #{$0} (options)"@bannerendendend# Gives the command line options definition as configured in the DSL. These# are used by #parse_options to generate the option parsing code. To get# the values supplied by the user, see #config.attr_accessor:options# A Hash containing the values supplied by command line options.## The behavior and contents of this Hash vary depending on whether# ClassMethods#use_separate_default_options is enabled.# ==== use_separate_default_options *disabled*# After initialization, +config+ will contain any default values defined# via the mixlib-config DSL. When #parse_options is called, user-supplied# values (from ARGV) will be merged in.# ==== use_separate_default_options *enabled*# After initialization, this will be an empty hash. When #parse_options is# called, +config+ is populated *only* with user-supplied values.attr_accessor:config# If ClassMethods#use_separate_default_options is enabled, this will be a# Hash containing key value pairs of `:option_name => default_value`# (populated during object initialization).## If use_separate_default_options is disabled, it will always be an empty# hash.attr_accessor:default_config# Any arguments which were not parsed and placed in "config"--the leftovers.attr_accessor:cli_arguments# Banner for the option parser. If the option parser is printed, e.g., by# `puts opt_parser`, this string will be used as the first line.attr_accessor:banner# Create a new Mixlib::CLI class. If you override this, make sure you call super!## === Parameters# *args<Array>:: The array of arguments passed to the initializer## === Returns# object<Mixlib::Config>:: Returns an instance of whatever you wanted :)definitialize(*args)@options=Hash.new@config=Hash.new@default_config=Hash.new@opt_parser=nil# Set the banner@banner=self.class.banner# Dupe the class options for this instanceklass_options=self.class.optionsklass_options.keys.inject(@options){|memo,key|memo[key]=klass_options[key].dup;memo}# If use_separate_defaults? is on, default values go in @default_configdefaults_container=ifself.class.use_separate_defaults?@default_configelse@configend# Set the default configuration values for this instance@options.eachdo|config_key,config_opts|config_opts[:on]||=:onconfig_opts[:boolean]||=falseconfig_opts[:required]||=falseconfig_opts[:proc]||=nilconfig_opts[:show_options]||=falseconfig_opts[:exit]||=nilifconfig_opts.has_key?(:default)defaults_container[config_key]=config_opts[:default]endendsuper(*args)end# Parses an array, by default ARGV, for command line options (as configured at# the class level).# === Parameters# argv<Array>:: The array of arguments to parse; defaults to ARGV## === Returns# argv<Array>:: Returns any un-parsed elements.defparse_options(argv=ARGV)argv=argv.dupopt_parser.parse!(argv)# Deal with any required valuesoptions.eachdo|opt_key,opt_value|ifopt_value[:required]&&!config.has_key?(opt_key)reqarg=opt_value[:short]||opt_value[:long]puts"You must supply #{reqarg}!"puts@opt_parserexit2endend@cli_arguments=argvargvend# The option parser generated from the mixlib-cli DSL. +opt_parser+ can be# used to print a help message including the banner and any CLI options via# `puts opt_parser`.# === Returns# opt_parser<OptionParser>:: The option parser object.defopt_parser@opt_parser||=OptionParser.newdo|opts|# Set the banneropts.banner=banner# Create new optionsoptions.sort{|a,b|a[0].to_s<=>b[0].to_s}.eachdo|opt_key,opt_val|opt_args=build_option_arguments(opt_val)opt_method=caseopt_val[:on]when:on:onwhen:tail:on_tailwhen:head:on_headelseraiseArgumentError,"You must pass :on, :tail, or :head to :on"endparse_block=Proc.new()do|c|config[opt_key]=(opt_val[:proc]&&opt_val[:proc].call(c))||cputsoptsifopt_val[:show_options]exitopt_val[:exit]ifopt_val[:exit]endfull_opt=[opt_method]opt_args.inject(full_opt){|memo,arg|memo<<arg;memo}full_opt<<parse_blockopts.send(*full_opt)endendenddefbuild_option_arguments(opt_setting)arguments=Array.newarguments<<opt_setting[:short]ifopt_setting.has_key?(:short)arguments<<opt_setting[:long]ifopt_setting.has_key?(:long)ifopt_setting.has_key?(:description)description=opt_setting[:description]description<<" (required)"ifopt_setting[:required]arguments<<descriptionendargumentsenddefself.included(receiver)receiver.extend(Mixlib::CLI::ClassMethods)endendend