rzo 0.1.0 → 0.2.0

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