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
:typeparameter 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
:typeparameter ofopt.- SINGLE_ARG_TYPES
The set of values that indicate a single-parameter (normal) option when passed as the
:typeparameter ofopt.A value of
iocorresponds to a readable IO resource, including a filename, URI, or the strings 'stdin' or '-'.- TYPES
The complete set of legal values for the
:typeparameter 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 94 def initialize *a, &b 95 @version = nil 96 @leftovers = [] 97 @specs = {} 98 @long = {} 99 @short = {} 100 @order = [] 101 @constraints = [] 102 @stop_words = [] 103 @stop_on_unknown = false 104 105 #instance_eval(&b) if b # can't take arguments 106 cloaker(&b).bind(self).call(*a) if b 107 end
Public Instance Methods
Marks two (or more!) options as conflicting.
# File lib/puppet/util/command_line/trollop.rb 279 def conflicts *syms 280 syms.each { |sym| raise ArgumentError, _("unknown option '%{sym}'") % { sym: sym } unless @specs[sym] } 281 @constraints << [:conflicts, syms] 282 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 273 def depends *syms 274 syms.each { |sym| raise ArgumentError, _("unknown option '%{sym}'") % { sym: sym } unless @specs[sym] } 275 @constraints << [:depends, syms] 276 end
The per-parser version of Trollop::die (see that for documentation).
# File lib/puppet/util/command_line/trollop.rb 553 def die arg, msg 554 if msg 555 $stderr.puts _("Error: argument --%{value0} %{msg}.") % { value0: @specs[arg][:long], msg: msg } 556 else 557 $stderr.puts _("Error: %{arg}.") % { arg: arg } 558 end 559 $stderr.puts _("Try --help for help.") 560 exit(-1) 561 end
Print the help message to stream.
# File lib/puppet/util/command_line/trollop.rb 464 def educate stream=$stdout 465 width # just calculate it now; otherwise we have to be careful not to 466 # call this unless the cursor's at the beginning of a line. 467 468 left = {} 469 @specs.each do |name, spec| 470 left[name] = "--#{spec[:long]}" + 471 (spec[:short] && spec[:short] != :none ? ", -#{spec[:short]}" : "") + 472 case spec[:type] 473 when :flag; "" 474 when :int; " <i>" 475 when :ints; " <i+>" 476 when :string; " <s>" 477 when :strings; " <s+>" 478 when :float; " <f>" 479 when :floats; " <f+>" 480 when :io; " <filename/uri>" 481 when :ios; " <filename/uri+>" 482 when :date; " <date>" 483 when :dates; " <date+>" 484 end 485 end 486 487 leftcol_width = left.values.map { |s| s.length }.max || 0 488 rightcol_start = leftcol_width + 6 # spaces 489 490 unless @order.size > 0 && @order.first.first == :text 491 stream.puts "#@version\n" if @version 492 stream.puts _("Options:") 493 end 494 495 @order.each do |what, opt| 496 if what == :text 497 stream.puts wrap(opt) 498 next 499 end 500 501 spec = @specs[opt] 502 stream.printf " %#{leftcol_width}s: ", left[opt] 503 desc = spec[:desc] + begin 504 default_s = case spec[:default] 505 when $stdout; "<stdout>" 506 when $stdin; "<stdin>" 507 when $stderr; "<stderr>" 508 when Array 509 spec[:default].join(", ") 510 else 511 spec[:default].to_s 512 end 513 514 if spec[:default] 515 if spec[:desc] =~ /\.$/ 516 _(" (Default: %{default_s})") % { default_s: default_s } 517 else 518 _(" (default: %{default_s})") % { default_s: default_s } 519 end 520 else 521 "" 522 end 523 end 524 stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start) 525 end 526 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
nameoption 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.Integerfor:int). For multiple-argument parameters, the value can be any member ofMULTI_ARG_TYPESconstant. If unset, the default argument type is:flag, meaning that the argument does not take a parameter. The specification of:typeis not necessary if a:defaultis given. :default-
Set the default value for an argument. Without a default value, the hash returned by
parse(and thusTrollop::options) will have anilvalue 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:typeis not necessary if a:defaultis 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 149 def opt name, desc="", opts={} 150 raise ArgumentError, _("you already have an argument named '%{name}'") % { name: name } if @specs.member? name 151 152 ## fill in :type 153 opts[:type] = # normalize 154 case opts[:type] 155 when :boolean, :bool; :flag 156 when :integer; :int 157 when :integers; :ints 158 when :double; :float 159 when :doubles; :floats 160 when Class 161 case opts[:type].name 162 when 'TrueClass', 'FalseClass'; :flag 163 when 'String'; :string 164 when 'Integer'; :int 165 when 'Float'; :float 166 when 'IO'; :io 167 when 'Date'; :date 168 else 169 raise ArgumentError, _("unsupported argument type '%{type}'") % { type: opts[:type].class.name } 170 end 171 when nil; nil 172 else 173 raise ArgumentError, _("unsupported argument type '%{type}'") % { type: opts[:type] } unless TYPES.include?(opts[:type]) 174 opts[:type] 175 end 176 177 ## for options with :multi => true, an array default doesn't imply 178 ## a multi-valued argument. for that you have to specify a :type 179 ## as well. (this is how we disambiguate an ambiguous situation; 180 ## see the docs for Parser#opt for details.) 181 disambiguated_default = 182 if opts[:multi] && opts[:default].is_a?(Array) && !opts[:type] 183 opts[:default].first 184 else 185 opts[:default] 186 end 187 188 type_from_default = 189 case disambiguated_default 190 when Integer; :int 191 when Numeric; :float 192 when TrueClass, FalseClass; :flag 193 when String; :string 194 when IO; :io 195 when Date; :date 196 when Array 197 if opts[:default].empty? 198 raise ArgumentError, _("multiple argument type cannot be deduced from an empty array for '%{value0}'") % { value0: opts[:default][0].class.name } 199 end 200 case opts[:default][0] # the first element determines the types 201 when Integer; :ints 202 when Numeric; :floats 203 when String; :strings 204 when IO; :ios 205 when Date; :dates 206 else 207 raise ArgumentError, _("unsupported multiple argument type '%{value0}'") % { value0: opts[:default][0].class.name } 208 end 209 when nil; nil 210 else 211 raise ArgumentError, _("unsupported argument type '%{value0}'") % { value0: opts[:default].class.name } 212 end 213 214 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 215 216 opts[:type] = opts[:type] || type_from_default || :flag 217 218 ## fill in :long 219 opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.tr("_", "-") 220 opts[:long] = 221 case opts[:long] 222 when /^--([^-].*)$/ 223 $1 224 when /^[^-]/ 225 opts[:long] 226 else 227 raise ArgumentError, _("invalid long option name %{name}") % { name: opts[:long].inspect } 228 end 229 raise ArgumentError, _("long option name %{value0} is already taken; please specify a (different) :long") % { value0: opts[:long].inspect } if @long[opts[:long]] 230 231 ## fill in :short 232 opts[:short] = opts[:short].to_s if opts[:short] unless opts[:short] == :none 233 opts[:short] = case opts[:short] 234 when /^-(.)$/; $1 235 when nil, :none, /^.$/; opts[:short] 236 else raise ArgumentError, _("invalid short option name '%{name}'") % { name: opts[:short].inspect } 237 end 238 239 if opts[:short] 240 raise ArgumentError, _("short option name %{value0} is already taken; please specify a (different) :short") % { value0: opts[:short].inspect } if @short[opts[:short]] 241 raise ArgumentError, _("a short option name can't be a number or a dash") if opts[:short] =~ INVALID_SHORT_ARG_REGEX 242 end 243 244 ## fill in :default for flags 245 opts[:default] = false if opts[:type] == :flag && opts[:default].nil? 246 247 ## autobox :default for :multi (multi-occurrence) arguments 248 opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].is_a?(Array) 249 250 ## fill in :multi 251 opts[:multi] ||= false 252 253 opts[:desc] ||= desc 254 @long[opts[:long]] = name 255 @short[opts[:short]] = name if opts[:short] && opts[:short] != :none 256 @specs[name] = opts 257 @order << [:opt, name] 258 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 309 def parse cmdline=ARGV 310 vals = {} 311 required = {} 312 313 if handle_help_and_version 314 opt :version, _("Print version and exit") if @version unless @specs[:version] || @long["version"] 315 opt :help, _("Show this message") unless @specs[:help] || @long["help"] 316 end 317 318 @specs.each do |sym, opts| 319 required[sym] = true if opts[:required] 320 vals[sym] = opts[:default] 321 vals[sym] = [] if opts[:multi] && !opts[:default] # multi arguments default to [], not nil 322 end 323 324 resolve_default_short_options if create_default_short_options 325 326 ## resolve symbols 327 given_args = {} 328 @leftovers = each_arg cmdline do |arg, params| 329 sym = case arg 330 when /^-([^-])$/ 331 @short[$1] 332 when /^--no-([^-]\S*)$/ 333 @long["[no-]#{$1}"] 334 when /^--([^-]\S*)$/ 335 @long[$1] ? @long[$1] : @long["[no-]#{$1}"] 336 else 337 raise CommandlineError, _("invalid argument syntax: '%{arg}'") % { arg: arg } 338 end 339 340 unless sym 341 next 0 if ignore_invalid_options 342 raise CommandlineError, _("unknown argument '%{arg}'") % { arg: arg } unless sym 343 end 344 345 if given_args.include?(sym) && !@specs[sym][:multi] 346 raise CommandlineError, _("option '%{arg}' specified multiple times") % { arg: arg } 347 end 348 349 given_args[sym] ||= {} 350 351 given_args[sym][:arg] = arg 352 given_args[sym][:params] ||= [] 353 354 # The block returns the number of parameters taken. 355 num_params_taken = 0 356 357 unless params.nil? 358 if SINGLE_ARG_TYPES.include?(@specs[sym][:type]) 359 given_args[sym][:params] << params[0, 1] # take the first parameter 360 num_params_taken = 1 361 elsif MULTI_ARG_TYPES.include?(@specs[sym][:type]) 362 given_args[sym][:params] << params # take all the parameters 363 num_params_taken = params.size 364 end 365 end 366 367 num_params_taken 368 end 369 370 if handle_help_and_version 371 ## check for version and help args 372 raise VersionNeeded if given_args.include? :version 373 raise HelpNeeded if given_args.include? :help 374 end 375 376 ## check constraint satisfaction 377 @constraints.each do |type, syms| 378 constraint_sym = syms.find { |sym| given_args[sym] } 379 next unless constraint_sym 380 381 case type 382 when :depends 383 syms.each { |sym| raise CommandlineError, _("--%{value0} requires --%{value1}") % { value0: @specs[constraint_sym][:long], value1: @specs[sym][:long] } unless given_args.include? sym } 384 when :conflicts 385 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) } 386 end 387 end 388 389 required.each do |sym, val| 390 raise CommandlineError, _("option --%{opt} must be specified") % { opt: @specs[sym][:long] } unless given_args.include? sym 391 end 392 393 ## parse parameters 394 given_args.each do |sym, given_data| 395 arg = given_data[:arg] 396 params = given_data[:params] 397 398 opts = @specs[sym] 399 raise CommandlineError, _("option '%{arg}' needs a parameter") % { arg: arg } if params.empty? && opts[:type] != :flag 400 401 vals["#{sym}_given".intern] = true # mark argument as specified on the commandline 402 403 case opts[:type] 404 when :flag 405 if arg =~ /^--no-/ and sym.to_s =~ /^--\[no-\]/ 406 vals[sym] = opts[:default] 407 else 408 vals[sym] = !opts[:default] 409 end 410 when :int, :ints 411 vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } } 412 when :float, :floats 413 vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } } 414 when :string, :strings 415 vals[sym] = params.map { |pg| pg.map { |p| p.to_s } } 416 when :io, :ios 417 vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } } 418 when :date, :dates 419 vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } } 420 end 421 422 if SINGLE_ARG_TYPES.include?(opts[:type]) 423 unless opts[:multi] # single parameter 424 vals[sym] = vals[sym][0][0] 425 else # multiple options, each with a single parameter 426 vals[sym] = vals[sym].map { |p| p[0] } 427 end 428 elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi] 429 vals[sym] = vals[sym][0] # single option, with multiple parameters 430 end 431 # else: multiple options, with multiple parameters 432 433 opts[:callback].call(vals[sym]) if opts.has_key?(:callback) 434 end 435 436 ## modify input in place with only those 437 ## arguments we didn't process 438 cmdline.clear 439 @leftovers.each { |l| cmdline << l } 440 441 ## allow openstruct-style accessors 442 class << vals 443 def method_missing(m, *args) 444 self[m] || self[m.to_s] 445 end 446 end 447 vals 448 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 293 def stop_on *words 294 @stop_words = [*words].flatten 295 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 301 def stop_on_unknown 302 @stop_on_unknown = true 303 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 263 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 706 def cloaker &b 707 (class << self; self; end).class_eval do 708 define_method :cloaker_, &b 709 meth = instance_method :cloaker_ 710 remove_method :cloaker_ 711 meth 712 end 713 end
# File lib/puppet/util/command_line/trollop.rb 660 def collect_argument_parameters args, start_at 661 params = [] 662 pos = start_at 663 while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do 664 params << args[pos] 665 pos += 1 666 end 667 params 668 end
yield successive arg, parameter pairs
# File lib/puppet/util/command_line/trollop.rb 566 def each_arg args 567 remains = [] 568 i = 0 569 570 until i >= args.length 571 if @stop_words.member? args[i] 572 remains += args[i .. -1] 573 return remains 574 end 575 case args[i] 576 when /^--$/ # arg terminator 577 remains += args[(i + 1) .. -1] 578 return remains 579 when /^--(\S+?)=(.*)$/ # long argument with equals 580 yield "--#{$1}", [$2] 581 i += 1 582 when /^--(\S+)$/ # long argument 583 params = collect_argument_parameters(args, i + 1) 584 unless params.empty? 585 num_params_taken = yield args[i], params 586 unless num_params_taken 587 if @stop_on_unknown 588 remains += args[i + 1 .. -1] 589 return remains 590 else 591 remains += params 592 end 593 end 594 i += 1 + num_params_taken 595 else # long argument no parameter 596 yield args[i], nil 597 i += 1 598 end 599 when /^-(\S+)$/ # one or more short arguments 600 shortargs = $1.split(//) 601 shortargs.each_with_index do |a, j| 602 if j == (shortargs.length - 1) 603 params = collect_argument_parameters(args, i + 1) 604 unless params.empty? 605 num_params_taken = yield "-#{a}", params 606 unless num_params_taken 607 if @stop_on_unknown 608 remains += args[i + 1 .. -1] 609 return remains 610 else 611 remains += params 612 end 613 end 614 i += 1 + num_params_taken 615 else # argument no parameter 616 yield "-#{a}", nil 617 i += 1 618 end 619 else 620 yield "-#{a}", nil 621 end 622 end 623 else 624 if @stop_on_unknown 625 remains += args[i .. -1] 626 return remains 627 else 628 remains << args[i] 629 i += 1 630 end 631 end 632 end 633 634 remains 635 end
# File lib/puppet/util/command_line/trollop.rb 642 def parse_float_parameter param, arg 643 raise CommandlineError, _("option '%{arg}' needs a floating-point number") % { arg: arg } unless param =~ FLOAT_RE 644 param.to_f 645 end
# File lib/puppet/util/command_line/trollop.rb 637 def parse_integer_parameter param, arg 638 raise CommandlineError, _("option '%{arg}' needs an integer") % { arg: arg } unless param =~ /^\d+$/ 639 param.to_i 640 end
# File lib/puppet/util/command_line/trollop.rb 647 def parse_io_parameter param, arg 648 case param 649 when /^(stdin|-)$/i; $stdin 650 else 651 require 'open-uri' 652 begin 653 open param 654 rescue SystemCallError => e 655 raise CommandlineError, _("file or url for option '%{arg}' cannot be opened: %{value0}") % { arg: arg, value0: e.message }, e.backtrace 656 end 657 end 658 end
# File lib/puppet/util/command_line/trollop.rb 670 def resolve_default_short_options 671 @order.each do |type, name| 672 next unless type == :opt 673 opts = @specs[name] 674 next if opts[:short] 675 676 c = opts[:long].split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) } 677 if c # found a character to use 678 opts[:short] = c 679 @short[c] = name 680 end 681 end 682 end
# File lib/puppet/util/command_line/trollop.rb 684 def wrap_line str, opts={} 685 prefix = opts[:prefix] || 0 686 width = opts[:width] || (self.width - 1) 687 start = 0 688 ret = [] 689 until start > str.length 690 nextt = 691 if start + width >= str.length 692 str.length 693 else 694 x = str.rindex(/\s/, start + width) 695 x = str.index(/\s/, start) if x && x < start 696 x || str.length 697 end 698 ret << (ret.empty? ? "" : " " * prefix) + str[start ... nextt] 699 start = nextt + 1 700 end 701 ret 702 end