kajiki 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 06718a7d50f5815e90bd8f262d1793ba99adf2d2
4
- data.tar.gz: 74460fce7204827d8e11bf8534e8014508b3c253
2
+ SHA256:
3
+ metadata.gz: 136248b0c9872cfb20df0b33476023c963a455daaf3b3045dc5d1ecc97d0d513
4
+ data.tar.gz: 34b5ba8b3dd9cb23a195f7a1bab87af122a7d9e9e7c4be3176aa41b977efdf3b
5
5
  SHA512:
6
- metadata.gz: b111b52141b43ed2687423f09112f5d48ca3bde98c721d0075b25872ee52d6db544b6624d508d2e4490fb16a0fc0afc1b8bf87060bfde092c9aa5cf11b8a0e7e
7
- data.tar.gz: 97af01b028c046751f5447524b84195d6fdfa7bdc307e4060d15a0543712488fcf14b6b86fbec9c4b71e753c2586c2884a40c1efa415d5f7afc9ac980d3eca95
6
+ metadata.gz: 5fc23f97d0733527e7e86ff9476d47e376e4465317a9a602289c6d2c0f4388228c24d9e99a8c6d99adf486fb6de1430da4078029b6f749696ad3ed91fbbc52b0
7
+ data.tar.gz: '09fa046b978177adbab62229283e2f0f16b14d6b3c9f20a7ce73e91aed1933b36cbb97c744321f0699bc4052777fc92ef07f120a4704bb49cfaffefd15562bf8'
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015 Ken J.
3
+ Copyright (c) 2015-2020 Ken J.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -10,7 +10,7 @@ It provides basic functions to start and stop a daemon process. It also parses c
10
10
 
11
11
  - Ruby 2.0.0 <=
12
12
 
