rzo 0.1.0 → 0.2.0

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,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