Class: Boson::OptionParser

Inherits:
Object
  • Object
show all
Defined in:
lib/boson/option_parser.rb

Overview

This class concisely defines commandline options that when parsed produce a Hash of option keys and values. Additional points:

  • Setting option values should follow conventions in *nix environments. See examples below.

  • By default, there are 5 option types, each which produce different objects for option values.

  • The default option types can produce objects for one or more of the following Ruby classes: String, Integer, Float, Array, Hash, FalseClass, TrueClass.

  • Users can define their own option types which create objects for any Ruby class. See Options.

  • Each option type can have attributes to enable more features (see OptionParser.new).

  • When options are parsed by parse(), an IndifferentAccessHash hash is returned.

  • Options are also called switches, parameters, flags etc.

  • Option parsing stops when it comes across a ‘–’.

Default option types:

:boolean

This option has no passed value. To toogle a boolean, prepend with ‘–no-’. Multiple booleans can be joined together.

'--debug'    -> {:debug=>true}
'--no-debug' -> {:debug=>false}
'--no-d'     -> {:debug=>false}
'-d -f -t' same as '-dft'
:string

Sets values by separating name from value with space or ‘=’.

'--color red' -> {:color=>'red'}
'--color=red' -> {:color=>'red'}
'--color "gotta love spaces"' -> {:color=>'gotta love spaces'}
:numeric

Sets values as :string does or by appending number right after aliased name. Shortened form can be appended to joined booleans.

'-n3'  -> {:num=>3}
'-dn3' -> {:debug=>true, :num=>3}
:array

Sets values as :string does. Multiple values are split by a configurable character Default is ‘,’ (see OptionParser.new). Passing ‘*’ refers to all known :values.

'--fields 1,2,3' -> {:fields=>['1','2','3']}
'--fields *'     -> {:fields=>['1','2','3']}
:hash

Sets values as :string does. Key-value pairs are split by ‘:’ and pairs are split by a configurable character (default ‘,’). Multiple keys can be joined to one value. Passing ‘*’ as a key refers to all known :keys.

'--fields a:b,c:d' -> {:fields=>{'a'=>'b', 'c'=>'d'} }
'--fields a,b:d'   -> {:fields=>{'a'=>'d', 'b'=>'d'} }
'--fields *:d'     -> {:fields=>{'a'=>'d', 'b'=>'d', 'c'=>'d'} }

This is a modified version of Yehuda Katz’s Thor::Options class which is a modified version of Daniel Berger’s Getopt::Long class (licensed under Ruby’s license).

Defined Under Namespace

Classes: Error

Constant Summary collapse

NUMERIC =
/(\d*\.\d+|\d+)/
LONG_RE =
/^(--\w+[-\w+]*)$/
SHORT_RE =
/^(-[a-zA-Z])$/i
EQ_RE =
/^(--\w+[-\w+]*|-[a-zA-Z])=(.*)$/i
SHORT_SQ_RE =

Allow either -x -v or -xv style for single char args

/^-([a-zA-Z]{2,})$/i
SHORT_NUM =
/^(-[a-zA-Z])#{NUMERIC}$/i
STOP_STRINGS =
%w{-- -}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts) ⇒ OptionParser

Takes a hash of options. Each option, a key-value pair, must provide the option’s name and type. Names longer than one character are accessed with ‘–’ while one character names are accessed with ‘-’. Names can be symbols, strings or even dasherized strings:

Boson::OptionParser.new :debug=>:boolean, 'level'=>:numeric,
  '--fields'=>:array

Options can have default values and implicit types simply by changing the option type for the default value:

Boson::OptionParser.new :debug=>true, 'level'=>3.1, :fields=>%w{f1 f2}

By default every option name longer than one character is given an alias, the first character from its name. For example, the –fields option has -f as its alias. You can override the default alias by providing your own option aliases as an array in the option’s key.

Boson::OptionParser.new [:debug, :damnit, :D]=>true

Note that aliases are accessed the same way as option names. For the above, –debug, –damnit and -D all refer to the same option.

