recheck 0.0.1 → 0.5.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,1361 @@
1
+ # frozen_string_literal: true
2
+
3
+ # optimist 3.2.0 3a075357657fc62325e918734599f1d6aa58d35c
4
+ # This file is vendored with 3 changes:
5
+ # 1. Move into Recheck module to avoid collisions if included in a
6
+ # project that already uses Optimist. Code references to Optimist:: changed
7
+ # to Recheck::Optimist::
8
+ # 2. Parser.parse calls .split() on strings instead of silently failing
9
+ # 3. Parser.parse does not mutate input. (??) Leftovers go to {:_leftovers}
10
+ # Optimist will not permit 'opt :_leftovers' so there's no collision.
11
+
12
+ # lib/optimist.rb -- optimist command-line processing library
13
+ # Copyright (c) 2008-2014 William Morgan.
14
+ # Copyright (c) 2014 Red Hat, Inc.
15
+ # optimist is licensed under the MIT license.
16
+
17
+ require "date"
18
+
19
+ module Recheck
20
+ module Optimist # change 1, see top of file
21
+ VERSION = "3.2.0"
22
+
23
+ ## Thrown by Parser in the event of a commandline error. Not needed if
24
+ ## you're using the Optimist::options entry.
25
+ class CommandlineError < StandardError
26
+ attr_reader :error_code
27
+
28
+ def initialize(msg, error_code = nil)
29
+ super(msg)
30
+ @error_code = error_code
31
+ end
32
+ end
33
+
34
+ ## Thrown by Parser if the user passes in '-h' or '--help'. Handled
35
+ ## automatically by Optimist#options.
36
+ class HelpNeeded < StandardError
37
+ end
38
+
39
+ ## Thrown by Parser if the user passes in '-v' or '--version'. Handled
40
+ ## automatically by Optimist#options.
41
+ class VersionNeeded < StandardError
42
+ end
43
+
44
+ ## Regex for floating point numbers
45
+ FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
46
+
47
+ ## Regex for parameters
48
+ PARAM_RE = /^-(-|\.$|[^\d\.])/
49
+
50
+ # Abstract class for a constraint. Do not use by itself.
51
+ class Constraint
52
+ def initialize(syms)
53
+ @idents = syms
54
+ end
55
+
56
+ def validate(given_args:, specs:)
57
+ overlap = @idents & given_args.keys
58
+ if error_condition(overlap.size)
59
+ longargs = @idents.map { |sym| "--#{specs[sym].long.long}" }
60
+ raise CommandlineError, error_message(longargs)
61
+ end
62
+ end
63
+ end
64
+
65
+ # A Dependency constraint. Useful when Option A requires Option B also be used.
66
+ class DependConstraint < Constraint
67
+ def error_condition(overlap_size)
68
+ (overlap_size != 0) && (overlap_size != @idents.size)
69
+ end
70
+
71
+ def error_message(longargs)
72
+ "#{longargs.join(", ")} have a dependency and must be given together"
73
+ end
74
+ end
75
+
76
+ # A Conflict constraint. Useful when Option A cannot be used with Option B.
77
+ class ConflictConstraint < Constraint
78
+ def error_condition(overlap_size)
79
+ (overlap_size != 0) && (overlap_size != 1)
80
+ end
81
+
82
+ def error_message(longargs)
83
+ "only one of #{longargs.join(", ")} can be given"
84
+ end
85
+ end
86
+
87
+ # An Either-Or constraint. For Mutually exclusive options
88
+ class EitherConstraint < Constraint
89
+ def error_condition(overlap_size)
90
+ overlap_size != 1
91
+ end
92
+
93
+ def error_message(longargs)
94
+ "one and only one of #{longargs.join(", ")} is required"
95
+ end
96
+ end
97
+
98
+ ## The commandline parser. In typical usage, the methods in this class
99
+ ## will be handled internally by Optimist::options. In this case, only the
100
+ ## #opt, #banner and #version, #depends, and #conflicts methods will
101
+ ## typically be called.
102
+ ##
103
+ ## If you want to instantiate this class yourself (for more complicated
104
+ ## argument-parsing logic), call #parse to actually produce the output hash,
105
+ ## and consider calling it from within
106
+ ## Optimist::with_standard_exception_handling.
107
+ class Parser
108
+ ## The registry is a class-instance-variable map of option aliases to their subclassed Option class.
109
+ @registry = {}
110
+
111
+ ## The Option subclasses are responsible for registering themselves using this function.
112
+ def self.register(lookup, klass)
113
+ @registry[lookup.to_sym] = klass
114
+ end
115
+
116
+ ## Gets the class from the registry.
117
+ ## Can be given either a class-name, e.g. Integer, a string, e.g "integer", or a symbol, e.g :integer
118
+ def self.registry_getopttype(type)
119
+ return nil unless type
120
+ if type.respond_to?(:name)
121
+ type = type.name
122
+ lookup = type.downcase.to_sym
123
+ else
124
+ lookup = type.to_sym
125
+ end
126
+ raise ArgumentError, "Unsupported argument type '#{type}', registry lookup '#{lookup}'" unless @registry.has_key?(lookup)
127
+ @registry[lookup].new
128
+ end
129
+
130
+ ## The values from the commandline that were not interpreted by #parse.
131
+ attr_reader :leftovers
132
+
133
+ ## The complete configuration hashes for each option. (Mainly useful
134
+ ## for testing.)
135
+ attr_reader :specs
136
+
137
+ ## A flag that determines whether or not to raise an error if the parser is passed one or more
138
+ ## options that were not registered ahead of time. If 'true', then the parser will simply
139
+ ## ignore options that it does not recognize.
140
+ attr_accessor :ignore_invalid_options
141
+
142
+ DEFAULT_SETTINGS = {
143
+ exact_match: true,
144
+ implicit_short_opts: true,
145
+ suggestions: true
146
+ }
147
+
148
+ ## Initializes the parser, and instance-evaluates any block given.
149
+ def initialize(*a, &b)
150
+ @version = nil
151
+ @leftovers = []
152
+ @specs = {}
153
+ @long = {}
154
+ @short = {}
155
+ @order = []
156
+ @constraints = []
157
+ @stop_words = []
158
+ @stop_on_unknown = false
159
+ @educate_on_error = false
160
+ @synopsis = nil
161
+ @usage = nil
162
+
163
+ ## allow passing settings through Parser.new as an optional hash.
164
+ ## but keep compatibility with non-hashy args, though.
165
+ begin
166
+ settings_hash = Hash[*a]
167
+ @settings = DEFAULT_SETTINGS.merge(settings_hash)
168
+ a = [] ## clear out args if using as settings-hash
169
+ rescue ArgumentError
170
+ @settings = DEFAULT_SETTINGS
171
+ end
172
+
173
+ instance_exec(*a, &b) if block_given?
174
+ end
175
+
176
+ ## Define an option. +name+ is the option name, a unique identifier
177
+ ## for the option that you will use internally, which should be a
178
+ ## symbol or a string. +desc+ is a string description which will be
179
+ ## displayed in help messages.
180
+ ##
181
+ ## Takes the following optional arguments:
182
+ ##
183
+ ## [+: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.
184
+ ## [+: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.
185
+ ## [+: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.
186
+ ## [+:default+] Set the default value for an argument. Without a default value, the hash returned by #parse (and thus Optimist::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+.
187
+ ## [+:required+] If set to +true+, the argument must be provided on the commandline.
188
+ ## [+: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.)
189
+ ## [+:permitted+] Specify an Array of permitted values for an option. If the user provides a value outside this list, an error is thrown.
190
+ ##
191
+ ## Note that there are two types of argument multiplicity: an argument
192
+ ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
193
+ ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2".
194
+ ##
195
+ ## Arguments that take multiple values should have a +:type+ parameter
196
+ ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+
197
+ ## value of an array of the correct type (e.g. [String]). The
198
+ ## value of this argument will be an array of the parameters on the
199
+ ## commandline.
200
+ ##
201
+ ## Arguments that can occur multiple times should be marked with
202
+ ## +:multi+ => +true+. The value of this argument will also be an array.
203
+ ## In contrast with regular non-multi options, if not specified on
204
+ ## the commandline, the default value will be [], not nil.
205
+ ##
206
+ ## These two attributes can be combined (e.g. +:type+ => +:strings+,
207
+ ## +:multi+ => +true+), in which case the value of the argument will be
208
+ ## an array of arrays.
209
+ ##
210
+ ## There's one ambiguous case to be aware of: when +:multi+: is true and a
211
+ ## +:default+ is set to an array (of something), it's ambiguous whether this
212
+ ## is a multi-value argument as well as a multi-occurrence argument.
213
+ ## In thise case, Optimist assumes that it's not a multi-value argument.
214
+ ## If you want a multi-value, multi-occurrence argument with a default
215
+ ## value, you must specify +:type+ as well.
216
+
217
+ def opt(name, desc = "", opts = {}, &b)
218
+ opts[:callback] ||= b if block_given?
219
+ opts[:desc] ||= desc
220
+
221
+ o = Option.create(name, desc, opts)
222
+
223
+ raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? o.name
224
+
225
+ o.long.names.each do |lng|
226
+ raise ArgumentError, "long option name #{lng.inspect} is already taken; please specify a (different) :long/:alt" if @long[lng]
227
+ @long[lng] = o.name
228
+ end
229
+
230
+ o.short.chars.each do |short|
231
+ raise ArgumentError, "short option name #{short.inspect} is already taken; please specify a (different) :short" if @short[short]
232
+ @short[short] = o.name
233
+ end
234
+
235
+ raise ArgumentError, "permitted values for option #{o.long.long.inspect} must be either nil, Range, Regexp or an Array;" unless o.permitted_type_valid?
236
+
237
+ @specs[o.name] = o
238
+ @order << [:opt, o.name]
239
+ end
240
+
241
+ ## Sets the version string. If set, the user can request the version
242
+ ## on the commandline. Should probably be of the form "<program name>
243
+ ## <version number>".
244
+ def version(s = nil)
245
+ s ? @version = s : @version
246
+ end
247
+
248
+ ## Sets the usage string. If set the message will be printed as the
249
+ ## first line in the help (educate) output and ending in two new
250
+ ## lines.
251
+ def usage(s = nil)
252
+ s ? @usage = s : @usage
253
+ end
254
+
255
+ ## Adds a synopsis (command summary description) right below the
256
+ ## usage line, or as the first line if usage isn't specified.
257
+ def synopsis(s = nil)
258
+ s ? @synopsis = s : @synopsis
259
+ end
260
+
261
+ ## Adds text to the help display. Can be interspersed with calls to
262
+ ## #opt to build a multi-section help page.
263
+ def banner(s)
264
+ @order << [:text, s]
265
+ end
266
+ alias_method :text, :banner
267
+
268
+ ## Marks two (or more!) options as requiring each other. Only handles
269
+ ## undirected (i.e., mutual) dependencies. Directed dependencies are
270
+ ## better modeled with Optimist::die.
271
+ def depends(*syms)
272
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
273
+ @constraints << DependConstraint.new(syms)
274
+ end
275
+
276
+ ## Marks two (or more!) options as conflicting.
277
+ def conflicts(*syms)
278
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
279
+ @constraints << ConflictConstraint.new(syms)
280
+ end
281
+
282
+ ## Marks two (or more!) options as required but mutually exclusive.
283
+ def either(*syms)
284
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
285
+ @constraints << EitherConstraint.new(syms)
286
+ end
287
+
288
+ ## Defines a set of words which cause parsing to terminate when
289
+ ## encountered, such that any options to the left of the word are
290
+ ## parsed as usual, and options to the right of the word are left
291
+ ## intact.
292
+ ##
293
+ ## A typical use case would be for subcommand support, where these
294
+ ## would be set to the list of subcommands. A subsequent Optimist
295
+ ## invocation would then be used to parse subcommand options, after
296
+ ## shifting the subcommand off of ARGV.
297
+ def stop_on(*words)
298
+ @stop_words = [*words].flatten
299
+ end
300
+
301
+ ## Similar to #stop_on, but stops on any unknown word when encountered
302
+ ## (unless it is a parameter for an argument). This is useful for
303
+ ## cases where you don't know the set of subcommands ahead of time,
304
+ ## i.e., without first parsing the global options.
305
+ def stop_on_unknown
306
+ @stop_on_unknown = true
307
+ end
308
+
309
+ ## Instead of displaying "Try --help for help." on an error
310
+ ## display the usage (via educate)
311
+ def educate_on_error
312
+ @educate_on_error = true
313
+ end
314
+
315
+ ## Match long variables with inexact match.
316
+ ## If we hit a complete match, then use that, otherwise see how many long-options partially match.
317
+ ## If only one partially matches, then we can safely use that.
318
+ ## Otherwise, we raise an error that the partially given option was ambiguous.
319
+ def perform_inexact_match(arg, partial_match) # :nodoc:
320
+ return @long[partial_match] if @long.has_key?(partial_match)
321
+ partially_matched_keys = @long.keys.select { |opt| opt.start_with?(partial_match) }
322
+ case partially_matched_keys.size
323
+ when 0 then nil
324
+ when 1 then @long[partially_matched_keys.first]
325
+ else; raise CommandlineError, "ambiguous option '#{arg}' matched keys (#{partially_matched_keys.join(",")})"
326
+ end
327
+ end
328
+ private :perform_inexact_match
329
+
330
+ def handle_unknown_argument(arg, candidates, suggestions)
331
+ errstring = "unknown argument '#{arg}'"
332
+ if suggestions &&
333
+ Module.const_defined?(:DidYouMean) &&
334
+ Module.const_defined?("DidYouMean::JaroWinkler") &&
335
+ Module.const_defined?("DidYouMean::Levenshtein")
336
+ input = arg.sub(/^[-]*/, "")
337
+
338
+ # Code borrowed from did_you_mean gem
339
+ jw_threshold = 0.75
340
+ seed = candidates.select { |candidate| DidYouMean::JaroWinkler.distance(candidate, input) >= jw_threshold }
341
+ .sort_by! { |candidate| DidYouMean::JaroWinkler.distance(candidate.to_s, input) }
342
+ .reverse!
343
+ # Correct mistypes
344
+ threshold = (input.length * 0.25).ceil
345
+ has_mistype = seed.rindex { |c| DidYouMean::Levenshtein.distance(c, input) <= threshold }
346
+ corrections = if has_mistype
347
+ seed.take(has_mistype + 1)
348
+ else
349
+ # Correct misspells
350
+ seed.select do |candidate|
351
+ length = (input.length < candidate.length) ? input.length : candidate.length
352
+
353
+ DidYouMean::Levenshtein.distance(candidate, input) < length
354
+ end.first(1)
355
+ end
356
+ unless corrections.empty?
357
+ dashdash_corrections = corrections.map { |s| "--#{s}" }
358
+ errstring += ". Did you mean: [#{dashdash_corrections.join(", ")}] ?"
359
+ end
360
+ end
361
+ raise CommandlineError, errstring
362
+ end
363
+ private :handle_unknown_argument
364
+
365
+ ## Parses the commandline. Typically called by Optimist::options,
366
+ ## but you can call it directly if you need more control.
367
+ ##
368
+ ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions.
369
+ def parse(cmdline = ARGV)
370
+ cmdline = cmdline.split if cmdline.is_a? String # change 2, see top of file
371
+ vals = {}
372
+ required = {}
373
+
374
+ opt :version, "Print version and exit" if @version && !(@specs[:version] || @long["version"])
375
+ opt :help, "Show this message" unless @specs[:help] || @long["help"]
376
+
377
+ @specs.each do |sym, opts|
378
+ required[sym] = true if opts.required?
379
+ vals[sym] = opts.default
380
+ vals[sym] = [] if opts.multi && !opts.default # multi arguments default to [], not nil
381
+ end
382
+
383
+ resolve_default_short_options! if @settings[:implicit_short_opts]
384
+
385
+ ## resolve symbols
386
+ given_args = {}
387
+ @leftovers = each_arg cmdline do |arg, params|
388
+ ## handle --no- forms
389
+ arg, negative_given = if arg =~ /^--no-([^-]\S*)$/
390
+ ["--#{$1}", true]
391
+ else
392
+ [arg, false]
393
+ end
394
+
395
+ sym = case arg
396
+ when /^-([^-])$/ then @short[$1]
397
+ when /^--([^-]\S*)$/ then @long[$1] || @long["no-#{$1}"]
398
+ else raise CommandlineError, "invalid argument syntax: '#{arg}'"
399
+ end
400
+
401
+ if arg.start_with?("--no-") # explicitly invalidate --no-no- arguments
402
+ sym = nil
403
+ ## Support inexact matching of long-arguments like perl's Getopt::Long
404
+ elsif !sym && !@settings[:exact_match] && arg.match(/^--(\S+)$/)
405
+ sym = perform_inexact_match(arg, $1)
406
+ end
407
+
408
+ next nil if ignore_invalid_options && !sym
409
+ handle_unknown_argument(arg, @long.keys, @settings[:suggestions]) unless sym
410
+
411
+ if given_args.include?(sym) && !@specs[sym].multi?
412
+ raise CommandlineError, "option '#{arg}' specified multiple times"
413
+ end
414
+
415
+ given_args[sym] ||= {}
416
+ given_args[sym][:arg] = arg
417
+ given_args[sym][:negative_given] = negative_given
418
+ given_args[sym][:params] ||= []
419
+
420
+ # The block returns the number of parameters taken.
421
+ num_params_taken = 0
422
+
423
+ unless params.empty?
424
+ if @specs[sym].single_arg?
425
+ given_args[sym][:params] << params[0, 1] # take the first parameter
426
+ num_params_taken = 1
427
+ elsif @specs[sym].multi_arg?
428
+ given_args[sym][:params] << params # take all the parameters
429
+ num_params_taken = params.size
430
+ end
431
+ end
432
+
433
+ num_params_taken
434
+ end
435
+
436
+ ## check for version and help args
437
+ raise VersionNeeded if given_args.include? :version
438
+ raise HelpNeeded if given_args.include? :help
439
+
440
+ ## check constraint satisfaction
441
+ @constraints.each do |const|
442
+ const.validate(given_args: given_args, specs: @specs)
443
+ end
444
+
445
+ required.each do |sym, val|
446
+ raise CommandlineError, "option --#{@specs[sym].long.long} must be specified" unless given_args.include? sym
447
+ end
448
+
449
+ ## parse parameters
450
+ given_args.each do |sym, given_data|
451
+ arg, params, negative_given = given_data.values_at :arg, :params, :negative_given
452
+
453
+ opts = @specs[sym]
454
+ if params.empty? && !opts.flag?
455
+ raise CommandlineError, "option '#{arg}' needs a parameter" unless opts.default
456
+ params << (opts.array_default? ? opts.default.clone : [opts.default])
457
+ end
458
+
459
+ if params.first && opts.permitted
460
+ params.first.each do |val|
461
+ opts.validate_permitted(arg, val)
462
+ end
463
+ end
464
+
465
+ vals[:"#{sym}_given"] = true # mark argument as specified on the commandline
466
+
467
+ vals[sym] = opts.parse(params, negative_given)
468
+
469
+ if opts.single_arg?
470
+ vals[sym] = if opts.multi? # multiple options, each with a single parameter
471
+ vals[sym].map { |p| p[0] }
472
+ else # single parameter
473
+ vals[sym][0][0]
474
+ end
475
+ elsif opts.multi_arg? && !opts.multi?
476
+ vals[sym] = vals[sym][0] # single option, with multiple parameters
477
+ end
478
+ # else: multiple options, with multiple parameters
479
+
480
+ opts.callback.call(vals[sym]) if opts.callback
481
+ end
482
+
483
+ # change 3 to not mutate input, see top of file
484
+ ## modify input in place with only those
485
+ ## arguments we didn't process
486
+ # cmdline.clear
487
+ # @leftovers.each { |l| cmdline << l }
488
+ vals[:_leftovers] = @leftovers.dup
489
+
490
+ ## allow openstruct-style accessors
491
+ class << vals
492
+ def method_missing(m, *_args)
493
+ self[m] || self[m.to_s]
494
+ end
495
+ end
496
+ vals
497
+ end
498
+
499
+ ## Print the help message to +stream+.
500
+ def educate(stream = $stdout)
501
+ width # hack: calculate it now; otherwise we have to be careful not to
502
+ # call this unless the cursor's at the beginning of a line.
503
+
504
+ left = {}
505
+ @specs.each { |name, spec| left[name] = spec.educate }
506
+
507
+ leftcol_width = left.values.map(&:length).max || 0
508
+ rightcol_start = leftcol_width + 6 # spaces
509
+
510
+ unless @order.size > 0 && @order.first.first == :text
511
+ command_name = File.basename($0).gsub(/\.[^.]+$/, "")
512
+ stream.puts "Usage: #{command_name} #{@usage}\n" if @usage
513
+ stream.puts "#{@synopsis}\n" if @synopsis
514
+ stream.puts if @usage || @synopsis
515
+ stream.puts "#{@version}\n" if @version
516
+ stream.puts "Options:"
517
+ end
518
+
519
+ @order.each do |what, opt|
520
+ if what == :text
521
+ stream.puts wrap(opt)
522
+ next
523
+ end
524
+
525
+ spec = @specs[opt]
526
+ stream.printf " %-#{leftcol_width}s ", left[opt]
527
+ desc = spec.full_description
528
+
529
+ stream.puts wrap(desc, width: width - rightcol_start - 1, prefix: rightcol_start)
530
+ end
531
+ end
532
+
533
+ def width # :nodoc:
534
+ @width ||= if $stdout.tty?
535
+ begin
536
+ require "io/console"
537
+ w = IO.console.winsize.last
538
+ (w.to_i > 0) ? w : 80
539
+ rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF, Errno::EINVAL
540
+ legacy_width
541
+ end
542
+ else
543
+ 80
544
+ end
545
+ end
546
+
547
+ def legacy_width
548
+ # Support for older Rubies where io/console is not available
549
+ `tput cols`.to_i
550
+ rescue Errno::ENOENT
551
+ 80
552
+ end
553
+ private :legacy_width
554
+
555
+ def wrap(str, opts = {}) # :nodoc:
556
+ if str == ""
557
+ [""]
558
+ else
559
+ inner = false
560
+ str.split("\n").map do |s|
561
+ line = wrap_line s, opts.merge(inner: inner)
562
+ inner = true
563
+ line
564
+ end.flatten
565
+ end
566
+ end
567
+
568
+ ## The per-parser version of Optimist::die (see that for documentation).
569
+ def die(arg, msg = nil, error_code = nil)
570
+ msg, error_code = nil, msg if msg.is_a?(Integer)
571
+ if msg
572
+ warn "Error: argument --#{@specs[arg].long.long} #{msg}."
573
+ else
574
+ warn "Error: #{arg}."
575
+ end
576
+ if @educate_on_error
577
+ $stderr.puts
578
+ educate $stderr
579
+ else
580
+ warn "Try --help for help."
581
+ end
582
+ exit(error_code || -1)
583
+ end
584
+
585
+ private
586
+
587
+ ## yield successive arg, parameter pairs
588
+ def each_arg(args)
589
+ remains = []
590
+ i = 0
591
+
592
+ until i >= args.length
593
+ return remains += args[i..-1] if @stop_words.member? args[i]
594
+ case args[i]
595
+ when "--" # arg terminator
596
+ return remains += args[(i + 1)..-1]
597
+ when /^--(\S+?)=(.*)$/ # long argument with equals
598
+ num_params_taken = yield "--#{$1}", [$2]
599
+ if num_params_taken.nil?
600
+ remains << args[i]
601
+ if @stop_on_unknown
602
+ return remains += args[i + 1..-1]
603
+ end
604
+ end
605
+ i += 1
606
+ when /^--(\S+)$/ # long argument
607
+ params = collect_argument_parameters(args, i + 1)
608
+ num_params_taken = yield args[i], params
609
+
610
+ if num_params_taken.nil?
611
+ remains << args[i]
612
+ if @stop_on_unknown
613
+ return remains += args[i + 1..-1]
614
+ end
615
+ else
616
+ i += num_params_taken
617
+ end
618
+ i += 1
619
+ when /^-(\S+)$/ # one or more short arguments
620
+ short_remaining = []
621
+ shortargs = $1.split("")
622
+ shortargs.each_with_index do |a, j|
623
+ if j == (shortargs.length - 1)
624
+ params = collect_argument_parameters(args, i + 1)
625
+
626
+ num_params_taken = yield "-#{a}", params
627
+ if num_params_taken
628
+ i += num_params_taken
629
+ else
630
+ short_remaining << a
631
+ if @stop_on_unknown
632
+ remains << "-#{short_remaining.join}"
633
+ return remains += args[i + 1..-1]
634
+ end
635
+ end
636
+ else
637
+ unless yield "-#{a}", []
638
+ short_remaining << a
639
+ if @stop_on_unknown
640
+ short_remaining << shortargs[j + 1..-1].join
641
+ remains << "-#{short_remaining.join}"
642
+ return remains += args[i + 1..-1]
643
+ end
644
+ end
645
+ end
646
+ end
647
+
648
+ unless short_remaining.empty?
649
+ remains << "-#{short_remaining.join}"
650
+ end
651
+ i += 1
652
+ else
653
+ if @stop_on_unknown
654
+ return remains += args[i..-1]
655
+ else
656
+ remains << args[i]
657
+ i += 1
658
+ end
659
+ end
660
+ end
661
+
662
+ remains
663
+ end
664
+
665
+ def collect_argument_parameters(args, start_at)
666
+ params = []
667
+ pos = start_at
668
+ while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos])
669
+ params << args[pos]
670
+ pos += 1
671
+ end
672
+ params
673
+ end
674
+
675
+ def resolve_default_short_options!
676
+ @order.each do |type, name|
677
+ opts = @specs[name]
678
+ next if type != :opt || opts.doesnt_need_autogen_short
679
+ c = opts.long.long.split("").find { |d| d !~ Recheck::Optimist::ShortNames::INVALID_ARG_REGEX && !@short.member?(d) }
680
+ if c # found a character to use
681
+ opts.short.add c
682
+ @short[c] = name
683
+ end
684
+ end
685
+ end
686
+
687
+ def wrap_line(str, opts = {})
688
+ prefix = opts[:prefix] || 0
689
+ width = opts[:width] || (self.width - 1)
690
+ start = 0
691
+ ret = []
692
+ until start > str.length
693
+ nextt =
694
+ if start + width >= str.length
695
+ str.length
696
+ else
697
+ x = str.rindex(/\s/, start + width)
698
+ x = str.index(/\s/, start) if x && x < start
699
+ x || str.length
700
+ end
701
+ ret << ((ret.empty? && !opts[:inner]) ? "" : " " * prefix) + str[start...nextt]
702
+ start = nextt + 1
703
+ end
704
+ ret
705
+ end
706
+ end
707
+
708
+ class LongNames
709
+ def initialize
710
+ @truename = nil
711
+ @long = nil
712
+ @alts = []
713
+ end
714
+
715
+ def make_valid(lopt)
716
+ return nil if lopt.nil?
717
+ case lopt.to_s
718
+ when /^--([^-].*)$/ then $1
719
+ when /^[^-]/ then lopt.to_s
720
+ else raise ArgumentError, "invalid long option name #{lopt.inspect}"
721
+ end
722
+ end
723
+
724
+ def set(name, lopt, alts)
725
+ @truename = name
726
+ lopt = lopt ? lopt.to_s : name.to_s.tr("_", "-")
727
+ @long = make_valid(lopt)
728
+ alts = [alts] unless alts.is_a?(Array) # box the value
729
+ @alts = alts.map { |alt| make_valid(alt) }.compact
730
+ end
731
+
732
+ # long specified with :long has precedence over the true-name
733
+ def long
734
+ @long || @truename
735
+ end
736
+
737
+ # all valid names, including alts
738
+ def names
739
+ [long] + @alts
740
+ end
741
+ end
742
+
743
+ class ShortNames
744
+ INVALID_ARG_REGEX = /[\d-]/ # :nodoc:
745
+
746
+ def initialize
747
+ @chars = []
748
+ @auto = true
749
+ end
750
+
751
+ attr_reader :chars, :auto
752
+
753
+ def add(values)
754
+ values = [values] unless values.is_a?(Array) # box the value
755
+ values = values.compact
756
+ if values.include?(:none)
757
+ if values.size == 1
758
+ @auto = false
759
+ return
760
+ end
761
+ raise ArgumentError, "Cannot use :none with any other values in short option: #{values.inspect}"
762
+ end
763
+ values.each do |val|
764
+ strval = val.to_s
765
+ sopt = case strval
766
+ when /^-(.)$/ then $1
767
+ when /^.$/ then strval
768
+ else raise ArgumentError, "invalid short option name '#{val.inspect}'"
769
+ end
770
+
771
+ if INVALID_ARG_REGEX.match?(sopt)
772
+ raise ArgumentError, "short option name '#{sopt}' can't be a number or a dash"
773
+ end
774
+ @chars << sopt
775
+ end
776
+ end
777
+ end
778
+
779
+ class Option
780
+ attr_accessor :name, :short, :long, :default, :permitted, :permitted_response
781
+ attr_writer :multi_given
782
+
783
+ def initialize
784
+ @long = LongNames.new
785
+ @short = ShortNames.new # can be an Array of one-char strings, a one-char String, nil or :none
786
+ @name = nil
787
+ @multi_given = false
788
+ @hidden = false
789
+ @default = nil
790
+ @permitted = nil
791
+ @permitted_response = "option '%{arg}' only accepts %{valid_string}"
792
+ @optshash = {}
793
+ end
794
+
795
+ def opts(key)
796
+ @optshash[key]
797
+ end
798
+
799
+ def opts=(o)
800
+ @optshash = o
801
+ end
802
+
803
+ ## Indicates a flag option, which is an option without an argument
804
+ def flag?
805
+ false
806
+ end
807
+
808
+ def single_arg?
809
+ !multi_arg? && !flag?
810
+ end
811
+
812
+ def multi
813
+ @multi_given
814
+ end
815
+ alias_method :multi?, :multi
816
+
817
+ ## Indicates that this is a multivalued (Array type) argument
818
+ def multi_arg?
819
+ false
820
+ end
821
+ ## note: Option-Types with both multi_arg? and flag? false are single-parameter (normal) options.
822
+
823
+ def array_default?
824
+ default.is_a?(Array)
825
+ end
826
+
827
+ def doesnt_need_autogen_short
828
+ !short.auto || short.chars.any?
829
+ end
830
+
831
+ def callback
832
+ opts(:callback)
833
+ end
834
+
835
+ def desc
836
+ opts(:desc)
837
+ end
838
+
839
+ def required?
840
+ opts(:required)
841
+ end
842
+
843
+ def parse(_paramlist, _neg_given)
844
+ raise NotImplementedError, "parse must be overridden for newly registered type"
845
+ end
846
+
847
+ # provide type-format string. default to empty, but user should probably override it
848
+ def type_format
849
+ ""
850
+ end
851
+
852
+ def educate
853
+ optionlist = []
854
+ optionlist.concat(short.chars.map { |o| "-#{o}" })
855
+ optionlist.concat(long.names.map { |o| "--#{o}" })
856
+ optionlist.compact.join(", ") + type_format + ((flag? && default) ? ", --no-#{long}" : "")
857
+ end
858
+
859
+ ## Format the educate-line description including the default and permitted value(s)
860
+ def full_description
861
+ desc_str = desc
862
+ desc_str += default_description_str(desc) if default
863
+ desc_str += permitted_description_str(desc) if permitted
864
+ desc_str
865
+ end
866
+
867
+ ## Format stdio like objects to a string
868
+ def format_stdio(obj)
869
+ case obj
870
+ when $stdout then "<stdout>"
871
+ when $stdin then "<stdin>"
872
+ when $stderr then "<stderr>"
873
+ else obj # pass-through-case
874
+ end
875
+ end
876
+
877
+ ## Generate the default value string for the educate line
878
+ private def default_description_str str
879
+ default_s = case default
880
+ when Array
881
+ default.join(", ")
882
+ else
883
+ format_stdio(default).to_s
884
+ end
885
+ defword = str.end_with?(".") ? "Default" : "default"
886
+ " (#{defword}: #{default_s})"
887
+ end
888
+
889
+ def permitted_valid_string
890
+ case permitted
891
+ when Array
892
+ return "one of: " + permitted.to_a.map(&:to_s).join(", ")
893
+ when Range
894
+ return "value in range of: #{permitted}"
895
+ when Regexp
896
+ return "value matching: #{permitted.inspect}"
897
+ end
898
+ raise NotImplementedError, "invalid branch"
899
+ end
900
+
901
+ def permitted_type_valid?
902
+ case permitted
903
+ when NilClass, Array, Range, Regexp then true
904
+ else false
905
+ end
906
+ end
907
+
908
+ def validate_permitted(arg, value)
909
+ return true if permitted.nil?
910
+ unless permitted_value?(value)
911
+ format_hash = {arg: arg, given: value, value: value, valid_string: permitted_valid_string, permitted: permitted}
912
+ raise CommandlineError, permitted_response % format_hash
913
+ end
914
+ true
915
+ end
916
+
917
+ # incoming values from the command-line should be strings, so we should
918
+ # stringify any permitted types as the basis of comparison.
919
+ def permitted_value?(val)
920
+ case permitted
921
+ when nil then true
922
+ when Regexp then val.match? permitted
923
+ when Range then permitted.include? as_type(val)
924
+ when Array then permitted.map(&:to_s).include? val
925
+ else false
926
+ end
927
+ end
928
+
929
+ ## Generate the permitted values string for the educate line
930
+ private def permitted_description_str str
931
+ permitted_s = case permitted
932
+ when Array
933
+ permitted.map do |p|
934
+ format_stdio(p).to_s
935
+ end.join(", ")
936
+ when Range, Regexp
937
+ permitted.inspect
938
+ else
939
+ raise NotImplementedError
940
+ end
941
+ permword = str.end_with?(".") ? "Permitted" : "permitted"
942
+ " (#{permword}: #{permitted_s})"
943
+ end
944
+
945
+ ## Provide a way to register symbol aliases to the Parser
946
+ def self.register_alias(*alias_keys)
947
+ alias_keys.each do |alias_key|
948
+ # pass in the alias-key and the class
949
+ Recheck::Optimist::Parser.register(alias_key, self)
950
+ end
951
+ end
952
+
953
+ ## Factory class methods ...
954
+
955
+ # Determines which type of object to create based on arguments passed
956
+ # to +Optimist::opt+. This is trickier in Optimist, than other cmdline
957
+ # parsers (e.g. Slop) because we allow the +default:+ to be able to
958
+ # set the option's type.
959
+ def self.create(name, desc = "", opts = {}, settings = {})
960
+ opttype = Recheck::Optimist::Parser.registry_getopttype(opts[:type])
961
+ opttype_from_default = get_klass_from_default(opts, opttype)
962
+
963
+ raise ArgumentError, ":type specification and default type don't match (default type is #{opttype_from_default.class})" if opttype && opttype_from_default && (opttype.class != opttype_from_default.class)
964
+
965
+ opt_inst = opttype || opttype_from_default || Recheck::Optimist::BooleanOption.new
966
+
967
+ ## fill in :long
968
+ opt_inst.long.set(name, opts[:long], opts[:alt])
969
+
970
+ ## fill in :short
971
+ opt_inst.short.add opts[:short]
972
+
973
+ ## fill in :multi
974
+ multi_given = opts[:multi] || false
975
+ opt_inst.multi_given = multi_given
976
+
977
+ ## fill in :default for flags
978
+ defvalue = opts[:default] || opt_inst.default
979
+
980
+ ## fill in permitted values
981
+ permitted = opts[:permitted] || nil
982
+
983
+ ## autobox :default for :multi (multi-occurrence) arguments
984
+ defvalue = [defvalue] if defvalue && multi_given && !defvalue.is_a?(Array)
985
+ opt_inst.permitted = permitted
986
+ opt_inst.permitted_response = opts[:permitted_response] if opts[:permitted_response]
987
+ opt_inst.default = defvalue
988
+ opt_inst.name = name
989
+ opt_inst.opts = opts
990
+ opt_inst
991
+ end
992
+
993
+ private
994
+
995
+ def self.get_type_from_disdef(optdef, opttype, disambiguated_default)
996
+ if disambiguated_default.is_a? Array
997
+ return(optdef.first.class.name.downcase + "s") if !optdef.empty?
998
+ if opttype
999
+ raise ArgumentError, "multiple argument type must be plural" unless opttype.multi_arg?
1000
+ return nil
1001
+ else
1002
+ raise ArgumentError, "multiple argument type cannot be deduced from an empty array"
1003
+ end
1004
+ end
1005
+ disambiguated_default.class.name.downcase
1006
+ end
1007
+
1008
+ def self.get_klass_from_default(opts, opttype)
1009
+ ## for options with :multi => true, an array default doesn't imply
1010
+ ## a multi-valued argument. for that you have to specify a :type
1011
+ ## as well. (this is how we disambiguate an ambiguous situation;
1012
+ ## see the docs for Parser#opt for details.)
1013
+
1014
+ disambiguated_default = if opts[:multi] && opts[:default].is_a?(Array) && opttype.nil?
1015
+ opts[:default].first
1016
+ else
1017
+ opts[:default]
1018
+ end
1019
+
1020
+ return nil if disambiguated_default.nil?
1021
+ type_from_default = get_type_from_disdef(opts[:default], opttype, disambiguated_default)
1022
+ Recheck::Optimist::Parser.registry_getopttype(type_from_default)
1023
+ end
1024
+ end
1025
+
1026
+ # Flag option. Has no arguments. Can be negated with "no-".
1027
+ class BooleanOption < Option
1028
+ register_alias :flag, :bool, :boolean, :trueclass, :falseclass
1029
+ def initialize
1030
+ super
1031
+ @default = false
1032
+ end
1033
+
1034
+ def flag?
1035
+ true
1036
+ end
1037
+
1038
+ def parse(_paramlist, neg_given)
1039
+ (/^no_/.match?(name.to_s) ? neg_given : !neg_given)
1040
+ end
1041
+ end
1042
+
1043
+ # Floating point number option class.
1044
+ class FloatOption < Option
1045
+ register_alias :float, :double
1046
+ def type_format
1047
+ "=<f>"
1048
+ end
1049
+
1050
+ def as_type(param)
1051
+ param.to_f
1052
+ end
1053
+
1054
+ def parse(paramlist, _neg_given)
1055
+ paramlist.map do |pg|
1056
+ pg.map do |param|
1057
+ raise CommandlineError, "option '#{name}' needs a floating-point number" unless param.is_a?(Numeric) || param =~ FLOAT_RE
1058
+ as_type(param)
1059
+ end
1060
+ end
1061
+ end
1062
+ end
1063
+
1064
+ # Integer number option class.
1065
+ class IntegerOption < Option
1066
+ register_alias :int, :integer, :fixnum
1067
+ def type_format
1068
+ "=<i>"
1069
+ end
1070
+
1071
+ def as_type(param)
1072
+ param.to_i
1073
+ end
1074
+
1075
+ def parse(paramlist, _neg_given)
1076
+ paramlist.map do |pg|
1077
+ pg.map do |param|
1078
+ raise CommandlineError, "option '#{name}' needs an integer" unless param.is_a?(Numeric) || param =~ /^-?[\d_]+$/
1079
+ as_type(param)
1080
+ end
1081
+ end
1082
+ end
1083
+ end
1084
+
1085
+ # Option class for handling IO objects and URLs.
1086
+ # Note that this will return the file-handle, not the file-name
1087
+ # in the case of file-paths given to it.
1088
+ class IOOption < Option
1089
+ register_alias :io
1090
+ def type_format
1091
+ "=<filename/uri>"
1092
+ end
1093
+
1094
+ def parse(paramlist, _neg_given)
1095
+ paramlist.map do |pg|
1096
+ pg.map do |param|
1097
+ if /^(stdin|-)$/i.match?(param)
1098
+ $stdin
1099
+ else
1100
+ require "open-uri"
1101
+ begin
1102
+ open param
1103
+ rescue SystemCallError => e
1104
+ raise CommandlineError, "file or url for option '#{name}' cannot be opened: #{e.message}"
1105
+ end
1106
+ end
1107
+ end
1108
+ end
1109
+ end
1110
+ end
1111
+
1112
+ # Option class for handling Strings.
1113
+ class StringOption < Option
1114
+ register_alias :string
1115
+ def as_type(val)
1116
+ val.to_s
1117
+ end
1118
+
1119
+ def type_format
1120
+ "=<s>"
1121
+ end
1122
+
1123
+ def parse(paramlist, _neg_given)
1124
+ paramlist.map { |pg| pg.map { |param| as_type(param) } }
1125
+ end
1126
+ end
1127
+
1128
+ # Option for dates. Uses Chronic if it exists.
1129
+ class DateOption < Option
1130
+ register_alias :date
1131
+ def type_format
1132
+ "=<date>"
1133
+ end
1134
+
1135
+ def parse(paramlist, _neg_given)
1136
+ paramlist.map do |pg|
1137
+ pg.map do |param|
1138
+ next param if param.is_a?(Date)
1139
+ begin
1140
+ begin
1141
+ require "chronic"
1142
+ time = Chronic.parse(param)
1143
+ rescue LoadError
1144
+ # chronic is not available
1145
+ end
1146
+ time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
1147
+ rescue ArgumentError
1148
+ raise CommandlineError, "option '#{name}' needs a date"
1149
+ end
1150
+ end
1151
+ end
1152
+ end
1153
+ end
1154
+
1155
+ ### MULTI_OPT_TYPES :
1156
+ ## The set of values that indicate a multiple-parameter option (i.e., that
1157
+ ## takes multiple space-separated values on the commandline) when passed as
1158
+ ## the +:type+ parameter of #opt.
1159
+
1160
+ # Option class for handling multiple Integers
1161
+ class IntegerArrayOption < IntegerOption
1162
+ register_alias :fixnums, :ints, :integers
1163
+ def type_format
1164
+ "=<i+>"
1165
+ end
1166
+
1167
+ def multi_arg?
1168
+ true
1169
+ end
1170
+ end
1171
+
1172
+ # Option class for handling multiple Floats
1173
+ class FloatArrayOption < FloatOption
1174
+ register_alias :doubles, :floats
1175
+ def type_format
1176
+ "=<f+>"
1177
+ end
1178
+
1179
+ def multi_arg?
1180
+ true
1181
+ end
1182
+ end
1183
+
1184
+ # Option class for handling multiple Strings
1185
+ class StringArrayOption < StringOption
1186
+ register_alias :strings
1187
+ def type_format
1188
+ "=<s+>"
1189
+ end
1190
+
1191
+ def multi_arg?
1192
+ true
1193
+ end
1194
+ end
1195
+
1196
+ # Option class for handling multiple dates
1197
+ class DateArrayOption < DateOption
1198
+ register_alias :dates
1199
+ def type_format
1200
+ "=<date+>"
1201
+ end
1202
+
1203
+ def multi_arg?
1204
+ true
1205
+ end
1206
+ end
1207
+
1208
+ # Option class for handling Files/URLs via 'open'
1209
+ class IOArrayOption < IOOption
1210
+ register_alias :ios
1211
+ def type_format
1212
+ "=<filename/uri+>"
1213
+ end
1214
+
1215
+ def multi_arg?
1216
+ true
1217
+ end
1218
+ end
1219
+
1220
+ ## The easy, syntactic-sugary entry method into Optimist. Creates a Parser,
1221
+ ## passes the block to it, then parses +args+ with it, handling any errors or
1222
+ ## requests for help or version information appropriately (and then exiting).
1223
+ ## Modifies +args+ in place. Returns a hash of option values.
1224
+ ##
1225
+ ## The block passed in should contain zero or more calls to +opt+
1226
+ ## (Parser#opt), zero or more calls to +text+ (Parser#text), and
1227
+ ## probably a call to +version+ (Parser#version).
1228
+ ##
1229
+ ## The returned block contains a value for every option specified with
1230
+ ## +opt+. The value will be the value given on the commandline, or the
1231
+ ## default value if the option was not specified on the commandline. For
1232
+ ## every option specified on the commandline, a key "<option
1233
+ ## name>_given" will also be set in the hash.
1234
+ ##
1235
+ ## Example:
1236
+ ##
1237
+ ## require 'optimist'
1238
+ ## opts = Optimist::options do
1239
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
1240
+ ## opt :name, "Monkey name", :type => :string # a string --name <s>, defaulting to nil
1241
+ ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4
1242
+ ## end
1243
+ ##
1244
+ ## ## if called with no arguments
1245
+ ## p opts # => {:monkey=>false, :name=>nil, :num_limbs=>4, :help=>false}
1246
+ ##
1247
+ ## ## if called with --monkey
1248
+ ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
1249
+ ##
1250
+ ## Settings:
1251
+ ## Optimist::options and Optimist::Parser.new accept +settings+ to control how
1252
+ ## options are interpreted. These settings are given as hash arguments, e.g:
1253
+ ##
1254
+ ## opts = Optimist::options(ARGV, exact_match: false) do
1255
+ ## opt :foobar, 'messed up'
1256
+ ## opt :forget, 'forget it'
1257
+ ## end
1258
+ ##
1259
+ ## +settings+ include:
1260
+ ## * :exact_match : (default=true) Allow minimum unambigous number of characters to match a long option
1261
+ ## * :suggestions : (default=true) Enables suggestions when unknown arguments are given and DidYouMean is installed. DidYouMean comes standard with Ruby 2.3+
1262
+ ## * :implicit_short_opts : (default=true) Short options will only be created where explicitly defined. If you do not like short-options, this will prevent having to define :short=> :none for all of your options.
1263
+ ## Because Optimist::options uses a default argument for +args+, you must pass that argument when using the settings feature.
1264
+ ##
1265
+ ## See more examples at https://www.manageiq.org/optimist
1266
+ def options(args = ARGV, *a, &b)
1267
+ @last_parser = Parser.new(*a, &b)
1268
+ with_standard_exception_handling(@last_parser) { @last_parser.parse args }
1269
+ end
1270
+
1271
+ ## If Optimist::options doesn't do quite what you want, you can create a Parser
1272
+ ## object and call Parser#parse on it. That method will throw CommandlineError,
1273
+ ## HelpNeeded and VersionNeeded exceptions when necessary; if you want to
1274
+ ## have these handled for you in the standard manner (e.g. show the help
1275
+ ## and then exit upon an HelpNeeded exception), call your code from within
1276
+ ## a block passed to this method.
1277
+ ##
1278
+ ## Note that this method will call System#exit after handling an exception!
1279
+ ##
1280
+ ## Usage example:
1281
+ ##
1282
+ ## require 'optimist'
1283
+ ## p = Optimist::Parser.new do
1284
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
1285
+ ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true
1286
+ ## end
1287
+ ##
1288
+ ## opts = Optimist::with_standard_exception_handling p do
1289
+ ## o = p.parse ARGV
1290
+ ## raise Optimist::HelpNeeded if ARGV.empty? # show help screen
1291
+ ## o
1292
+ ## end
1293
+ ##
1294
+ ## Requires passing in the parser object.
1295
+
1296
+ def with_standard_exception_handling(parser)
1297
+ yield
1298
+ rescue CommandlineError => e
1299
+ parser.die(e.message, nil, e.error_code)
1300
+ rescue HelpNeeded
1301
+ parser.educate
1302
+ exit
1303
+ rescue VersionNeeded
1304
+ puts parser.version
1305
+ exit
1306
+ end
1307
+
1308
+ ## Informs the user that their usage of 'arg' was wrong, as detailed by
1309
+ ## 'msg', and dies. Example:
1310
+ ##
1311
+ ## options do
1312
+ ## opt :volume, :default => 0.0
1313
+ ## end
1314
+ ##
1315
+ ## die :volume, "too loud" if opts[:volume] > 10.0
1316
+ ## die :volume, "too soft" if opts[:volume] < 0.1
1317
+ ##
1318
+ ## In the one-argument case, simply print that message, a notice
1319
+ ## about -h, and die. Example:
1320
+ ##
1321
+ ## options do
1322
+ ## opt :whatever # ...
1323
+ ## end
1324
+ ##
1325
+ ## Optimist::die "need at least one filename" if ARGV.empty?
1326
+ ##
1327
+ ## An exit code can be provide if needed
1328
+ ##
1329
+ ## Optimist::die "need at least one filename", -2 if ARGV.empty?
1330
+ def die(arg, msg = nil, error_code = nil)
1331
+ if @last_parser
1332
+ @last_parser.die arg, msg, error_code
1333
+ else
1334
+ raise ArgumentError, "Optimist::die can only be called after Optimist::options"
1335
+ end
1336
+ end
1337
+
1338
+ ## Displays the help message and dies. Example:
1339
+ ##
1340
+ ## options do
1341
+ ## opt :volume, :default => 0.0
1342
+ ## banner <<-EOS
1343
+ ## Usage:
1344
+ ## #$0 [options] <name>
1345
+ ## where [options] are:
1346
+ ## EOS
1347
+ ## end
1348
+ ##
1349
+ ## Optimist::educate if ARGV.empty?
1350
+ def educate
1351
+ if @last_parser
1352
+ @last_parser.educate
1353
+ exit
1354
+ else
1355
+ raise ArgumentError, "Optimist::educate can only be called after Optimist::options"
1356
+ end
1357
+ end
1358
+
1359
+ module_function :options, :die, :educate, :with_standard_exception_handling
1360
+ end # module Optimist
1361
+ end # module Recheck