class Puppet::Util::CommandLine::Trollop::Parser
The commandline parser. In typical usage, the methods in this class will be handled internally by Trollop::options
. In this case, only the opt
, banner
and version
, depends
, and conflicts
methods will typically be called.
If you want to instantiate this class yourself (for more complicated argument-parsing logic), call parse
to actually produce the output hash, and consider calling it from within Trollop::with_standard_exception_handling
.
Constants
- FLAG_TYPES
The set of values that indicate a flag option when passed as the
:type
parameter ofopt
.- MULTI_ARG_TYPES
The set of values that indicate a multiple-parameter option (i.e., that takes multiple space-separated values on the commandline) when passed as the
:type
parameter ofopt
.- SINGLE_ARG_TYPES
The set of values that indicate a single-parameter (normal) option when passed as the
:type
parameter ofopt
.A value of
io
corresponds to a readable IO resource, including a filename, URI, or the strings 'stdin' or '-'.- TYPES
The complete set of legal values for the
:type
parameter ofopt
.
Attributes
A flag that determines whether or not to attempt to automatically generate “short” options if they are not
explicitly specified.
A flag indicating whether or not the parser should attempt to handle “–help” and
"--version" specially. If 'false', it will treat them just like any other option.
A flag that determines whether or not to raise an error if the parser is passed one or more
options that were not registered ahead of time. If 'true', then the parser will simply ignore options that it does not recognize.
The values from the commandline that were not interpreted by parse
.
The complete configuration hashes for each option. (Mainly useful for testing.)
Public Class Methods
Initializes the parser, and instance-evaluates any block given.
# File lib/puppet/util/command_line/trollop.rb 93 def initialize *a, &b 94 @version = nil 95 @leftovers = [] 96 @specs = {} 97 @long = {} 98 @short = {} 99 @order = [] 100 @constraints = [] 101 @stop_words = [] 102 @stop_on_unknown = false 103 104 #instance_eval(&b) if b # can't take arguments 105 cloaker(&b).bind(self).call(*a) if b 106 end
Public Instance Methods
Marks two (or more!) options as conflicting.
# File lib/puppet/util/command_line/trollop.rb 278 def conflicts *syms 279 syms.each { |sym| raise ArgumentError, _("unknown option '%{sym}'") % { sym: sym } unless @specs[sym] } 280 @constraints << [:conflicts, syms] 281 end
Marks two (or more!) options as requiring each other. Only handles undirected (i.e., mutual) dependencies. Directed dependencies are better modeled with Trollop::die
.
# File lib/puppet/util/command_line/trollop.rb 272 def depends *syms 273 syms.each { |sym| raise ArgumentError, _("unknown option '%{sym}'") % { sym: sym } unless @specs[sym] } 274 @constraints << [:depends, syms] 275 end
The per-parser version of Trollop::die
(see that for documentation).
# File lib/puppet/util/command_line/trollop.rb 552 def die arg, msg 553 if msg 554 $stderr.puts _("Error: argument --%{value0} %{msg}.") % { value0: @specs[arg][:long], msg: msg } 555 else 556 $stderr.puts _("Error: %{arg}.") % { arg: arg } 557 end 558 $stderr.puts _("Try --help for help.") 559 exit(-1) 560 end
Print the help message to stream
.
# File lib/puppet/util/command_line/trollop.rb 463 def educate stream=$stdout 464 width # just calculate it now; otherwise we have to be careful not to 465 # call this unless the cursor's at the beginning of a line. 466 467 left = {} 468 @specs.each do |name, spec| 469 left[name] = "--#{spec[:long]}" + 470 (spec[:short] && spec[:short] != :none ? ", -#{spec[:short]}" : "") + 471 case spec[:type] 472 when :flag; "" 473 when :int; " <i>" 474 when :ints; " <i+>" 475 when :string; " <s>" 476 when :strings; " <s+>" 477 when :float; " <f>" 478 when :floats; " <f+>" 479 when :io; " <filename/uri>" 480 when :ios; " <filename/uri+>" 481 when :date; " <date>" 482 when :dates; " <date+>" 483 end 484 end 485 486 leftcol_width = left.values.map { |s| s.length }.max || 0 487 rightcol_start = leftcol_width + 6 # spaces 488 489 unless @order.size > 0 && @order.first.first == :text 490 stream.puts "#@version\n" if @version 491 stream.puts _("Options:") 492 end 493 494 @order.each do |what, opt| 495 if what == :text 496 stream.puts wrap(opt) 497 next 498 end 499 500 spec = @specs[opt] 501 stream.printf " %#{leftcol_width}s: ", left[opt] 502 desc = spec[:desc] + begin 503 default_s = case spec[:default] 504 when $stdout; "<stdout>" 505 when $stdin; "<stdin>" 506 when $stderr; "<stderr>" 507 when Array 508 spec[:default].join(", ") 509 else 510 spec[:default].to_s 511 end 512 513 if spec[:default] 514 if spec[:desc] =~ /\.$/ 515 _(" (Default: %{default_s})") % { default_s: default_s } 516 else 517 _(" (default: %{default_s})") % { default_s: default_s } 518 end 519 else 520 "" 521 end 522 end 523 stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start) 524 end 525 end
Define an option. name
is the option name, a unique identifier for the option that you will use internally, which should be a symbol or a string. desc
is a string description which will be displayed in help messages.
Takes the following optional arguments:
:long
-
Specify the long form of the argument, i.e. the form with two dashes. If unspecified, will be automatically derived based on the argument name by turning the
name
option into a string, and replacing any _'s by -'s. :short
-
Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from
name
. :type
-
Require that the argument take a parameter or parameters of type
type
. For a single parameter, the value can be a member ofSINGLE_ARG_TYPES
, or a corresponding Ruby class (e.g.Integer
for:int
). For multiple-argument parameters, the value can be any member ofMULTI_ARG_TYPES
constant. If unset, the default argument type is:flag
, meaning that the argument does not take a parameter. The specification of:type
is not necessary if a:default
is given. :default
-
Set the default value for an argument. Without a default value, the hash returned by
parse
(and thusTrollop::options
) will have anil
value for this key unless the argument is given on the commandline. The argument type is derived automatically from the class of the default value given, so specifying a:type
is not necessary if a:default
is given. (But see below for an important caveat when:multi
: is specified too.) If the argument is a flag, and the default is set totrue
, then if it is specified on the commandline the value will befalse
. :required
-
If set to
true
, the argument must be provided on the commandline. :multi
-
If set to
true
, allows multiple occurrences of the option on the commandline. Otherwise, only a single instance of the option is allowed. (Note that this is different from taking multiple parameters. See below.)
Note that there are two types of argument multiplicity: an argument can take multiple values, e.g. “–arg 1 2 3”. An argument can also be allowed to occur multiple times, e.g. “–arg 1 –arg 2”.
Arguments that take multiple values should have a :type
parameter drawn from MULTI_ARG_TYPES
(e.g. :strings
), or a :default:
value of an array of the correct type (e.g. [String]). The value of this argument will be an array of the parameters on the commandline.
Arguments that can occur multiple times should be marked with :multi
=> true
. The value of this argument will also be an array. In contrast with regular non-multi options, if not specified on the commandline, the default value will be [], not nil.
These two attributes can be combined (e.g. :type
=> :strings
, :multi
=> true
), in which case the value of the argument will be an array of arrays.
There's one ambiguous case to be aware of: when :multi
: is true and a :default
is set to an array (of something), it's ambiguous whether this is a multi-value argument as well as a multi-occurrence argument. In this case, Trollop
assumes that it's not a multi-value argument. If you want a multi-value, multi-occurrence argument with a default value, you must specify :type
as well.
# File lib/puppet/util/command_line/trollop.rb 148 def opt name, desc="", opts={} 149 raise ArgumentError, _("you already have an argument named '%{name}'") % { name: name } if @specs.member? name 150 151 ## fill in :type 152 opts[:type] = # normalize 153 case opts[:type] 154 when :boolean, :bool; :flag 155 when :integer; :int 156 when :integers; :ints 157 when :double; :float 158 when :doubles; :floats 159 when Class 160 case opts[:type].name 161 when 'TrueClass', 'FalseClass'; :flag 162 when 'String'; :string 163 when 'Integer'; :int 164 when 'Float'; :float 165 when 'IO'; :io 166 when 'Date'; :date 167 else 168 raise ArgumentError, _("unsupported argument type '%{type}'") % { type: opts[:type].class.name } 169 end 170 when nil; nil 171 else 172 raise ArgumentError, _("unsupported argument type '%{type}'") % { type: opts[:type] } unless TYPES.include?(opts[:type]) 173 opts[:type] 174 end 175 176 ## for options with :multi => true, an array default doesn't imply 177 ## a multi-valued argument. for that you have to specify a :type 178 ## as well. (this is how we disambiguate an ambiguous situation; 179 ## see the docs for Parser#opt for details.) 180 disambiguated_default = 181 if opts[:multi] && opts[:default].is_a?(Array) && !opts[:type] 182 opts[:default].first 183 else 184 opts[:default] 185 end 186 187 type_from_default = 188 case disambiguated_default 189 when Integer; :int 190 when Numeric; :float 191 when TrueClass, FalseClass; :flag 192 when String; :string 193 when IO; :io 194 when Date; :date 195 when Array 196 if opts[:default].empty? 197 raise ArgumentError, _("multiple argument type cannot be deduced from an empty array for '%{value0}'") % { value0: opts[:default][0].class.name } 198 end 199 case opts[:default][0] # the first element determines the types 200 when Integer; :ints 201 when Numeric; :floats 202 when String; :strings 203 when IO; :ios 204 when Date; :dates 205 else 206 raise ArgumentError, _("unsupported multiple argument type '%{value0}'") % { value0: opts[:default][0].class.name } 207 end 208 when nil; nil 209 else 210 raise ArgumentError, _("unsupported argument type '%{value0}'") % { value0: opts[:default].class.name } 211 end 212 213 raise ArgumentError, _(":type specification and default type don't match (default type is %{type_from_default})") % { type_from_default: type_from_default } if opts[:type] && type_from_default && opts[:type] != type_from_default 214 215 opts[:type] = opts[:type] || type_from_default || :flag 216 217 ## fill in :long 218 opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.tr("_", "-") 219 opts[:long] = 220 case opts[:long] 221 when /^--([^-].*)$/ 222 $1 223 when /^[^-]/ 224 opts[:long] 225 else 226 raise ArgumentError, _("invalid long option name %{name}") % { name: opts[:long].inspect } 227 end 228 raise ArgumentError, _("long option name %{value0} is already taken; please specify a (different) :long") % { value0: opts[:long].inspect } if @long[opts[:long]] 229 230 ## fill in :short 231 opts[:short] = opts[:short].to_s if opts[:short] unless opts[:short] == :none 232 opts[:short] = case opts[:short] 233 when /^-(.)$/; $1 234 when nil, :none, /^.$/; opts[:short] 235 else raise ArgumentError, _("invalid short option name '%{name}'") % { name: opts[:short].inspect } 236 end 237 238 if opts[:short] 239 raise ArgumentError, _("short option name %{value0} is already taken; please specify a (different) :short") % { value0: opts[:short].inspect } if @short[opts[:short]] 240 raise ArgumentError, _("a short option name can't be a number or a dash") if opts[:short] =~ INVALID_SHORT_ARG_REGEX 241 end 242 243 ## fill in :default for flags 244 opts[:default] = false if opts[:type] == :flag && opts[:default].nil? 245 246 ## autobox :default for :multi (multi-occurrence) arguments 247 opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].is_a?(Array) 248 249 ## fill in :multi 250 opts[:multi] ||= false 251 252 opts[:desc] ||= desc 253 @long[opts[:long]] = name 254 @short[opts[:short]] = name if opts[:short] && opts[:short] != :none 255 @specs[name] = opts 256 @order << [:opt, name] 257 end
Parses the commandline. Typically called by Trollop::options
, but you can call it directly if you need more control.
throws CommandlineError
, HelpNeeded
, and VersionNeeded
exceptions.
# File lib/puppet/util/command_line/trollop.rb 308 def parse cmdline=ARGV 309 vals = {} 310 required = {} 311 312 if handle_help_and_version 313 opt :version, _("Print version and exit") if @version unless @specs[:version] || @long["version"] 314 opt :help, _("Show this message") unless @specs[:help] || @long["help"] 315 end 316 317 @specs.each do |sym, opts| 318 required[sym] = true if opts[:required] 319 vals[sym] = opts[:default] 320 vals[sym] = [] if opts[:multi] && !opts[:default] # multi arguments default to [], not nil 321 end 322 323 resolve_default_short_options if create_default_short_options 324 325 ## resolve symbols 326 given_args = {} 327 @leftovers = each_arg cmdline do |arg, params| 328 sym = case arg 329 when /^-([^-])$/ 330 @short[$1] 331 when /^--no-([^-]\S*)$/ 332 @long["[no-]#{$1}"] 333 when /^--([^-]\S*)$/ 334 @long[$1] ? @long[$1] : @long["[no-]#{$1}"] 335 else 336 raise CommandlineError, _("invalid argument syntax: '%{arg}'") % { arg: arg } 337 end 338 339 unless sym 340 next 0 if ignore_invalid_options 341 raise CommandlineError, _("unknown argument '%{arg}'") % { arg: arg } unless sym 342 end 343 344 if given_args.include?(sym) && !@specs[sym][:multi] 345 raise CommandlineError, _("option '%{arg}' specified multiple times") % { arg: arg } 346 end 347 348 given_args[sym] ||= {} 349 350 given_args[sym][:arg] = arg 351 given_args[sym][:params] ||= [] 352 353 # The block returns the number of parameters taken. 354 num_params_taken = 0 355 356 unless params.nil? 357 if SINGLE_ARG_TYPES.include?(@specs[sym][:type]) 358 given_args[sym][:params] << params[0, 1] # take the first parameter 359 num_params_taken = 1 360 elsif MULTI_ARG_TYPES.include?(@specs[sym][:type]) 361 given_args[sym][:params] << params # take all the parameters 362 num_params_taken = params.size 363 end 364 end 365 366 num_params_taken 367 end 368 369 if handle_help_and_version 370 ## check for version and help args 371 raise VersionNeeded if given_args.include? :version 372 raise HelpNeeded if given_args.include? :help 373 end 374 375 ## check constraint satisfaction 376 @constraints.each do |type, syms| 377 constraint_sym = syms.find { |sym| given_args[sym] } 378 next unless constraint_sym 379 380 case type 381 when :depends 382 syms.each { |sym| raise CommandlineError, _("--%{value0} requires --%{value1}") % { value0: @specs[constraint_sym][:long], value1: @specs[sym][:long] } unless given_args.include? sym } 383 when :conflicts 384 syms.each { |sym| raise CommandlineError, _("--%{value0} conflicts with --%{value1}") % { value0: @specs[constraint_sym][:long], value1: @specs[sym][:long] } if given_args.include?(sym) && (sym != constraint_sym) } 385 end 386 end 387 388 required.each do |sym, val| 389 raise CommandlineError, _("option --%{opt} must be specified") % { opt: @specs[sym][:long] } unless given_args.include? sym 390 end 391 392 ## parse parameters 393 given_args.each do |sym, given_data| 394 arg = given_data[:arg] 395 params = given_data[:params] 396 397 opts = @specs[sym] 398 raise CommandlineError, _("option '%{arg}' needs a parameter") % { arg: arg } if params.empty? && opts[:type] != :flag 399 400 vals["#{sym}_given".intern] = true # mark argument as specified on the commandline 401 402 case opts[:type] 403 when :flag 404 if arg =~ /^--no-/ and sym.to_s =~ /^--\[no-\]/ 405 vals[sym] = opts[:default] 406 else 407 vals[sym] = !opts[:default] 408 end 409 when :int, :ints 410 vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } } 411 when :float, :floats 412 vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } } 413 when :string, :strings 414 vals[sym] = params.map { |pg| pg.map { |p| p.to_s } } 415 when :io, :ios 416 vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } } 417 when :date, :dates 418 vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } } 419 end 420 421 if SINGLE_ARG_TYPES.include?(opts[:type]) 422 unless opts[:multi] # single parameter 423 vals[sym] = vals[sym][0][0] 424 else # multiple options, each with a single parameter 425 vals[sym] = vals[sym].map { |p| p[0] } 426 end 427 elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi] 428 vals[sym] = vals[sym][0] # single option, with multiple parameters 429 end 430 # else: multiple options, with multiple parameters 431 432 opts[:callback].call(vals[sym]) if opts.has_key?(:callback) 433 end 434 435 ## modify input in place with only those 436 ## arguments we didn't process 437 cmdline.clear 438 @leftovers.each { |l| cmdline << l } 439 440 ## allow openstruct-style accessors 441 class << vals 442 def method_missing(m, *args) 443 self[m] || self[m.to_s] 444 end 445 end 446 vals 447 end
Defines a set of words which cause parsing to terminate when encountered, such that any options to the left of the word are parsed as usual, and options to the right of the word are left intact.
A typical use case would be for subcommand support, where these would be set to the list of subcommands. A subsequent Trollop
invocation would then be used to parse subcommand options, after shifting the subcommand off of ARGV.
# File lib/puppet/util/command_line/trollop.rb 292 def stop_on *words 293 @stop_words = [*words].flatten 294 end
Similar to stop_on
, but stops on any unknown word when encountered (unless it is a parameter for an argument). This is useful for cases where you don't know the set of subcommands ahead of time, i.e., without first parsing the global options.
# File lib/puppet/util/command_line/trollop.rb 300 def stop_on_unknown 301 @stop_on_unknown = true 302 end
Sets the version string. If set, the user can request the version on the commandline. Should probably be of the form “<program name> <version number>”.
# File lib/puppet/util/command_line/trollop.rb 262 def version s=nil; @version = s if s; @version end
Private Instance Methods
instance_eval but with ability to handle block arguments thanks to why: redhanded.hobix.com/inspect/aBlockCostume.html
# File lib/puppet/util/command_line/trollop.rb 705 def cloaker &b 706 (class << self; self; end).class_eval do 707 define_method :cloaker_, &b 708 meth = instance_method :cloaker_ 709 remove_method :cloaker_ 710 meth 711 end 712 end
# File lib/puppet/util/command_line/trollop.rb 659 def collect_argument_parameters args, start_at 660 params = [] 661 pos = start_at 662 while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do 663 params << args[pos] 664 pos += 1 665 end 666 params 667 end
yield successive arg, parameter pairs
# File lib/puppet/util/command_line/trollop.rb 565 def each_arg args 566 remains = [] 567 i = 0 568 569 until i >= args.length 570 if @stop_words.member? args[i] 571 remains += args[i .. -1] 572 return remains 573 end 574 case args[i] 575 when /^--$/ # arg terminator 576 remains += args[(i + 1) .. -1] 577 return remains 578 when /^--(\S+?)=(.*)$/ # long argument with equals 579 yield "--#{$1}", [$2] 580 i += 1 581 when /^--(\S+)$/ # long argument 582 params = collect_argument_parameters(args, i + 1) 583 unless params.empty? 584 num_params_taken = yield args[i], params 585 unless num_params_taken 586 if @stop_on_unknown 587 remains += args[i + 1 .. -1] 588 return remains 589 else 590 remains += params 591 end 592 end 593 i += 1 + num_params_taken 594 else # long argument no parameter 595 yield args[i], nil 596 i += 1 597 end 598 when /^-(\S+)$/ # one or more short arguments 599 shortargs = $1.split(//) 600 shortargs.each_with_index do |a, j| 601 if j == (shortargs.length - 1) 602 params = collect_argument_parameters(args, i + 1) 603 unless params.empty? 604 num_params_taken = yield "-#{a}", params 605 unless num_params_taken 606 if @stop_on_unknown 607 remains += args[i + 1 .. -1] 608 return remains 609 else 610 remains += params 611 end 612 end 613 i += 1 + num_params_taken 614 else # argument no parameter 615 yield "-#{a}", nil 616 i += 1 617 end 618 else 619 yield "-#{a}", nil 620 end 621 end 622 else 623 if @stop_on_unknown 624 remains += args[i .. -1] 625 return remains 626 else 627 remains << args[i] 628 i += 1 629 end 630 end 631 end 632 633 remains 634 end
# File lib/puppet/util/command_line/trollop.rb 641 def parse_float_parameter param, arg 642 raise CommandlineError, _("option '%{arg}' needs a floating-point number") % { arg: arg } unless param =~ FLOAT_RE 643 param.to_f 644 end
# File lib/puppet/util/command_line/trollop.rb 636 def parse_integer_parameter param, arg 637 raise CommandlineError, _("option '%{arg}' needs an integer") % { arg: arg } unless param =~ /^\d+$/ 638 param.to_i 639 end
# File lib/puppet/util/command_line/trollop.rb 646 def parse_io_parameter param, arg 647 case param 648 when /^(stdin|-)$/i; $stdin 649 else 650 require 'open-uri' 651 begin 652 open param 653 rescue SystemCallError => e 654 raise CommandlineError, _("file or url for option '%{arg}' cannot be opened: %{value0}") % { arg: arg, value0: e.message }, e.backtrace 655 end 656 end 657 end
# File lib/puppet/util/command_line/trollop.rb 669 def resolve_default_short_options 670 @order.each do |type, name| 671 next unless type == :opt 672 opts = @specs[name] 673 next if opts[:short] 674 675 c = opts[:long].split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) } 676 if c # found a character to use 677 opts[:short] = c 678 @short[c] = name 679 end 680 end 681 end
# File lib/puppet/util/command_line/trollop.rb 683 def wrap_line str, opts={} 684 prefix = opts[:prefix] || 0 685 width = opts[:width] || (self.width - 1) 686 start = 0 687 ret = [] 688 until start > str.length 689 nextt = 690 if start + width >= str.length 691 str.length 692 else 693 x = str.rindex(/\s/, start + width) 694 x = str.index(/\s/, start) if x && x < start 695 x || str.length 696 end 697 ret << (ret.empty? ? "" : " " * prefix) + str[start ... nextt] 698 start = nextt + 1 699 end 700 ret 701 end