Options can have additional attributes by passing a hash to the option value instead of a type or default:

Boson::OptionParser.new :fields=>{:type=>:array, :values=>%w{f1 f2 f3},
 :enum=>false}

These attributes are available when an option is parsed via current_attributes(). Here are the available option attributes for the default option types:

:type

This or :default is required. Available types are :string, :boolean, :array, :numeric, :hash.

:default

This or :type is required. This is the default value an option has when not passed.

:bool_default

This is the value an option has when passed as a boolean. However, by enabling this an option can only have explicit values with ‘=’ i.e. ‘–index=alias’ and no ‘–index alias’. If this value is a string, it is parsed as any option value would be. Otherwise, the value is passed directly without parsing.

:required

Boolean indicating if option is required. Option parses raises error if value not given. Default is false.

:alias

Alternative way to define option aliases with an option name or an array of them. Useful in yaml files. Setting to false will prevent creating an automatic alias.

:values

An array of values an option can have. Available for :array and :string options. Values here can be aliased by typing a unique string it starts with or underscore aliasing (see Util.underscore_search). For example, for values foo, odd and obnoxiously_long, f refers to foo, od to odd and o_l to obnoxiously_long.

:enum

Boolean indicating if an option enforces values in :values or :keys. Default is true. For :array, :hash and :string options.

:split

For :array and :hash options. A string or regular expression on which an array value splits to produce an array of values. Default is ‘,’.

:keys

:hash option only. An array of values a hash option’s keys can have. Keys can be aliased just like :values.

:default_keys

For :hash option only. Default keys to assume when only a value is given. Multiple keys can be joined by the :split character. Defaults to first key of :keys if :keys given.

:regexp

For :array option with a :values attribute. Boolean indicating that each option value does a regular expression search of :values. If there are values that match, they replace the original option value. If none, then the original option value is used.



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/boson/option_parser.rb', line 165

def initialize(opts)
  @defaults = {}
  @opt_aliases = {}
  @leading_non_opts, @trailing_non_opts = [], []

  # build hash of dashed options to option types
  # type can be a hash of opt attributes, a default value or a type symbol
  @opt_types = opts.inject({}) do |mem, (name, type)|
    name, *aliases = name if name.is_a?(Array)
    name = name.to_s
    # we need both nice and dasherized form of option name
    if name.index('-') == 0
      nice_name = undasherize name
    else
      nice_name = name
      name = dasherize name
    end
    # store for later
    @opt_aliases[nice_name] = aliases || []

    if type.is_a?(Hash)
      @option_attributes ||= {}
      @option_attributes[nice_name] = type
      @opt_aliases[nice_name] = Array(type[:alias]) if type.key?(:alias)
      @defaults[nice_name] = type[:default] if type[:default]
      @option_attributes[nice_name][:enum] = true if (type.key?(:values) || type.key?(:keys)) &&
        !type.key?(:enum)
      @option_attributes[nice_name][:default_keys] ||= type[:keys][0] if type.key?(:keys)
      type = type[:type] || (!type[:default].nil? ? determine_option_type(type[:default]) : :boolean)
    end

    # set defaults
    case type
    when TrueClass                     then  @defaults[nice_name] = true
    when FalseClass                    then  @defaults[nice_name] = false
    else @defaults[nice_name] = type unless type.is_a?(Symbol)
    end
    mem[name] = !type.nil? ? determine_option_type(type) : type
    mem
  end

  # generate hash of dashed aliases to dashed options
  @opt_aliases = @opt_aliases.sort.inject({}) {|h, (nice_name, aliases)|
    name = dasherize nice_name
    # allow for aliases as symbols
    aliases.map! {|e| e.to_s.index('-') == 0 || e == false ? e : dasherize(e.to_s) }
    if aliases.empty? and nice_name.length > 1
      opt_alias = nice_name[0,1]
      opt_alias = h.key?("-"+opt_alias) ? "-"+opt_alias.capitalize : "-"+opt_alias
      h[opt_alias] ||= name unless @opt_types.key?(opt_alias)
    else
      aliases.each {|e| h[e] = name if !@opt_types.key?(e) && e != false }
    end
    h
  }
