rbcli 0.3.2 → 0.3.3

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