rbcli 0.3.2 → 0.3.3

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