end

Instance Attribute Details

#leading_non_optsObject (readonly)

Returns the value of attribute leading_non_opts.



82
83
84
# File 'lib/boson/option_parser.rb', line 82

def leading_non_opts
  @leading_non_opts
end

#opt_aliasesObject (readonly)

Returns the value of attribute opt_aliases.



82
83
84
# File 'lib/boson/option_parser.rb', line 82

def opt_aliases
  @opt_aliases
end

#trailing_non_optsObject (readonly)

Returns the value of attribute trailing_non_opts.



82
83
84
# File 'lib/boson/option_parser.rb', line 82

def trailing_non_opts
  @trailing_non_opts
end

Class Method Details

.make_mergeable!(opts) ⇒ Object

:nodoc:



97
98
99
100
101
102
103
# File 'lib/boson/option_parser.rb', line 97

def self.make_mergeable!(opts) #:nodoc:
  opts.each {|k,v|
    if !v.is_a?(Hash) && !v.is_a?(Symbol)
      opts[k] = {:default=>v}
    end
  }
end

.parse(options, args = ARGV) ⇒ Object

Given options to pass to OptionParser.new, this method parses ARGV and returns the remaining arguments and a hash of parsed options. This is useful for scripts outside of Boson.



86
87
88
89
90
# File 'lib/boson/option_parser.rb', line 86

def self.parse(options, args=ARGV)
  @opt_parser ||= new(options)
  parsed_options = @opt_parser.parse(args)
  [@opt_parser.non_opts, parsed_options]
end

.usageObject

Usage string summarizing options defined in parse



93
94
95
# File 'lib/boson/option_parser.rb', line 93

def self.usage
  @opt_parser.to_s
end

Instance Method Details

#aliasesObject

List of option aliases



349
350
351
# File 'lib/boson/option_parser.rb', line 349

def aliases
  @opt_aliases.keys.map {|e| undasherize e }
end

#all_options_with_fields(fields) ⇒ Object

:nodoc:



293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/boson/option_parser.rb', line 293

def all_options_with_fields(fields) #:nodoc:
  aliases = @opt_aliases.invert
  @opt_types.keys.sort.inject([]) {|t,e|
    nice_name = undasherize(e)
    h = {:name=>e, :type=>@opt_types[e], :alias=>aliases[e] || '' }
    h[:default] = @defaults[nice_name] if fields.include?(:default)
    (fields - h.keys).each {|f|
      h[f] = (option_attributes[nice_name] || {})[f]
    }
    t << h
  }
end

#current_attributesObject

Hash of option attributes for the currently parsed option. Any hash keys passed to an option are available here. This means that an option type can have any user-defined attributes available during option parsing and object creation.



324
325
326
# File 'lib/boson/option_parser.rb', line 324

def current_attributes
  @option_attributes && @option_attributes[@current_option] || {}
end

#dasherize(str) ⇒ Object

Adds dashes to an option name i.e. ‘date’ -> ‘–date’ and ‘d’ -> ‘-d’.



334
335
336
# File 'lib/boson/option_parser.rb', line 334

def dasherize(str)
  (str.length > 1 ? "--" : "-") + str
end

#default_render_optionsObject

:nodoc:



306
307
308
309
# File 'lib/boson/option_parser.rb', line 306

def default_render_options #:nodoc:
  {:header_filter=>:capitalize, :description=>false, :filter_any=>true,
    :filter_classes=>{Array=>[:join, ',']}, :hide_empty=>true}
end

#default_usage(opt, val) ⇒ Object

Helper method to generate usage. Takes a dashed option and a string value indicating an option value’s format.



267
268
269
# File 'lib/boson/option_parser.rb', line 267

def default_usage(opt, val)
  opt + "=" + (@defaults[undasherize(opt)] || val).to_s
end

#formatted_usageObject Also known as: to_s

Generates one-line usage of all options.



272
273
274
275
276
277
278
# File 'lib/boson/option_parser.rb', line 272

