p4tools 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,842 @@
1
+ ## lib/trollop.rb -- trollop command-line processing library
2
+ ## Author:: William Morgan (mailto: wmorgan-trollop@masanjin.net)
3
+ ## Copyright:: Copyright 2007 William Morgan
4
+ ## License:: the same terms as ruby itself
5
+
6
+ require 'date'
7
+
8
+ module P4Tools
9
+ module Trollop
10
+
11
+ VERSION = "2.0"
12
+
13
+ ## Thrown by Parser in the event of a commandline error. Not needed if
14
+ ## you're using the Trollop::options entry.
15
+ class CommandlineError < StandardError;
16
+ end
17
+
18
+ ## Thrown by Parser if the user passes in '-h' or '--help'. Handled
19
+ ## automatically by Trollop#options.
20
+ class HelpNeeded < StandardError;
21
+ end
22
+
23
+ ## Thrown by Parser if the user passes in '-h' or '--version'. Handled
24
+ ## automatically by Trollop#options.
25
+ class VersionNeeded < StandardError;
26
+ end
27
+
28
+ ## Regex for floating point numbers
29
+ FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
30
+
31
+ ## Regex for parameters
32
+ PARAM_RE = /^-(-|\.$|[^\d\.])/
33
+
34
+ ## The commandline parser. In typical usage, the methods in this class
35
+ ## will be handled internally by Trollop::options. In this case, only the
36
+ ## #opt, #banner and #version, #depends, and #conflicts methods will
37
+ ## typically be called.
38
+ ##
39
+ ## If you want to instantiate this class yourself (for more complicated
40
+ ## argument-parsing logic), call #parse to actually produce the output hash,
41
+ ## and consider calling it from within
42
+ ## Trollop::with_standard_exception_handling.
43
+ class Parser
44
+
45
+ ## The set of values that indicate a flag option when passed as the
46
+ ## +:type+ parameter of #opt.
47
+ FLAG_TYPES = [:flag, :bool, :boolean]
48
+
49
+ ## The set of values that indicate a single-parameter (normal) option when
50
+ ## passed as the +:type+ parameter of #opt.
51
+ ##
52
+ ## A value of +io+ corresponds to a readable IO resource, including
53
+ ## a filename, URI, or the strings 'stdin' or '-'.
54
+ SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io, :date]
55
+
56
+ ## The set of values that indicate a multiple-parameter option (i.e., that
57
+ ## takes multiple space-separated values on the commandline) when passed as
58
+ ## the +:type+ parameter of #opt.
59
+ MULTI_ARG_TYPES = [:ints, :integers, :strings, :doubles, :floats, :ios, :dates]
60
+
61
+ ## The complete set of legal values for the +:type+ parameter of #opt.
62
+ TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES
63
+
64
+ INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
65
+
66
+ ## The values from the commandline that were not interpreted by #parse.
67
+ attr_reader :leftovers
68
+
69
+ ## The complete configuration hashes for each option. (Mainly useful
70
+ ## for testing.)
71
+ attr_reader :specs
72
+
73
+ ## Initializes the parser, and instance-evaluates any block given.
74
+ def initialize *a, &b
75
+ @version = nil
76
+ @leftovers = []
77
+ @specs = {}
78
+ @long = {}
79
+ @short = {}
80
+ @order = []
81
+ @constraints = []
82
+ @stop_words = []
83
+ @stop_on_unknown = false
84
+
85
+ #instance_eval(&b) if b # can't take arguments
86
+ cloaker(&b).bind(self).call(*a) if b
87
+ end
88
+
89
+ ## Define an option. +name+ is the option name, a unique identifier
90
+ ## for the option that you will use internally, which should be a
91
+ ## symbol or a string. +desc+ is a string description which will be
92
+ ## displayed in help messages.
93
+ ##
94
+ ## Takes the following optional arguments:
95
+ ##
96
+ ## [+: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.
97
+ ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+.
98
+ ## [+:type+] Require that the argument take a parameter or parameters of type +type+. For a single parameter, the value can be a member of +SINGLE_ARG_TYPES+, or a corresponding Ruby class (e.g. +Integer+ for +:int+). For multiple-argument parameters, the value can be any member of +MULTI_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.
99
+ ## [+:default+] Set the default value for an argument. Without a default value, the hash returned by #parse (and thus Trollop::options) will have a +nil+ 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 to +true+, then if it is specified on the the commandline the value will be +false+.
100
+ ## [+:required+] If set to +true+, the argument must be provided on the commandline.
101
+ ## [+: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.)
102
+ ##
103
+ ## Note that there are two types of argument multiplicity: an argument
104
+ ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
105
+ ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2".
106
+ ##
107
+ ## Arguments that take multiple values should have a +:type+ parameter
108
+ ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+
109
+ ## value of an array of the correct type (e.g. [String]). The
110
+ ## value of this argument will be an array of the parameters on the
111
+ ## commandline.
112
+ ##
113
+ ## Arguments that can occur multiple times should be marked with
114
+ ## +:multi+ => +true+. The value of this argument will also be an array.
115
+ ## In contrast with regular non-multi options, if not specified on
116
+ ## the commandline, the default value will be [], not nil.
117
+ ##
118
+ ## These two attributes can be combined (e.g. +:type+ => +:strings+,
119
+ ## +:multi+ => +true+), in which case the value of the argument will be
120
+ ## an array of arrays.
121
+ ##
122
+ ## There's one ambiguous case to be aware of: when +:multi+: is true and a
123
+ ## +:default+ is set to an array (of something), it's ambiguous whether this
124
+ ## is a multi-value argument as well as a multi-occurrence argument.
125
+ ## In thise case, Trollop assumes that it's not a multi-value argument.
126
+ ## If you want a multi-value, multi-occurrence argument with a default
127
+ ## value, you must specify +:type+ as well.
128
+
129
+ def opt name, desc="", opts={}
130
+ raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? name
131
+
132
+ ## fill in :type
133
+ opts[:type] = # normalize
134
+ case opts[:type]
135
+ when :boolean, :bool;
136
+ :flag
137
+ when :integer;
138
+ :int
139
+ when :integers;
140
+ :ints
141
+ when :double;
142
+ :float
143
+ when :doubles;
144
+ :floats
145
+ when Class
146
+ case opts[:type].name
147
+ when 'TrueClass', 'FalseClass';
148
+ :flag
149
+ when 'String';
150
+ :string
151
+ when 'Integer';
152
+ :int
153
+ when 'Float';
154
+ :float
155
+ when 'IO';
156
+ :io
157
+ when 'Date';
158
+ :date
159
+ else
160
+ raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
161
+ end
162
+ when nil;
163
+ nil
164
+ else
165
+ raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
166
+ opts[:type]
167
+ end
168
+
169
+ ## for options with :multi => true, an array default doesn't imply
170
+ ## a multi-valued argument. for that you have to specify a :type
171
+ ## as well. (this is how we disambiguate an ambiguous situation;
172
+ ## see the docs for Parser#opt for details.)
173
+ disambiguated_default = if opts[:multi] && opts[:default].is_a?(Array) && !opts[:type]
174
+ opts[:default].first
175
+ else
176
+ opts[:default]
177
+ end
178
+
179
+ type_from_default =
180
+ case disambiguated_default
181
+ when Integer;
182
+ :int
183
+ when Numeric;
184
+ :float
185
+ when TrueClass, FalseClass;
186
+ :flag
187
+ when String;
188
+ :string
189
+ when IO;
190
+ :io
191
+ when Date;
192
+ :date
193
+ when Array
194
+ if opts[:default].empty?
195
+ raise ArgumentError, "multiple argument type cannot be deduced from an empty array for '#{opts[:default][0].class.name}'"
196
+ end
197
+ case opts[:default][0] # the first element determines the types
198
+ when Integer;
199
+ :ints
200
+ when Numeric;
201
+ :floats
202
+ when String;
203
+ :strings
204
+ when IO;
205
+ :ios
206
+ when Date;
207
+ :dates
208
+ else
209
+ raise ArgumentError, "unsupported multiple argument type '#{opts[:default][0].class.name}'"
210
+ end
211
+ when nil;
212
+ nil
213
+ else
214
+ raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
215
+ end
216
+
217
+ raise ArgumentError, ":type specification and default type don't match (default type is #{type_from_default})" if opts[:type] && type_from_default && opts[:type] != type_from_default
218
+
219
+ opts[:type] = opts[:type] || type_from_default || :flag
220
+
221
+ ## fill in :long
222
+ opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-")
223
+ opts[:long] = case opts[:long]
224
+ when /^--([^-].*)$/;
225
+ $1
226
+ when /^[^-]/;
227
+ opts[:long]
228
+ else
229
+ ; raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
230
+ end
231
+ raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]]
232
+
233
+ ## fill in :short
234
+ opts[:short] = opts[:short].to_s if opts[:short] unless opts[:short] == :none
235
+ opts[:short] = case opts[:short]
236
+ when /^-(.)$/;
237
+ $1
238
+ when nil, :none, /^.$/;
239
+ opts[:short]
240
+ else
241
+ raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'"
242
+ end
243
+
244
+ if opts[:short]
245
+ raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]]
246
+ raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ INVALID_SHORT_ARG_REGEX
247
+ end
248
+
249
+ ## fill in :default for flags
250
+ opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
251
+
252
+ ## autobox :default for :multi (multi-occurrence) arguments
253
+ opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].is_a?(Array)
254
+
255
+ ## fill in :multi
256
+ opts[:multi] ||= false
257
+
258
+ opts[:desc] ||= desc
259
+ @long[opts[:long]] = name
260
+ @short[opts[:short]] = name if opts[:short] && opts[:short] != :none
261
+ @specs[name] = opts
262
+ @order << [:opt, name]
263
+ end
264
+
265
+ ## Sets the version string. If set, the user can request the version
266
+ ## on the commandline. Should probably be of the form "<program name>
267
+ ## <version number>".
268
+ def version s=nil;
269
+ @version = s if s; @version
270
+ end
271
+
272
+ ## Adds text to the help display. Can be interspersed with calls to
273
+ ## #opt to build a multi-section help page.
274
+ def banner s;
275
+ @order << [:text, s]
276
+ end
277
+
278
+ alias :text :banner
279
+
280
+ ## Marks two (or more!) options as requiring each other. Only handles
281
+ ## undirected (i.e., mutual) dependencies. Directed dependencies are
282
+ ## better modeled with Trollop::die.
283
+ def depends *syms
284
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
285
+ @constraints << [:depends, syms]
286
+ end
287
+
288
+ ## Marks two (or more!) options as conflicting.
289
+ def conflicts *syms
290
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
291
+ @constraints << [:conflicts, syms]
292
+ end
293
+
294
+ ## Defines a set of words which cause parsing to terminate when
295
+ ## encountered, such that any options to the left of the word are
296
+ ## parsed as usual, and options to the right of the word are left
297
+ ## intact.
298
+ ##
299
+ ## A typical use case would be for subcommand support, where these
300
+ ## would be set to the list of subcommands. A subsequent Trollop
301
+ ## invocation would then be used to parse subcommand options, after
302
+ ## shifting the subcommand off of ARGV.
303
+ def stop_on *words
304
+ @stop_words = [*words].flatten
305
+ end
306
+
307
+ ## Similar to #stop_on, but stops on any unknown word when encountered
308
+ ## (unless it is a parameter for an argument). This is useful for
309
+ ## cases where you don't know the set of subcommands ahead of time,
310
+ ## i.e., without first parsing the global options.
311
+ def stop_on_unknown
312
+ @stop_on_unknown = true
313
+ end
314
+
315
+ ## Parses the commandline. Typically called by Trollop::options,
316
+ ## but you can call it directly if you need more control.
317
+ ##
318
+ ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions.
319
+ def parse cmdline=ARGV
320
+ vals = {}
321
+ required = {}
322
+
323
+ opt :version, "Print version and exit" if @version unless @specs[:version] || @long["version"]
324
+ opt :help, "Show this message" unless @specs[:help] || @long["help"]
325
+
326
+ @specs.each do |sym, opts|
327
+ required[sym] = true if opts[:required]
328
+ vals[sym] = opts[:default]
329
+ vals[sym] = [] if opts[:multi] && !opts[:default] # multi arguments default to [], not nil
330
+ end
331
+
332
+ resolve_default_short_options!
333
+
334
+ ## resolve symbols
335
+ given_args = {}
336
+ @leftovers = each_arg cmdline do |arg, params|
337
+ ## handle --no- forms
338
+ arg, negative_given = if arg =~ /^--no-([^-]\S*)$/
339
+ ["--#{$1}", true]
340
+ else
341
+ [arg, false]
342
+ end
343
+
344
+ sym = case arg
345
+ when /^-([^-])$/;
346
+ @short[$1]
347
+ when /^--([^-]\S*)$/;
348
+ @long[$1] || @long["no-#{$1}"]
349
+ else
350
+ ; raise CommandlineError, "invalid argument syntax: '#{arg}'"
351
+ end
352
+
353
+ sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments
354
+
355
+ raise CommandlineError, "unknown argument '#{arg}'" unless sym
356
+
357
+ if given_args.include?(sym) && !@specs[sym][:multi]
358
+ raise CommandlineError, "option '#{arg}' specified multiple times"
359
+ end
360
+
361
+ given_args[sym] ||= {}
362
+ given_args[sym][:arg] = arg
363
+ given_args[sym][:negative_given] = negative_given
364
+ given_args[sym][:params] ||= []
365
+
366
+ # The block returns the number of parameters taken.
367
+ num_params_taken = 0
368
+
369
+ unless params.nil?
370
+ if SINGLE_ARG_TYPES.include?(@specs[sym][:type])
371
+ given_args[sym][:params] << params[0, 1] # take the first parameter
372
+ num_params_taken = 1
373
+ elsif MULTI_ARG_TYPES.include?(@specs[sym][:type])
374
+ given_args[sym][:params] << params # take all the parameters
375
+ num_params_taken = params.size
376
+ end
377
+ end
378
+
379
+ num_params_taken
380
+ end
381
+
382
+ ## check for version and help args
383
+ raise VersionNeeded if given_args.include? :version
384
+ raise HelpNeeded if given_args.include? :help
385
+
386
+ ## check constraint satisfaction
387
+ @constraints.each do |type, syms|
388
+ constraint_sym = syms.find { |sym| given_args[sym] }
389
+ next unless constraint_sym
390
+
391
+ case type
392
+ when :depends
393
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} requires --#{@specs[sym][:long]}" unless given_args.include? sym }
394
+ when :conflicts
395
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} conflicts with --#{@specs[sym][:long]}" if given_args.include?(sym) && (sym != constraint_sym) }
396
+ end
397
+ end
398
+
399
+ required.each do |sym, val|
400
+ raise CommandlineError, "option --#{@specs[sym][:long]} must be specified" unless given_args.include? sym
401
+ end
402
+
403
+ ## parse parameters
404
+ given_args.each do |sym, given_data|
405
+ arg, params, negative_given = given_data.values_at :arg, :params, :negative_given
406
+
407
+ opts = @specs[sym]
408
+ raise CommandlineError, "option '#{arg}' needs a parameter" if params.empty? && opts[:type] != :flag
409
+
410
+ vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
411
+
412
+ case opts[:type]
413
+ when :flag
414
+ vals[sym] = (sym.to_s =~ /^no_/ ? negative_given : !negative_given)
415
+ when :int, :ints
416
+ vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } }
417
+ when :float, :floats
418
+ vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } }
419
+ when :string, :strings
420
+ vals[sym] = params.map { |pg| pg.map { |p| p.to_s } }
421
+ when :io, :ios
422
+ vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } }
423
+ when :date, :dates
424
+ vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } }
425
+ end
426
+
427
+ if SINGLE_ARG_TYPES.include?(opts[:type])
428
+ unless opts[:multi] # single parameter
429
+ vals[sym] = vals[sym][0][0]
430
+ else # multiple options, each with a single parameter
431
+ vals[sym] = vals[sym].map { |p| p[0] }
432
+ end
433
+ elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi]
434
+ vals[sym] = vals[sym][0] # single option, with multiple parameters
435
+ end
436
+ # else: multiple options, with multiple parameters
437
+ end
438
+
439
+ ## modify input in place with only those
440
+ ## arguments we didn't process
441
+ cmdline.clear
442
+ @leftovers.each { |l| cmdline << l }
443
+
444
+ ## allow openstruct-style accessors
445
+ class << vals
446
+ def method_missing(m, *args)
447
+ self[m] || self[m.to_s]
448
+ end
449
+ end
450
+ vals
451
+ end
452
+
453
+ def parse_date_parameter param, arg #:nodoc:
454
+ begin
455
+ begin
456
+ time = Chronic.parse(param)
457
+ rescue NameError
458
+ # chronic is not available
459
+ end
460
+ time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
461
+ rescue ArgumentError
462
+ raise CommandlineError, "option '#{arg}' needs a date"
463
+ end
464
+ end
465
+
466
+ ## Print the help message to +stream+.
467
+ def educate stream=$stdout
468
+ width # hack: calculate it now; otherwise we have to be careful not to
469
+ # call this unless the cursor's at the beginning of a line.
470
+ stream.puts "\n"
471
+ left = {}
472
+ @specs.each do |name, spec|
473
+ left[name] = "--#{spec[:long]}" +
474
+ (spec[:type] == :flag && spec[:default] ? ", --no-#{spec[:long]}" : "") +
475
+ (spec[:short] && spec[:short] != :none ? ", -#{spec[:short]}" : "") +
476
+ case spec[:type]
477
+ when :flag;
478
+ ""
479
+ when :int;
480
+ " <i>"
481
+ when :ints;
482
+ " <i+>"
483
+ when :string;
484
+ " <s>"
485
+ when :strings;
486
+ " <s+>"
487
+ when :float;
488
+ " <f>"
489
+ when :floats;
490
+ " <f+>"
491
+ when :io;
492
+ " <filename/uri>"
493
+ when :ios;
494
+ " <filename/uri+>"
495
+ when :date;
496
+ " <date>"
497
+ when :dates;
498
+ " <date+>"
499
+ end
500
+ end
501
+
502
+ leftcol_width = left.values.map { |s| s.length }.max || 0
503
+ rightcol_start = leftcol_width + 6 # spaces
504
+
505
+ unless @order.size > 0 && @order.first.first == :text
506
+ stream.puts "#@version\n" if @version
507
+ stream.puts "Options:"
508
+ end
509
+
510
+ @order.each do |what, opt|
511
+ if what == :text
512
+ stream.puts wrap(opt)
513
+ next
514
+ end
515
+
516
+ spec = @specs[opt]
517
+ stream.printf "%s\n ", left[opt]
518
+ desc = spec[:desc] + begin
519
+ default_s = case spec[:default]
520
+ when $stdout;
521
+ "<stdout>"
522
+ when $stdin;
523
+ "<stdin>"
524
+ when $stderr;
525
+ "<stderr>"
526
+ when Array
527
+ spec[:default].join(", ")
528
+ else
529
+ spec[:default].to_s
530
+ end
531
+
532
+ if spec[:default]
533
+ if spec[:desc] =~ /\.$/
534
+ " (Default: #{default_s})"
535
+ else
536
+ " (default: #{default_s})"
537
+ end
538
+ else
539
+ ""
540
+ end
541
+ end
542
+ stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
543
+ end
544
+ end
545
+
546
+ def width #:nodoc:
547
+ if (ENV['COLUMNS'] =~ /^\d+$/) && (ENV['LINES'] =~ /^\d+$/)
548
+ @width = ENV['COLUMNS'].to_i
549
+ elsif (RUBY_PLATFORM =~ /java/ || (!STDIN.tty? && ENV['TERM'])) && command_exists?('tput')
550
+ @width = `tput cols`.to_i
551
+ elsif STDIN.tty? && command_exists?('stty')
552
+ @width = `stty size`.scan(/\d+/).map { |s| s.to_i }.reverse.first
553
+ else
554
+ @width = 150
555
+ end
556
+ rescue
557
+ @width = 150
558
+ end
559
+
560
+ def wrap str, opts={} # :nodoc:
561
+ if str == ""
562
+ [""]
563
+ else
564
+ str.split("\n").map { |s| wrap_line s, opts }.flatten
565
+ end
566
+ end
567
+
568
+ ## The per-parser version of Trollop::die (see that for documentation).
569
+ def die arg, msg
570
+ if msg
571
+ $stderr.puts "Error: argument --#{@specs[arg][:long]} #{msg}."
572
+ else
573
+ $stderr.puts "Error: #{arg}."
574
+ end
575
+ $stderr.puts "Try --help for help."
576
+ exit(-1)
577
+ end
578
+
579
+ private
580
+
581
+ ## yield successive arg, parameter pairs
582
+ def each_arg args
583
+ remains = []
584
+ i = 0
585
+
586
+ until i >= args.length
587
+ if @stop_words.member? args[i]
588
+ remains += args[i .. -1]
589
+ return remains
590
+ end
591
+ case args[i]
592
+ when /^--$/ # arg terminator
593
+ remains += args[(i + 1) .. -1]
594
+ return remains
595
+ when /^--(\S+?)=(.*)$/ # long argument with equals
596
+ yield "--#{$1}", [$2]
597
+ i += 1
598
+ when /^--(\S+)$/ # long argument
599
+ params = collect_argument_parameters(args, i + 1)
600
+ unless params.empty?
601
+ num_params_taken = yield args[i], params
602
+ unless num_params_taken
603
+ if @stop_on_unknown
604
+ remains += args[i + 1 .. -1]
605
+ return remains
606
+ else
607
+ remains += params
608
+ end
609
+ end
610
+ i += 1 + num_params_taken
611
+ else # long argument no parameter
612
+ yield args[i], nil
613
+ i += 1
614
+ end
615
+ when /^-(\S+)$/ # one or more short arguments
616
+ shortargs = $1.split(//)
617
+ shortargs.each_with_index do |a, j|
618
+ if j == (shortargs.length - 1)
619
+ params = collect_argument_parameters(args, i + 1)
620
+ unless params.empty?
621
+ num_params_taken = yield "-#{a}", params
622
+ unless num_params_taken
623
+ if @stop_on_unknown
624
+ remains += args[i + 1 .. -1]
625
+ return remains
626
+ else
627
+ remains += params
628
+ end
629
+ end
630
+ i += 1 + num_params_taken
631
+ else # argument no parameter
632
+ yield "-#{a}", nil
633
+ i += 1
634
+ end
635
+ else
636
+ yield "-#{a}", nil
637
+ end
638
+ end
639
+ else
640
+ if @stop_on_unknown
641
+ remains += args[i .. -1]
642
+ return remains
643
+ else
644
+ remains << args[i]
645
+ i += 1
646
+ end
647
+ end
648
+ end
649
+
650
+ remains
651
+ end
652
+
653
+ def parse_integer_parameter param, arg
654
+ raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^\d+$/
655
+ param.to_i
656
+ end
657
+
658
+ def parse_float_parameter param, arg
659
+ raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
660
+ param.to_f
661
+ end
662
+
663
+ def parse_io_parameter param, arg
664
+ case param
665
+ when /^(stdin|-)$/i;
666
+ $stdin
667
+ else
668
+ require 'open-uri'
669
+ begin
670
+ open param
671
+ rescue SystemCallError => e
672
+ raise CommandlineError, "file or url for option '#{arg}' cannot be opened: #{e.message}"
673
+ end
674
+ end
675
+ end
676
+
677
+ def collect_argument_parameters args, start_at
678
+ params = []
679
+ pos = start_at
680
+ while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do
681
+ params << args[pos]
682
+ pos += 1
683
+ end
684
+ params
685
+ end
686
+
687
+ def resolve_default_short_options!
688
+ @order.each do |type, name|
689
+ next unless type == :opt
690
+ opts = @specs[name]
691
+ next if opts[:short]
692
+
693
+ c = opts[:long].split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
694
+ if c # found a character to use
695
+ opts[:short] = c
696
+ @short[c] = name
697
+ end
698
+ end
699
+ end
700
+
701
+ def wrap_line str, opts={}
702
+ prefix = opts[:prefix] || 0
703
+ width = opts[:width] || (self.width - 1)
704
+ start = 0
705
+ ret = []
706
+ until start > str.length
707
+ nextt =
708
+ if start + width >= str.length
709
+ str.length
710
+ else
711
+ x = str.rindex(/\s/, start + width)
712
+ x = str.index(/\s/, start) if x && x < start
713
+ x || str.length
714
+ end
715
+ ret << (ret.empty? ? "" : " " * prefix) + str[start ... nextt]
716
+ start = nextt + 1
717
+ end
718
+ ret
719
+ end
720
+
721
+ ## instance_eval but with ability to handle block arguments
722
+ ## thanks to _why: http://redhanded.hobix.com/inspect/aBlockCostume.html
723
+ def cloaker &b
724
+ (
725
+ class << self;
726
+ self;
727
+ end).class_eval do
728
+ define_method :cloaker_, &b
729
+ meth = instance_method :cloaker_
730
+ remove_method :cloaker_
731
+ meth
732
+ end
733
+ end
734
+ end
735
+
736
+ ## The easy, syntactic-sugary entry method into Trollop. Creates a Parser,
737
+ ## passes the block to it, then parses +args+ with it, handling any errors or
738
+ ## requests for help or version information appropriately (and then exiting).
739
+ ## Modifies +args+ in place. Returns a hash of option values.
740
+ ##
741
+ ## The block passed in should contain zero or more calls to +opt+
742
+ ## (Parser#opt), zero or more calls to +text+ (Parser#text), and
743
+ ## probably a call to +version+ (Parser#version).
744
+ ##
745
+ ## The returned block contains a value for every option specified with
746
+ ## +opt+. The value will be the value given on the commandline, or the
747
+ ## default value if the option was not specified on the commandline. For
748
+ ## every option specified on the commandline, a key "<option
749
+ ## name>_given" will also be set in the hash.
750
+ ##
751
+ ## Example:
752
+ ##
753
+ ## require 'trollop'
754
+ ## opts = Trollop::options do
755
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
756
+ ## opt :name, "Monkey name", :type => :string # a string --name <s>, defaulting to nil
757
+ ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4
758
+ ## end
759
+ ##
760
+ ## ## if called with no arguments
761
+ ## p opts # => {:monkey=>false, :name=>nil, :num_limbs=>4, :help=>false}
762
+ ##
763
+ ## ## if called with --monkey
764
+ ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
765
+ ##
766
+ ## See more examples at http://trollop.rubyforge.org.
767
+ def options args=ARGV, *a, &b
768
+ @last_parser = Parser.new(*a, &b)
769
+ with_standard_exception_handling(@last_parser) { @last_parser.parse args }
770
+ end
771
+
772
+ ## If Trollop::options doesn't do quite what you want, you can create a Parser
773
+ ## object and call Parser#parse on it. That method will throw CommandlineError,
774
+ ## HelpNeeded and VersionNeeded exceptions when necessary; if you want to
775
+ ## have these handled for you in the standard manner (e.g. show the help
776
+ ## and then exit upon an HelpNeeded exception), call your code from within
777
+ ## a block passed to this method.
778
+ ##
779
+ ## Note that this method will call System#exit after handling an exception!
780
+ ##
781
+ ## Usage example:
782
+ ##
783
+ ## require 'trollop'
784
+ ## p = Trollop::Parser.new do
785
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
786
+ ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true
787
+ ## end
788
+ ##
789
+ ## opts = Trollop::with_standard_exception_handling p do
790
+ ## o = p.parse ARGV
791
+ ## raise Trollop::HelpNeeded if ARGV.empty? # show help screen
792
+ ## o
793
+ ## end
794
+ ##
795
+ ## Requires passing in the parser object.
796
+
797
+ def with_standard_exception_handling parser
798
+ begin
799
+ yield
800
+ rescue CommandlineError => e
801
+ $stderr.puts "Error: #{e.message}."
802
+ $stderr.puts "Try --help for help."
803
+ exit(-1)
804
+ rescue HelpNeeded
805
+ parser.educate
806
+ exit
807
+ rescue VersionNeeded
808
+ puts parser.version
809
+ exit
810
+ end
811
+ end
812
+
813
+ ## Informs the user that their usage of 'arg' was wrong, as detailed by
814
+ ## 'msg', and dies. Example:
815
+ ##
816
+ ## options do
817
+ ## opt :volume, :default => 0.0
818
+ ## end
819
+ ##
820
+ ## die :volume, "too loud" if opts[:volume] > 10.0
821
+ ## die :volume, "too soft" if opts[:volume] < 0.1
822
+ ##
823
+ ## In the one-argument case, simply print that message, a notice
824
+ ## about -h, and die. Example:
825
+ ##
826
+ ## options do
827
+ ## opt :whatever # ...
828
+ ## end
829
+ ##
830
+ ## Trollop::die "need at least one filename" if ARGV.empty?
831
+ def die arg, msg=nil
832
+ if @last_parser
833
+ @last_parser.die arg, msg
834
+ else
835
+ raise ArgumentError, "Trollop::die can only be called after Trollop::options"
836
+ end
837
+ end
838
+
839
+ module_function :options, :die, :with_standard_exception_handling
840
+
841
+ end
842
+ end