13
- Kajiki has no gem dependencies. It uses [Trollop](https://rubygems.org/gems/trollop), but it's embedded.
13
+ Kajiki has no gem dependencies. It uses [Optimist](https://rubygems.org/gems/optimist), but it's embedded.
14
14
 
15
15
  ## Getting Started
16
16
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "kajiki"
3
- s.version = "1.1.1"
3
+ s.version = "1.2.0"
4
4
  s.authors = ["Ken J."]
5
5
  s.email = ["kenjij@gmail.com"]
6
6
  s.description = %q{A simple gem to build daemons}
@@ -1,4 +1,4 @@
1
- require 'kajiki/trollop'
1
+ require 'kajiki/optimist'
2
2
  require 'kajiki/presets'
3
3
  require 'kajiki/runner'
4
4
 
@@ -0,0 +1,1014 @@
1
+ # lib/optimist.rb -- optimist command-line processing library
2
+ # Copyright (c) 2008-2014 William Morgan.
3
+ # Copyright (c) 2014 Red Hat, Inc.
4
+ # optimist is licensed under the MIT license.
5
+
6
+ require 'date'
7
+
8
+ module Optimist
9
+ # note: this is duplicated in gemspec
10
+ # please change over there too
11
+ VERSION = "3.0.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
+ @long[o.long] = o.name
156
+ @short[o.short] = o.name if o.short?
157
+ @specs[o.name] = o
158
+ @order << [:opt, o.name]
159
+ end
160
+
161
+ ## Sets the version string. If set, the user can request the version
162
+ ## on the commandline. Should probably be of the form "<program name>
163
+ ## <version number>".
164
+ def version(s = nil)
165
+ s ? @version = s : @version
166
+ end
167
+
168
+ ## Sets the usage string. If set the message will be printed as the
169
+ ## first line in the help (educate) output and ending in two new
170
+ ## lines.
171
+ def usage(s = nil)
172
+ s ? @usage = s : @usage
173
+ end
174
+
175
+ ## Adds a synopsis (command summary description) right below the
176
+ ## usage line, or as the first line if usage isn't specified.
177
+ def synopsis(s = nil)
178
+ s ? @synopsis = s : @synopsis
179
+ end
180
+
181
+ ## Adds text to the help display. Can be interspersed with calls to
182
+ ## #opt to build a multi-section help page.
183
+ def banner(s)
184
+ @order << [:text, s]
185
+ end
186
+ alias_method :text, :banner
187
+
188
+ ## Marks two (or more!) options as requiring each other. Only handles
189
+ ## undirected (i.e., mutual) dependencies. Directed dependencies are
190
+ ## better modeled with Optimist::die.
191
+ def depends(*syms)
192
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
193
+ @constraints << [:depends, syms]
194
+ end
195
+
196
+ ## Marks two (or more!) options as conflicting.
197
+ def conflicts(*syms)
198
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
199
+ @constraints << [:conflicts, syms]
200
+ end
201
+
202
+ ## Defines a set of words which cause parsing to terminate when
203
+ ## encountered, such that any options to the left of the word are
204
+ ## parsed as usual, and options to the right of the word are left
205
+ ## intact.
206
+ ##
207
+ ## A typical use case would be for subcommand support, where these
208
+ ## would be set to the list of subcommands. A subsequent Optimist
209
+ ## invocation would then be used to parse subcommand options, after
210
+ ## shifting the subcommand off of ARGV.
211
+ def stop_on(*words)
212
+ @stop_words = [*words].flatten
213
+ end
214
+
215
+ ## Similar to #stop_on, but stops on any unknown word when encountered
216
+ ## (unless it is a parameter for an argument). This is useful for
217
+ ## cases where you don't know the set of subcommands ahead of time,
218
+ ## i.e., without first parsing the global options.
219
+ def stop_on_unknown
220
+ @stop_on_unknown = true
221
+ end
222
+
223
+ ## Instead of displaying "Try --help for help." on an error
224
+ ## display the usage (via educate)
225
+ def educate_on_error
226
+ @educate_on_error = true
227
+ end
228
+
229
+ ## Parses the commandline. Typically called by Optimist::options,
230
+ ## but you can call it directly if you need more control.
231
+ ##
232
+ ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions.
233
+ def parse(cmdline = ARGV)
234
+ vals = {}
235
+ required = {}
236
+
237
+ opt :version, "Print version and exit" if @version && ! (@specs[:version] || @long["version"])
238
+ opt :help, "Show this message" unless @specs[:help] || @long["help"]
239
+
240
+ @specs.each do |sym, opts|
241
+ required[sym] = true if opts.required?
242
+ vals[sym] = opts.default
243
+ vals[sym] = [] if opts.multi && !opts.default # multi arguments default to [], not nil
244
+ end
245
+
246
+ resolve_default_short_options!
247
+
248
+ ## resolve symbols
249
+ given_args = {}
250
+ @leftovers = each_arg cmdline do |arg, params|
251
+ ## handle --no- forms
252
+ arg, negative_given = if arg =~ /^--no-([^-]\S*)$/
253
+ ["--#{$1}", true]
254
+ else
255
+ [arg, false]
256
+ end
257
+
258
+ sym = case arg
259
+ when /^-([^-])$/ then @short[$1]
260
+ when /^--([^-]\S*)$/ then @long[$1] || @long["no-#{$1}"]
261
+ else raise CommandlineError, "invalid argument syntax: '#{arg}'"
262
+ end
263
+
264
+ sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments
265
+
266
+ next nil if ignore_invalid_options && !sym
267
+ raise CommandlineError, "unknown argument '#{arg}'" unless sym
268
+
269
+ if given_args.include?(sym) && !@specs[sym].multi?
270
+ raise CommandlineError, "option '#{arg}' specified multiple times"
271
+ end
272
+
273
+ given_args[sym] ||= {}
274
+ given_args[sym][:arg] = arg
275
+ given_args[sym][:negative_given] = negative_given
276
+ given_args[sym][:params] ||= []
277
+
278
+ # The block returns the number of parameters taken.
279
+ num_params_taken = 0
280
+
281
+ unless params.empty?
282
+ if @specs[sym].single_arg?
283
+ given_args[sym][:params] << params[0, 1] # take the first parameter
284
+ num_params_taken = 1
285
+ elsif @specs[sym].multi_arg?
286
+ given_args[sym][:params] << params # take all the parameters
287
+ num_params_taken = params.size
288
+ end
289
+ end
290
+
291
+ num_params_taken
292
+ end
293
+
294
+ ## check for version and help args
295
+ raise VersionNeeded if given_args.include? :version
296
+ raise HelpNeeded if given_args.include? :help
297
+
298
+ ## check constraint satisfaction
299
+ @constraints.each do |type, syms|
300
+ constraint_sym = syms.find { |sym| given_args[sym] }
301
+ next unless constraint_sym
302
+
303
+ case type
304
+ when :depends
305
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym].long} requires --#{@specs[sym].long}" unless given_args.include? sym }
306
+ when :conflicts
307
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym].long} conflicts with --#{@specs[sym].long}" if given_args.include?(sym) && (sym != constraint_sym) }
308
+ end
309
+ end
310
+
311
+ required.each do |sym, val|
312
+ raise CommandlineError, "option --#{@specs[sym].long} must be specified" unless given_args.include? sym
313
+ end
314
+
315
+ ## parse parameters
316
+ given_args.each do |sym, given_data|
317
+ arg, params, negative_given = given_data.values_at :arg, :params, :negative_given
318
+
319
+ opts = @specs[sym]
320
+ if params.empty? && !opts.flag?
321
+ raise CommandlineError, "option '#{arg}' needs a parameter" unless opts.default
322
+ params << (opts.array_default? ? opts.default.clone : [opts.default])
323
+ end
324
+
325
+ vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
326
+
327
+ vals[sym] = opts.parse(params, negative_given)
328
+
329
+ if opts.single_arg?
330
+ if opts.multi? # multiple options, each with a single parameter
331
+ vals[sym] = vals[sym].map { |p| p[0] }
332
+ else # single parameter
333
+ vals[sym] = vals[sym][0][0]
334
+ end
335
+ elsif opts.multi_arg? && !opts.multi?
336
+ vals[sym] = vals[sym][0] # single option, with multiple parameters
337
+ end
338
+ # else: multiple options, with multiple parameters
339
+
340
+ opts.callback.call(vals[sym]) if opts.callback
341
+ end
342
+
343
+ ## modify input in place with only those
344
+ ## arguments we didn't process
345
+ cmdline.clear
346
+ @leftovers.each { |l| cmdline << l }
347
+
348
+ ## allow openstruct-style accessors
349
+ class << vals
350
+ def method_missing(m, *_args)
351
+ self[m] || self[m.to_s]
352
+ end
353
+ end
354
+ vals
355
+ end
356
+
357
+ ## Print the help message to +stream+.
358
+ def educate(stream = $stdout)
359
+ width # hack: calculate it now; otherwise we have to be careful not to
360
+ # call this unless the cursor's at the beginning of a line.
361
+
362
+ left = {}
363
+ @specs.each { |name, spec| left[name] = spec.educate }
364
+
365
+ leftcol_width = left.values.map(&:length).max || 0
366
+ rightcol_start = leftcol_width + 6 # spaces
367
+
368
+ unless @order.size > 0 && @order.first.first == :text
369
+ command_name = File.basename($0).gsub(/\.[^.]+$/, '')
370
+ stream.puts "Usage: #{command_name} #{@usage}\n" if @usage
371
+ stream.puts "#{@synopsis}\n" if @synopsis
372
+ stream.puts if @usage || @synopsis
373
+ stream.puts "#{@version}\n" if @version
374
+ stream.puts "Options:"
375
+ end
376
+
377
+ @order.each do |what, opt|
378
+ if what == :text
379
+ stream.puts wrap(opt)
380
+ next
381
+ end
382
+
383
+ spec = @specs[opt]
384
+ stream.printf " %-#{leftcol_width}s ", left[opt]
385
+ desc = spec.description_with_default
386
+
387
+ stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
388
+ end
389
+ end
390
+
391
+ def width #:nodoc:
392
+ @width ||= if $stdout.tty?
393
+ begin
394
+ require 'io/console'
395
+ w = IO.console.winsize.last
396
+ w.to_i > 0 ? w : 80
397
+ rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF, Errno::EINVAL
398
+ legacy_width
399
+ end
400
+ else
401
+ 80
402
+ end
403
+ end
404
+
405
+ def legacy_width
406
+ # Support for older Rubies where io/console is not available
407
+ `tput cols`.to_i
408
+ rescue Errno::ENOENT
409
+ 80
410
+ end
411
+ private :legacy_width
412
+
413
+ def wrap(str, opts = {}) # :nodoc:
414
+ if str == ""
415
+ [""]
416
+ else
417
+ inner = false
418
+ str.split("\n").map do |s|
419
+ line = wrap_line s, opts.merge(:inner => inner)
420
+ inner = true
421
+ line
422
+ end.flatten
423
+ end
424
+ end
425
+
426
+ ## The per-parser version of Optimist::die (see that for documentation).
427
+ def die(arg, msg = nil, error_code = nil)
428
+ msg, error_code = nil, msg if msg.kind_of?(Integer)
429
+ if msg
430
+ $stderr.puts "Error: argument --#{@specs[arg].long} #{msg}."
431
+ else
432
+ $stderr.puts "Error: #{arg}."
433
+ end
434
+ if @educate_on_error
435
+ $stderr.puts
436
+ educate $stderr
437
+ else
438
+ $stderr.puts "Try --help for help."
439
+ end
440
+ exit(error_code || -1)
441
+ end
442
+
443
+ private
444
+
445
+ ## yield successive arg, parameter pairs
446
+ def each_arg(args)
447
+ remains = []
448
+ i = 0
449
+
450
+ until i >= args.length
451
+ return remains += args[i..-1] if @stop_words.member? args[i]
452
+ case args[i]
453
+ when /^--$/ # arg terminator
454
+ return remains += args[(i + 1)..-1]
455
+ when /^--(\S+?)=(.*)$/ # long argument with equals
456
+ num_params_taken = yield "--#{$1}", [$2]
457
+ if num_params_taken.nil?
458
+ remains << args[i]
459
+ if @stop_on_unknown
460
+ return remains += args[i + 1..-1]
461
+ end
462
+ end
463
+ i += 1
464
+ when /^--(\S+)$/ # long argument
465
+ params = collect_argument_parameters(args, i + 1)
466
+ num_params_taken = yield args[i], params
467
+
468
+ if num_params_taken.nil?
469
+ remains << args[i]
470
+ if @stop_on_unknown
471
+ return remains += args[i + 1..-1]
472
+ end
473
+ else
474
+ i += num_params_taken
475
+ end
476
+ i += 1
477
+ when /^-(\S+)$/ # one or more short arguments
478
+ short_remaining = ""
479
+ shortargs = $1.split(//)
480
+ shortargs.each_with_index do |a, j|
481
+ if j == (shortargs.length - 1)
482
+ params = collect_argument_parameters(args, i + 1)
483
+
484
+ num_params_taken = yield "-#{a}", params
485
+ unless num_params_taken
486
+ short_remaining << a
487
+ if @stop_on_unknown
488
+ remains << "-#{short_remaining}"
489
+ return remains += args[i + 1..-1]
490
+ end
491
+ else
492
+ i += num_params_taken
493
+ end
494
+ else
495
+ unless yield "-#{a}", []
496
+ short_remaining << a
497
+ if @stop_on_unknown
498
+ short_remaining += shortargs[j + 1..-1].join
499
+ remains << "-#{short_remaining}"
500
+ return remains += args[i + 1..-1]
501
+ end
502
+ end
503
+ end
504
+ end
505
+
506
+ unless short_remaining.empty?
507
+ remains << "-#{short_remaining}"
508
+ end
509
+ i += 1
510
+ else
511
+ if @stop_on_unknown
512
+ return remains += args[i..-1]
513
+ else
514
+ remains << args[i]
515
+ i += 1
516
+ end
517
+ end
518
+ end
519
+
520
+ remains
521
+ end
522
+
523
+ def collect_argument_parameters(args, start_at)
524
+ params = []
525
+ pos = start_at
526
+ while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do
527
+ params << args[pos]
528
+ pos += 1
529
+ end
530
+ params
531
+ end
532
+
533
+ def resolve_default_short_options!
534
+ @order.each do |type, name|
535
+ opts = @specs[name]
536
+ next if type != :opt || opts.short
537
+
538
+ c = opts.long.split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
539
+ if c # found a character to use
540
+ opts.short = c
541
+ @short[c] = name
542
+ end
543
+ end
544
+ end
545
+
546
+ def wrap_line(str, opts = {})
547
+ prefix = opts[:prefix] || 0
548
+ width = opts[:width] || (self.width - 1)
549
+ start = 0
550
+ ret = []
551
+ until start > str.length
552
+ nextt =
553
+ if start + width >= str.length
554
+ str.length
555
+ else
556
+ x = str.rindex(/\s/, start + width)
557
+ x = str.index(/\s/, start) if x && x < start
558
+ x || str.length
559
+ end
560
+ ret << ((ret.empty? && !opts[:inner]) ? "" : " " * prefix) + str[start...nextt]
561
+ start = nextt + 1
562
+ end
563
+ ret
564
+ end
565
+
566
+ ## instance_eval but with ability to handle block arguments
567
+ ## thanks to _why: http://redhanded.hobix.com/inspect/aBlockCostume.html
568
+ def cloaker(&b)
569
+ (class << self; self; end).class_eval do
570
+ define_method :cloaker_, &b
571
+ meth = instance_method :cloaker_
572
+ remove_method :cloaker_
573
+ meth
574
+ end
575
+ end
576
+ end
577
+
578
+ class Option
579
+
580
+ attr_accessor :name, :short, :long, :default
581
+ attr_writer :multi_given
582
+
583
+ def initialize
584
+ @long = nil
585
+ @short = nil
586
+ @name = nil
587
+ @multi_given = false
588
+ @hidden = false
589
+ @default = nil
590
+ @optshash = Hash.new()
591
+ end
592
+
593
+ def opts(key)
594
+ @optshash[key]
595
+ end
596
+
597
+ def opts=(o)
598
+ @optshash = o
599
+ end
600
+
601
+ ## Indicates a flag option, which is an option without an argument
602
+ def flag? ; false ; end
603
+ def single_arg?
604
+ !self.multi_arg? && !self.flag?
605
+ end
606
+
607
+ def multi ; @multi_given ; end
608
+ alias multi? multi
609
+
610
+ ## Indicates that this is a multivalued (Array type) argument
611
+ def multi_arg? ; false ; end
612
+ ## note: Option-Types with both multi_arg? and flag? false are single-parameter (normal) options.
613
+
614
+ def array_default? ; self.default.kind_of?(Array) ; end
615
+
616
+ def short? ; short && short != :none ; end
617
+
618
+ def callback ; opts(:callback) ; end
619
+ def desc ; opts(:desc) ; end
620
+
621
+ def required? ; opts(:required) ; end
622
+
623
+ def parse(_paramlist, _neg_given)
624
+ raise NotImplementedError, "parse must be overridden for newly registered type"
625
+ end
626
+
627
+ # provide type-format string. default to empty, but user should probably override it
628
+ def type_format ; "" ; end
629
+
630
+ def educate
631
+ (short? ? "-#{short}, " : "") + "--#{long}" + type_format + (flag? && default ? ", --no-#{long}" : "")
632
+ end
633
+
634
+ ## Format the educate-line description including the default-value(s)
635
+ def description_with_default
636
+ return desc unless default
637
+ default_s = case default
638
+ when $stdout then '<stdout>'
639
+ when $stdin then '<stdin>'
640
+ when $stderr then '<stderr>'
641
+ when Array
642
+ default.join(', ')
643
+ else
644
+ default.to_s
645
+ end
646
+ defword = desc.end_with?('.') ? 'Default' : 'default'
647
+ return "#{desc} (#{defword}: #{default_s})"
648
+ end
649
+
650
+ ## Provide a way to register symbol aliases to the Parser
651
+ def self.register_alias(*alias_keys)
652
+ alias_keys.each do |alias_key|
653
+ # pass in the alias-key and the class
654
+ Parser.register(alias_key, self)
655
+ end
656
+ end
657
+
658
+ ## Factory class methods ...
659
+
660
+ # Determines which type of object to create based on arguments passed
661
+ # to +Optimist::opt+. This is trickier in Optimist, than other cmdline
662
+ # parsers (e.g. Slop) because we allow the +default:+ to be able to
663
+ # set the option's type.
664
+ def self.create(name, desc="", opts={}, settings={})
665
+
666
+ opttype = Optimist::Parser.registry_getopttype(opts[:type])
667
+ opttype_from_default = get_klass_from_default(opts, opttype)
668
+
669
+ 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)
670
+
671
+ opt_inst = (opttype || opttype_from_default || Optimist::BooleanOption.new)
672
+
673
+ ## fill in :long
674
+ opt_inst.long = handle_long_opt(opts[:long], name)
675
+
676
+ ## fill in :short
677
+ opt_inst.short = handle_short_opt(opts[:short])
678
+
679
+ ## fill in :multi
680
+ multi_given = opts[:multi] || false
681
+ opt_inst.multi_given = multi_given
682
+
683
+ ## fill in :default for flags
684
+ defvalue = opts[:default] || opt_inst.default
685
+
686
+ ## autobox :default for :multi (multi-occurrence) arguments
687
+ defvalue = [defvalue] if defvalue && multi_given && !defvalue.kind_of?(Array)
688
+ opt_inst.default = defvalue
689
+ opt_inst.name = name
690
+ opt_inst.opts = opts
691
+ opt_inst
692
+ end
693
+
694
+ private
695
+
696
+ def self.get_type_from_disdef(optdef, opttype, disambiguated_default)
697
+ if disambiguated_default.is_a? Array
698
+ return(optdef.first.class.name.downcase + "s") if !optdef.empty?
699
+ if opttype
700
+ raise ArgumentError, "multiple argument type must be plural" unless opttype.multi_arg?
701
+ return nil
702
+ else
703
+ raise ArgumentError, "multiple argument type cannot be deduced from an empty array"
704
+ end
705
+ end
706
+ return disambiguated_default.class.name.downcase
707
+ end
708
+
709
+ def self.get_klass_from_default(opts, opttype)
710
+ ## for options with :multi => true, an array default doesn't imply
711
+ ## a multi-valued argument. for that you have to specify a :type
712
+ ## as well. (this is how we disambiguate an ambiguous situation;
713
+ ## see the docs for Parser#opt for details.)
714
+
715
+ disambiguated_default = if opts[:multi] && opts[:default].is_a?(Array) && opttype.nil?
716
+ opts[:default].first
717
+ else
718
+ opts[:default]
719
+ end
720
+
721
+ return nil if disambiguated_default.nil?
722
+ type_from_default = get_type_from_disdef(opts[:default], opttype, disambiguated_default)
723
+ return Optimist::Parser.registry_getopttype(type_from_default)
724
+ end
725
+
726
+ def self.handle_long_opt(lopt, name)
727
+ lopt = lopt ? lopt.to_s : name.to_s.gsub("_", "-")
728
+ lopt = case lopt
729
+ when /^--([^-].*)$/ then $1
730
+ when /^[^-]/ then lopt
731
+ else raise ArgumentError, "invalid long option name #{lopt.inspect}"
732
+ end
733
+ end
734
+
735
+ def self.handle_short_opt(sopt)
736
+ sopt = sopt.to_s if sopt && sopt != :none
737
+ sopt = case sopt
738
+ when /^-(.)$/ then $1
739
+ when nil, :none, /^.$/ then sopt
740
+ else raise ArgumentError, "invalid short option name '#{sopt.inspect}'"
741
+ end
742
+
743
+ if sopt
744
+ raise ArgumentError, "a short option name can't be a number or a dash" if sopt =~ ::Optimist::Parser::INVALID_SHORT_ARG_REGEX
745
+ end
746
+ return sopt
747
+ end
748
+
749
+ end
750
+
751
+ # Flag option. Has no arguments. Can be negated with "no-".
752
+ class BooleanOption < Option
753
+ register_alias :flag, :bool, :boolean, :trueclass, :falseclass
754
+ def initialize
755
+ super()
756
+ @default = false
757
+ end
758
+ def flag? ; true ; end
759
+ def parse(_paramlist, neg_given)
760
+ return(self.name.to_s =~ /^no_/ ? neg_given : !neg_given)
761
+ end
762
+ end
763
+
764
+ # Floating point number option class.
765
+ class FloatOption < Option
766
+ register_alias :float, :double
767
+ def type_format ; "=<f>" ; end
768
+ def parse(paramlist, _neg_given)
769
+ paramlist.map do |pg|
770
+ pg.map do |param|
771
+ raise CommandlineError, "option '#{self.name}' needs a floating-point number" unless param.is_a?(Numeric) || param =~ FLOAT_RE
772
+ param.to_f
773
+ end
774
+ end
775
+ end
776
+ end
777
+
778
+ # Integer number option class.
779
+ class IntegerOption < Option
780
+ register_alias :int, :integer, :fixnum
781
+ def type_format ; "=<i>" ; end
782
+ def parse(paramlist, _neg_given)
783
+ paramlist.map do |pg|
784
+ pg.map do |param|
785
+ raise CommandlineError, "option '#{self.name}' needs an integer" unless param.is_a?(Numeric) || param =~ /^-?[\d_]+$/
786
+ param.to_i
787
+ end
788
+ end
789
+ end
790
+ end
791
+
792
+ # Option class for handling IO objects and URLs.
793
+ # Note that this will return the file-handle, not the file-name
794
+ # in the case of file-paths given to it.
795
+ class IOOption < Option
796
+ register_alias :io
797
+ def type_format ; "=<filename/uri>" ; end
798
+ def parse(paramlist, _neg_given)
799
+ paramlist.map do |pg|
800
+ pg.map do |param|
801
+ if param =~ /^(stdin|-)$/i
802
+ $stdin
803
+ else
804
+ require 'open-uri'
805
+ begin
806
+ open param
807
+ rescue SystemCallError => e
808
+ raise CommandlineError, "file or url for option '#{self.name}' cannot be opened: #{e.message}"
809
+ end
810
+ end
811
+ end
812
+ end
813
+ end
814
+ end
815
+
816
+ # Option class for handling Strings.
817
+ class StringOption < Option
818
+ register_alias :string
819
+ def type_format ; "=<s>" ; end
820
+ def parse(paramlist, _neg_given)
821
+ paramlist.map { |pg| pg.map(&:to_s) }
822
+ end
823
+ end
824
+
825
+ # Option for dates. Uses Chronic if it exists.
826
+ class DateOption < Option
827
+ register_alias :date
828
+ def type_format ; "=<date>" ; end
829
+ def parse(paramlist, _neg_given)
830
+ paramlist.map do |pg|
831
+ pg.map do |param|
832
+ next param if param.is_a?(Date)
833
+ begin
834
+ begin
835
+ require 'chronic'
836
+ time = Chronic.parse(param)
837
+ rescue LoadError
838
+ # chronic is not available
839
+ end
840
+ time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
841
+ rescue ArgumentError
842
+ raise CommandlineError, "option '#{self.name}' needs a date"
843
+ end
844
+ end
845
+ end
846
+ end
847
+ end
848
+
849
+ ### MULTI_OPT_TYPES :
850
+ ## The set of values that indicate a multiple-parameter option (i.e., that
851
+ ## takes multiple space-separated values on the commandline) when passed as
852
+ ## the +:type+ parameter of #opt.
853
+
854
+ # Option class for handling multiple Integers
855
+ class IntegerArrayOption < IntegerOption
856
+ register_alias :fixnums, :ints, :integers
857
+ def type_format ; "=<i+>" ; end
858
+ def multi_arg? ; true ; end
859
+ end
860
+
861
+ # Option class for handling multiple Floats
862
+ class FloatArrayOption < FloatOption
863
+ register_alias :doubles, :floats
864
+ def type_format ; "=<f+>" ; end
865
+ def multi_arg? ; true ; end
866
+ end
867
+
868
+ # Option class for handling multiple Strings
869
+ class StringArrayOption < StringOption
870
+ register_alias :strings
871
+ def type_format ; "=<s+>" ; end
872
+ def multi_arg? ; true ; end
873
+ end
874
+
875
+ # Option class for handling multiple dates
876
+ class DateArrayOption < DateOption
877
+ register_alias :dates
878
+ def type_format ; "=<date+>" ; end
879
+ def multi_arg? ; true ; end
880
+ end
881
+
882
+ # Option class for handling Files/URLs via 'open'
883
+ class IOArrayOption < IOOption
884
+ register_alias :ios
885
+ def type_format ; "=<filename/uri+>" ; end
886
+ def multi_arg? ; true ; end
887
+ end
888
+
889
+ ## The easy, syntactic-sugary entry method into Optimist. Creates a Parser,
890
+ ## passes the block to it, then parses +args+ with it, handling any errors or
891
+ ## requests for help or version information appropriately (and then exiting).
892
+ ## Modifies +args+ in place. Returns a hash of option values.
893
+ ##
894
+ ## The block passed in should contain zero or more calls to +opt+
895
+ ## (Parser#opt), zero or more calls to +text+ (Parser#text), and
896
+ ## probably a call to +version+ (Parser#version).
897
+ ##
898
+ ## The returned block contains a value for every option specified with
899
+ ## +opt+. The value will be the value given on the commandline, or the
900
+ ## default value if the option was not specified on the commandline. For
901
+ ## every option specified on the commandline, a key "<option
902
+ ## name>_given" will also be set in the hash.
903
+ ##
904
+ ## Example:
905
+ ##
906
+ ## require 'optimist'
907
+ ## opts = Optimist::options do
908
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
909
+ ## opt :name, "Monkey name", :type => :string # a string --name <s>, defaulting to nil
910
+ ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4
911
+ ## end
912
+ ##
913
+ ## ## if called with no arguments
914
+ ## p opts # => {:monkey=>false, :name=>nil, :num_limbs=>4, :help=>false}
915
+ ##
916
+ ## ## if called with --monkey
917
+ ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
918
+ ##
919
+ ## See more examples at http://optimist.rubyforge.org.
920
+ def options(args = ARGV, *a, &b)
921
+ @last_parser = Parser.new(*a, &b)
922
+ with_standard_exception_handling(@last_parser) { @last_parser.parse args }
923
+ end
924
+
925
+ ## If Optimist::options doesn't do quite what you want, you can create a Parser
926
+ ## object and call Parser#parse on it. That method will throw CommandlineError,
927
+ ## HelpNeeded and VersionNeeded exceptions when necessary; if you want to
928
+ ## have these handled for you in the standard manner (e.g. show the help
929
+ ## and then exit upon an HelpNeeded exception), call your code from within
930
+ ## a block passed to this method.
931
+ ##
932
+ ## Note that this method will call System#exit after handling an exception!
933
+ ##
934
+ ## Usage example:
935
+ ##
936
+ ## require 'optimist'
937
+ ## p = Optimist::Parser.new do
938
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
939
+ ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true
940
+ ## end
941
+ ##
942
+ ## opts = Optimist::with_standard_exception_handling p do
943
+ ## o = p.parse ARGV
944
+ ## raise Optimist::HelpNeeded if ARGV.empty? # show help screen
945
+ ## o
946
+ ## end
947
+ ##
948
+ ## Requires passing in the parser object.
949
+
950
+ def with_standard_exception_handling(parser)
951
+ yield
952
+ rescue CommandlineError => e
953
+ parser.die(e.message, nil, e.error_code)
954
+ rescue HelpNeeded
955
+ parser.educate
956
+ exit
957
+ rescue VersionNeeded
958
+ puts parser.version
959
+ exit
960
+ end
961
+
962
+ ## Informs the user that their usage of 'arg' was wrong, as detailed by
963
+ ## 'msg', and dies. Example:
964
+ ##
965
+ ## options do
966
+ ## opt :volume, :default => 0.0
967
+ ## end
968
+ ##
969
+ ## die :volume, "too loud" if opts[:volume] > 10.0
970
+ ## die :volume, "too soft" if opts[:volume] < 0.1
971
+ ##
972
+ ## In the one-argument case, simply print that message, a notice
973
+ ## about -h, and die. Example:
974
+ ##
975
+ ## options do
976
+ ## opt :whatever # ...
977
+ ## end
978
+ ##
979
+ ## Optimist::die "need at least one filename" if ARGV.empty?
980
+ ##
981
+ ## An exit code can be provide if needed
982
+ ##
983
+ ## Optimist::die "need at least one filename", -2 if ARGV.empty?
984
+ def die(arg, msg = nil, error_code = nil)
985
+ if @last_parser
986
+ @last_parser.die arg, msg, error_code
987
+ else
988
+ raise ArgumentError, "Optimist::die can only be called after Optimist::options"
989
+ end
990
+ end
991
+
992
+ ## Displays the help message and dies. Example:
993
+ ##
994
+ ## options do
995
+ ## opt :volume, :default => 0.0
996
+ ## banner <<-EOS
997
+ ## Usage:
998
+ ## #$0 [options] <name>
999
+ ## where [options] are:
1000
+ ## EOS
1001
+ ## end
1002
+ ##
1003
+ ## Optimist::educate if ARGV.empty?
1004
+ def educate
1005
+ if @last_parser
1006
+ @last_parser.educate
1007
+ exit
1008
+ else
1009
+ raise ArgumentError, "Optimist::educate can only be called after Optimist::options"
1010
+ end
1011
+ end
1012
+
1013
+ module_function :options, :die, :educate, :with_standard_exception_handling
1014
+ end # module