def formatted_usage
  return "" if @opt_types.empty?
  @opt_types.map do |opt, type|
    val = respond_to?("usage_for_#{type}", true) ? send("usage_for_#{type}", opt) : "#{opt}=:#{type}"
    "[" + val + "]"
  end.join(" ")
end

#get_usage_fields(fields) ⇒ Object

:nodoc:



316
317
318
319
# File 'lib/boson/option_parser.rb', line 316

def get_usage_fields(fields) #:nodoc:
  fields || ([:name, :alias, :type] + [:desc, :values, :keys].select {|e|
    option_attributes.values.any? {|f| f.key?(e) } }).uniq
end

#namesObject

List of option names



344
345
346
# File 'lib/boson/option_parser.rb', line 344

def names
  @opt_types.keys.map {|e| undasherize e }
end

#non_optsObject

Array of arguments left after defined options have been parsed out by parse.



106
107
108
# File 'lib/boson/option_parser.rb', line 106

def non_opts
  leading_non_opts + trailing_non_opts
end

#option_attributesObject

Hash of option names mapped to hash of its external attributes



312
313
314
# File 'lib/boson/option_parser.rb', line 312

def option_attributes
  @option_attributes || {}
end

#option_type(opt) ⇒ Object



353
354
355
356
357
358
359
# File 'lib/boson/option_parser.rb', line 353

def option_type(opt)
  if opt =~ /^--no-(\w+)$/
    @opt_types[opt] || @opt_types[dasherize($1)] || @opt_types[original_no_opt($1)]
  else
    @opt_types[opt]
  end
end

#parse(args, flags = {}) ⇒ Object

Parses an array of arguments for defined options to return an IndifferentAccessHash. Once the parser recognizes a valid option, it continues to parse until an non option argument is detected. Flags that can be passed to the parser:

  • :opts_before_args: When true options must come before arguments. Default is false.

  • :delete_invalid_opts: When true deletes any invalid options left after parsing. Will stop deleting if it comes across - or –. Default is false.



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/boson/option_parser.rb', line 228

def parse(args, flags={})
  @args = args
  # start with defaults
  hash = IndifferentAccessHash.new @defaults
  
  @leading_non_opts = []
  unless flags[:opts_before_args]
    @leading_non_opts << shift until current_is_option? || @args.empty? || STOP_STRINGS.include?(peek)
  end

  while current_is_option?
    case @original_current_option = shift
    when SHORT_SQ_RE
      unshift $1.split('').map { |f| "-#{f}" }
      next
    when EQ_RE, SHORT_NUM
      unshift $2
      option = $1
    when LONG_RE, SHORT_RE
      option = $1
    end

    dashed_option = normalize_option(option)
    @current_option = undasherize(dashed_option)
    type = option_type(dashed_option)
    validate_option_value(type)
    value = create_option_value(type)
    # set on different line since current_option may change
    hash[@current_option.to_sym] = value
  end

  @trailing_non_opts = @args
  check_required! hash
  delete_invalid_opts if flags[:delete_invalid_opts]
  hash
end

More verbose option help in the form of a table.



283
284
285
286
287
288
289
290
291
# File 'lib/boson/option_parser.rb', line 283

def print_usage_table(render_options={})
  user_fields = render_options.delete(:fields)
  fields = get_usage_fields user_fields
  (fields << :default).uniq! if render_options.delete(:local) || user_fields == '*'
  opts = all_options_with_fields fields
  fields.delete(:default) if fields.include?(:default) && opts.all? {|e| e[:default].nil? }
  render_options = default_render_options.merge(:fields=>fields).merge(render_options)
  View.render opts, render_options
end

#typesObject

List of option types



339
340
341
# File 'lib/boson/option_parser.rb', line 339

def types
  @opt_types.values
end

#undasherize(str) ⇒ Object

Removes dashes from a dashed option i.e. ‘–date’ -> ‘date’ and ‘-d’ -> ‘d’.



329
330
331
# File 'lib/boson/option_parser.rb', line 329

def undasherize(str)
  str.sub(/^-{1,2}/, '')
end