optimist 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/optimist.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # lib/optimist.rb -- optimist command-line processing library
2
4
  # Copyright (c) 2008-2014 William Morgan.
3
5
  # Copyright (c) 2014 Red Hat, Inc.
@@ -6,7 +8,7 @@
6
8
  require 'date'
7
9
 
8
10
  module Optimist
9
- VERSION = "3.1.0"
11
+ VERSION = "3.2.0"
10
12
 
11
13
  ## Thrown by Parser in the event of a commandline error. Not needed if
12
14
  ## you're using the Optimist::options entry.
@@ -35,6 +37,50 @@ FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
35
37
  ## Regex for parameters
36
38
  PARAM_RE = /^-(-|\.$|[^\d\.])/
37
39
 
40
+ # Abstract class for a constraint. Do not use by itself.
41
+ class Constraint
42
+ def initialize(syms)
43
+ @idents = syms
44
+ end
45
+ def validate(given_args:, specs:)
46
+ overlap = @idents & given_args.keys
47
+ if error_condition(overlap.size)
48
+ longargs = @idents.map { |sym| "--#{specs[sym].long.long}" }
49
+ raise CommandlineError, error_message(longargs)
50
+ end
51
+ end
52
+ end
53
+
54
+ # A Dependency constraint. Useful when Option A requires Option B also be used.
55
+ class DependConstraint < Constraint
56
+ def error_condition(overlap_size)
57
+ (overlap_size != 0) && (overlap_size != @idents.size)
58
+ end
59
+ def error_message(longargs)
60
+ "#{longargs.join(', ')} have a dependency and must be given together"
61
+ end
62
+ end
63
+
64
+ # A Conflict constraint. Useful when Option A cannot be used with Option B.
65
+ class ConflictConstraint < Constraint
66
+ def error_condition(overlap_size)
67
+ (overlap_size != 0) && (overlap_size != 1)
68
+ end
69
+ def error_message(longargs)
70
+ "only one of #{longargs.join(', ')} can be given"
71
+ end
72
+ end
73
+
74
+ # An Either-Or constraint. For Mutually exclusive options
75
+ class EitherConstraint < Constraint
76
+ def error_condition(overlap_size)
77
+ overlap_size != 1
78
+ end
79
+ def error_message(longargs)
80
+ "one and only one of #{longargs.join(', ')} is required"
81
+ end
82
+ end
83
+
38
84
  ## The commandline parser. In typical usage, the methods in this class
39
85
  ## will be handled internally by Optimist::options. In this case, only the
40
86
  ## #opt, #banner and #version, #depends, and #conflicts methods will
@@ -68,8 +114,6 @@ class Parser
68
114
  return @registry[lookup].new
69
115
  end
70
116
 
71
- INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
72
-
73
117
  ## The values from the commandline that were not interpreted by #parse.
74
118
  attr_reader :leftovers
75
119
 
@@ -82,6 +126,12 @@ class Parser
82
126
  ## ignore options that it does not recognize.
83
127
  attr_accessor :ignore_invalid_options
84
128
 
129
+ DEFAULT_SETTINGS = {
130
+ exact_match: true,
131
+ implicit_short_opts: true,
132
+ suggestions: true
133
+ }
134
+
85
135
  ## Initializes the parser, and instance-evaluates any block given.
86
136
  def initialize(*a, &b)
87
137
  @version = nil
@@ -97,8 +147,17 @@ class Parser
97
147
  @synopsis = nil
98
148
  @usage = nil
99
149
 
100
- # instance_eval(&b) if b # can't take arguments
101
- cloaker(&b).bind(self).call(*a) if b
150
+ ## allow passing settings through Parser.new as an optional hash.
151
+ ## but keep compatibility with non-hashy args, though.
152
+ begin
153
+ settings_hash = Hash[*a]
154
+ @settings = DEFAULT_SETTINGS.merge(settings_hash)
155
+ a=[] ## clear out args if using as settings-hash
156
+ rescue ArgumentError
157
+ @settings = DEFAULT_SETTINGS
158
+ end
159
+
160
+ self.instance_exec(*a, &b) if block_given?
102
161
  end
103
162
 
104
163
  ## Define an option. +name+ is the option name, a unique identifier
@@ -114,6 +173,7 @@ class Parser
114
173
  ## [+: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+.
115
174
  ## [+:required+] If set to +true+, the argument must be provided on the commandline.
116
175
  ## [+: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.)
176
+ ## [+:permitted+] Specify an Array of permitted values for an option. If the user provides a value outside this list, an error is thrown.
117
177
  ##
118
178
  ## Note that there are two types of argument multiplicity: an argument
119
179
  ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
@@ -148,10 +208,19 @@ class Parser
148
208
  o = Option.create(name, desc, opts)
149
209
 
150
210
  raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? o.name
151
- raise ArgumentError, "long option name #{o.long.inspect} is already taken; please specify a (different) :long" if @long[o.long]
152
- raise ArgumentError, "short option name #{o.short.inspect} is already taken; please specify a (different) :short" if @short[o.short]
153
- @long[o.long] = o.name
154
- @short[o.short] = o.name if o.short?
211
+
212
+ o.long.names.each do |lng|
213
+ raise ArgumentError, "long option name #{lng.inspect} is already taken; please specify a (different) :long/:alt" if @long[lng]
214
+ @long[lng] = o.name
215
+ end
216
+
217
+ o.short.chars.each do |short|
218
+ raise ArgumentError, "short option name #{short.inspect} is already taken; please specify a (different) :short" if @short[short]
219
+ @short[short] = o.name
220
+ end
221
+
222
+ raise ArgumentError, "permitted values for option #{o.long.long.inspect} must be either nil, Range, Regexp or an Array;" unless o.permitted_type_valid?
223
+
155
224
  @specs[o.name] = o
156
225
  @order << [:opt, o.name]
157
226
  end
@@ -188,20 +257,19 @@ class Parser
188
257
  ## better modeled with Optimist::die.
189
258
  def depends(*syms)
190
259
  syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
191
- @constraints << [:depends, syms]
260
+ @constraints << DependConstraint.new(syms)
192
261
  end
193
262
 
194
263
  ## Marks two (or more!) options as conflicting.
195
264
  def conflicts(*syms)
196
265
  syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
197
- @constraints << [:conflicts, syms]
266
+ @constraints << ConflictConstraint.new(syms)
198
267
  end
199
268
 
200
269
  ## Marks two (or more!) options as required but mutually exclusive.
201
270
  def either(*syms)
202
271
  syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
203
- @constraints << [:conflicts, syms]
204
- @constraints << [:either, syms]
272
+ @constraints << EitherConstraint.new(syms)
205
273
  end
206
274
 
207
275
  ## Defines a set of words which cause parsing to terminate when
@@ -231,6 +299,56 @@ class Parser
231
299
  @educate_on_error = true
232
300
  end
233
301
 
302
+ ## Match long variables with inexact match.
303
+ ## If we hit a complete match, then use that, otherwise see how many long-options partially match.
304
+ ## If only one partially matches, then we can safely use that.
305
+ ## Otherwise, we raise an error that the partially given option was ambiguous.
306
+ def perform_inexact_match(arg, partial_match) # :nodoc:
307
+ return @long[partial_match] if @long.has_key?(partial_match)
308
+ partially_matched_keys = @long.keys.select { |opt| opt.start_with?(partial_match) }
309
+ case partially_matched_keys.size
310
+ when 0 ; nil
311
+ when 1 ; @long[partially_matched_keys.first]
312
+ else ; raise CommandlineError, "ambiguous option '#{arg}' matched keys (#{partially_matched_keys.join(',')})"
313
+ end
314
+ end
315
+ private :perform_inexact_match
316
+
317
+ def handle_unknown_argument(arg, candidates, suggestions)
318
+ errstring = "unknown argument '#{arg}'"
319
+ if (suggestions &&
320
+ Module::const_defined?("DidYouMean") &&
321
+ Module::const_defined?("DidYouMean::JaroWinkler") &&
322
+ Module::const_defined?("DidYouMean::Levenshtein"))
323
+ input = arg.sub(/^[-]*/,'')
324
+
325
+ # Code borrowed from did_you_mean gem
326
+ jw_threshold = 0.75
327
+ seed = candidates.select {|candidate| DidYouMean::JaroWinkler.distance(candidate, input) >= jw_threshold } \
328
+ .sort_by! {|candidate| DidYouMean::JaroWinkler.distance(candidate.to_s, input) } \
329
+ .reverse!
330
+ # Correct mistypes
331
+ threshold = (input.length * 0.25).ceil
332
+ has_mistype = seed.rindex {|c| DidYouMean::Levenshtein.distance(c, input) <= threshold }
333
+ corrections = if has_mistype
334
+ seed.take(has_mistype + 1)
335
+ else
336
+ # Correct misspells
337
+ seed.select do |candidate|
338
+ length = input.length < candidate.length ? input.length : candidate.length
339
+
340
+ DidYouMean::Levenshtein.distance(candidate, input) < length
341
+ end.first(1)
342
+ end
343
+ unless corrections.empty?
344
+ dashdash_corrections = corrections.map{|s| "--#{s}" }
345
+ errstring += ". Did you mean: [#{dashdash_corrections.join(', ')}] ?"
346
+ end
347
+ end
348
+ raise CommandlineError, errstring
349
+ end
350
+ private :handle_unknown_argument
351
+
234
352
  ## Parses the commandline. Typically called by Optimist::options,
235
353
  ## but you can call it directly if you need more control.
236
354
  ##
@@ -248,7 +366,7 @@ class Parser
248
366
  vals[sym] = [] if opts.multi && !opts.default # multi arguments default to [], not nil
249
367
  end
250
368
 
251
- resolve_default_short_options!
369
+ resolve_default_short_options! if @settings[:implicit_short_opts]
252
370
 
253
371
  ## resolve symbols
254
372
  given_args = {}
@@ -266,10 +384,15 @@ class Parser
266
384
  else raise CommandlineError, "invalid argument syntax: '#{arg}'"
267
385
  end
268
386
 
269
- sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments
387
+ if arg.start_with?("--no-") # explicitly invalidate --no-no- arguments
388
+ sym = nil
389
+ ## Support inexact matching of long-arguments like perl's Getopt::Long
390
+ elsif !sym && !@settings[:exact_match] && arg.match(/^--(\S+)$/)
391
+ sym = perform_inexact_match(arg, $1)
392
+ end
270
393
 
271
394
  next nil if ignore_invalid_options && !sym
272
- raise CommandlineError, "unknown argument '#{arg}'" unless sym
395
+ handle_unknown_argument(arg, @long.keys, @settings[:suggestions]) unless sym
273
396
 
274
397
  if given_args.include?(sym) && !@specs[sym].multi?
275
398
  raise CommandlineError, "option '#{arg}' specified multiple times"
@@ -301,23 +424,12 @@ class Parser
301
424
  raise HelpNeeded if given_args.include? :help
302
425
 
303
426
  ## check constraint satisfaction
304
- @constraints.each do |type, syms|
305
- constraint_sym = syms.find { |sym| given_args[sym] }
306
-
307
- case type
308
- when :depends
309
- next unless constraint_sym
310
- syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym].long} requires --#{@specs[sym].long}" unless given_args.include? sym }
311
- when :conflicts
312
- next unless constraint_sym
313
- syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym].long} conflicts with --#{@specs[sym].long}" if given_args.include?(sym) && (sym != constraint_sym) }
314
- when :either
315
- raise CommandlineError, "one of #{syms.map { |sym| "--#{@specs[sym].long}" }.join(', ') } is required" if (syms & given_args.keys).size != 1
316
- end
427
+ @constraints.each do |const|
428
+ const.validate(given_args: given_args, specs: @specs)
317
429
  end
318
430
 
319
431
  required.each do |sym, val|
320
- raise CommandlineError, "option --#{@specs[sym].long} must be specified" unless given_args.include? sym
432
+ raise CommandlineError, "option --#{@specs[sym].long.long} must be specified" unless given_args.include? sym
321
433
  end
322
434
 
323
435
  ## parse parameters
@@ -330,6 +442,12 @@ class Parser
330
442
  params << (opts.array_default? ? opts.default.clone : [opts.default])
331
443
  end
332
444
 
445
+ if params.first && opts.permitted
446
+ params.first.each do |val|
447
+ opts.validate_permitted(arg, val)
448
+ end
449
+ end
450
+
333
451
  vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
334
452
 
335
453
  vals[sym] = opts.parse(params, negative_given)
@@ -390,7 +508,7 @@ class Parser
390
508
 
391
509
  spec = @specs[opt]
392
510
  stream.printf " %-#{leftcol_width}s ", left[opt]
393
- desc = spec.description_with_default
511
+ desc = spec.full_description
394
512
 
395
513
  stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
396
514
  end
@@ -435,7 +553,7 @@ class Parser
435
553
  def die(arg, msg = nil, error_code = nil)
436
554
  msg, error_code = nil, msg if msg.kind_of?(Integer)
437
555
  if msg
438
- $stderr.puts "Error: argument --#{@specs[arg].long} #{msg}."
556
+ $stderr.puts "Error: argument --#{@specs[arg].long.long} #{msg}."
439
557
  else
440
558
  $stderr.puts "Error: #{arg}."
441
559
  end
@@ -458,7 +576,7 @@ private
458
576
  until i >= args.length
459
577
  return remains += args[i..-1] if @stop_words.member? args[i]
460
578
  case args[i]
461
- when /^--$/ # arg terminator
579
+ when "--" # arg terminator
462
580
  return remains += args[(i + 1)..-1]
463
581
  when /^--(\S+?)=(.*)$/ # long argument with equals
464
582
  num_params_taken = yield "--#{$1}", [$2]
@@ -483,7 +601,7 @@ private
483
601
  end
484
602
  i += 1
485
603
  when /^-(\S+)$/ # one or more short arguments
486
- short_remaining = ""
604
+ short_remaining = []
487
605
  shortargs = $1.split(//)
488
606
  shortargs.each_with_index do |a, j|
489
607
  if j == (shortargs.length - 1)
@@ -493,7 +611,7 @@ private
493
611
  unless num_params_taken
494
612
  short_remaining << a
495
613
  if @stop_on_unknown
496
- remains << "-#{short_remaining}"
614
+ remains << "-#{short_remaining.join}"
497
615
  return remains += args[i + 1..-1]
498
616
  end
499
617
  else
@@ -503,8 +621,8 @@ private
503
621
  unless yield "-#{a}", []
504
622
  short_remaining << a
505
623
  if @stop_on_unknown
506
- short_remaining += shortargs[j + 1..-1].join
507
- remains << "-#{short_remaining}"
624
+ short_remaining << shortargs[j + 1..-1].join
625
+ remains << "-#{short_remaining.join}"
508
626
  return remains += args[i + 1..-1]
509
627
  end
510
628
  end
@@ -512,7 +630,7 @@ private
512
630
  end
513
631
 
514
632
  unless short_remaining.empty?
515
- remains << "-#{short_remaining}"
633
+ remains << "-#{short_remaining.join}"
516
634
  end
517
635
  i += 1
518
636
  else
@@ -541,11 +659,10 @@ private
541
659
  def resolve_default_short_options!
542
660
  @order.each do |type, name|
543
661
  opts = @specs[name]
544
- next if type != :opt || opts.short
545
-
546
- c = opts.long.split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
662
+ next if type != :opt || opts.doesnt_need_autogen_short
663
+ c = opts.long.long.split(//).find { |d| d !~ Optimist::ShortNames::INVALID_ARG_REGEX && !@short.member?(d) }
547
664
  if c # found a character to use
548
- opts.short = c
665
+ opts.short.add c
549
666
  @short[c] = name
550
667
  end
551
668
  end
@@ -571,30 +688,94 @@ private
571
688
  ret
572
689
  end
573
690
 
574
- ## instance_eval but with ability to handle block arguments
575
- ## thanks to _why: http://redhanded.hobix.com/inspect/aBlockCostume.html
576
- def cloaker(&b)
577
- (class << self; self; end).class_eval do
578
- define_method :cloaker_, &b
579
- meth = instance_method :cloaker_
580
- remove_method :cloaker_
581
- meth
691
+ end
692
+
693
+ class LongNames
694
+ def initialize
695
+ @truename = nil
696
+ @long = nil
697
+ @alts = []
698
+ end
699
+
700
+ def make_valid(lopt)
701
+ return nil if lopt.nil?
702
+ case lopt.to_s
703
+ when /^--([^-].*)$/ then $1
704
+ when /^[^-]/ then lopt.to_s
705
+ else raise ArgumentError, "invalid long option name #{lopt.inspect}"
582
706
  end
583
707
  end
708
+
709
+ def set(name, lopt, alts)
710
+ @truename = name
711
+ lopt = lopt ? lopt.to_s : name.to_s.gsub("_", "-")
712
+ @long = make_valid(lopt)
713
+ alts = [alts] unless alts.is_a?(Array) # box the value
714
+ @alts = alts.map { |alt| make_valid(alt) }.compact
715
+ end
716
+
717
+ # long specified with :long has precedence over the true-name
718
+ def long ; @long || @truename ; end
719
+
720
+ # all valid names, including alts
721
+ def names
722
+ [long] + @alts
723
+ end
724
+
725
+ end
726
+
727
+ class ShortNames
728
+
729
+ INVALID_ARG_REGEX = /[\d-]/ #:nodoc:
730
+
731
+ def initialize
732
+ @chars = []
733
+ @auto = true
734
+ end
735
+
736
+ attr_reader :chars, :auto
737
+
738
+ def add(values)
739
+ values = [values] unless values.is_a?(Array) # box the value
740
+ values = values.compact
741
+ if values.include?(:none)
742
+ if values.size == 1
743
+ @auto = false
744
+ return
745
+ end
746
+ raise ArgumentError, "Cannot use :none with any other values in short option: #{values.inspect}"
747
+ end
748
+ values.each do |val|
749
+ strval = val.to_s
750
+ sopt = case strval
751
+ when /^-(.)$/ then $1
752
+ when /^.$/ then strval
753
+ else raise ArgumentError, "invalid short option name '#{val.inspect}'"
754
+ end
755
+
756
+ if sopt =~ INVALID_ARG_REGEX
757
+ raise ArgumentError, "short option name '#{sopt}' can't be a number or a dash"
758
+ end
759
+ @chars << sopt
760
+ end
761
+ end
762
+
584
763
  end
585
764
 
586
765
  class Option
587
766
 
588
- attr_accessor :name, :short, :long, :default
767
+ attr_accessor :name, :short, :long, :default, :permitted, :permitted_response
589
768
  attr_writer :multi_given
590
769
 
591
770
  def initialize
592
- @long = nil
593
- @short = nil
771
+ @long = LongNames.new
772
+ @short = ShortNames.new # can be an Array of one-char strings, a one-char String, nil or :none
594
773
  @name = nil
595
774
  @multi_given = false
596
775
  @hidden = false
597
776
  @default = nil
777
+ @permitted = nil
778
+ @permitted_response = "option '%{arg}' only accepts %{valid_string}"
598
779
  @optshash = Hash.new()
599
780
  end
600
781
 
@@ -621,7 +802,7 @@ class Option
621
802
 
622
803
  def array_default? ; self.default.kind_of?(Array) ; end
623
804
 
624
- def short? ; short && short != :none ; end
805
+ def doesnt_need_autogen_short ; !short.auto || short.chars.any? ; end
625
806
 
626
807
  def callback ; opts(:callback) ; end
627
808
  def desc ; opts(:desc) ; end
@@ -636,23 +817,96 @@ class Option
636
817
  def type_format ; "" ; end
637
818
 
638
819
  def educate
639
- (short? ? "-#{short}, " : "") + "--#{long}" + type_format + (flag? && default ? ", --no-#{long}" : "")
820
+ optionlist = []
821
+ optionlist.concat(short.chars.map { |o| "-#{o}" })
822
+ optionlist.concat(long.names.map { |o| "--#{o}" })
823
+ optionlist.compact.join(', ') + type_format + (flag? && default ? ", --no-#{long}" : "")
640
824
  end
641
825
 
642
- ## Format the educate-line description including the default-value(s)
643
- def description_with_default
644
- return desc unless default
826
+ ## Format the educate-line description including the default and permitted value(s)
827
+ def full_description
828
+ desc_str = desc
829
+ desc_str += default_description_str(desc) if default
830
+ desc_str += permitted_description_str(desc) if permitted
831
+ desc_str
832
+ end
833
+
834
+ ## Format stdio like objects to a string
835
+ def format_stdio(obj)
836
+ case obj
837
+ when $stdout then '<stdout>'
838
+ when $stdin then '<stdin>'
839
+ when $stderr then '<stderr>'
840
+ else obj # pass-through-case
841
+ end
842
+ end
843
+
844
+ ## Generate the default value string for the educate line
845
+ private def default_description_str str
645
846
  default_s = case default
646
- when $stdout then '<stdout>'
647
- when $stdin then '<stdin>'
648
- when $stderr then '<stderr>'
649
847
  when Array
650
848
  default.join(', ')
651
849
  else
652
- default.to_s
850
+ format_stdio(default).to_s
653
851
  end
654
- defword = desc.end_with?('.') ? 'Default' : 'default'
655
- return "#{desc} (#{defword}: #{default_s})"
852
+ defword = str.end_with?('.') ? 'Default' : 'default'
853
+ " (#{defword}: #{default_s})"
854
+ end
855
+
856
+ def permitted_valid_string
857
+ case permitted
858
+ when Array
859
+ return "one of: " + permitted.to_a.map(&:to_s).join(', ')
860
+ when Range
861
+ return "value in range of: #{permitted}"
862
+ when Regexp
863
+ return "value matching: #{permitted.inspect}"
864
+ end
865
+ raise NotImplementedError, "invalid branch"
866
+ end
867
+
868
+ def permitted_type_valid?
869
+ case permitted
870
+ when NilClass, Array, Range, Regexp then true
871
+ else false
872
+ end
873
+ end
874
+
875
+ def validate_permitted(arg, value)
876
+ return true if permitted.nil?
877
+ unless permitted_value?(value)
878
+ format_hash = {arg: arg, given: value, value: value, valid_string: permitted_valid_string(), permitted: permitted }
879
+ raise CommandlineError, permitted_response % format_hash
880
+ end
881
+ true
882
+ end
883
+
884
+ # incoming values from the command-line should be strings, so we should
885
+ # stringify any permitted types as the basis of comparison.
886
+ def permitted_value?(val)
887
+ case permitted
888
+ when nil then true
889
+ when Regexp then val.match? permitted
890
+ when Range then permitted.include? as_type(val)
891
+ when Array then permitted.map(&:to_s).include? val
892
+ else false
893
+ end
894
+ end
895
+
896
+ ## Generate the permitted values string for the educate line
897
+ private def permitted_description_str str
898
+ permitted_s = case permitted
899
+ when Array
900
+ permitted.map do |p|
901
+ format_stdio(p).to_s
902
+ end.join(', ')
903
+ when Range, Regexp
904
+ permitted.inspect
905
+ else
906
+ raise NotImplementedError
907
+ end
908
+ permword = str.end_with?('.') ? 'Permitted' : 'permitted'
909
+ " (#{permword}: #{permitted_s})"
656
910
  end
657
911
 
658
912
  ## Provide a way to register symbol aliases to the Parser
@@ -679,10 +933,10 @@ class Option
679
933
  opt_inst = (opttype || opttype_from_default || Optimist::BooleanOption.new)
680
934
 
681
935
  ## fill in :long
682
- opt_inst.long = handle_long_opt(opts[:long], name)
936
+ opt_inst.long.set(name, opts[:long], opts[:alt])
683
937
 
684
938
  ## fill in :short
685
- opt_inst.short = handle_short_opt(opts[:short])
939
+ opt_inst.short.add opts[:short]
686
940
 
687
941
  ## fill in :multi
688
942
  multi_given = opts[:multi] || false
@@ -691,8 +945,13 @@ class Option
691
945
  ## fill in :default for flags
692
946
  defvalue = opts[:default] || opt_inst.default
693
947
 
948
+ ## fill in permitted values
949
+ permitted = opts[:permitted] || nil
950
+
694
951
  ## autobox :default for :multi (multi-occurrence) arguments
695
952
  defvalue = [defvalue] if defvalue && multi_given && !defvalue.kind_of?(Array)
953
+ opt_inst.permitted = permitted
954
+ opt_inst.permitted_response = opts[:permitted_response] if opts[:permitted_response]
696
955
  opt_inst.default = defvalue
697
956
  opt_inst.name = name
698
957
  opt_inst.opts = opts
@@ -731,29 +990,6 @@ class Option
731
990
  return Optimist::Parser.registry_getopttype(type_from_default)
732
991
  end
733
992
 
734
- def self.handle_long_opt(lopt, name)
735
- lopt = lopt ? lopt.to_s : name.to_s.gsub("_", "-")
736
- lopt = case lopt
737
- when /^--([^-].*)$/ then $1
738
- when /^[^-]/ then lopt
739
- else raise ArgumentError, "invalid long option name #{lopt.inspect}"
740
- end
741
- end
742
-
743
- def self.handle_short_opt(sopt)
744
- sopt = sopt.to_s if sopt && sopt != :none
745
- sopt = case sopt
746
- when /^-(.)$/ then $1
747
- when nil, :none, /^.$/ then sopt
748
- else raise ArgumentError, "invalid short option name '#{sopt.inspect}'"
749
- end
750
-
751
- if sopt
752
- raise ArgumentError, "a short option name can't be a number or a dash" if sopt =~ ::Optimist::Parser::INVALID_SHORT_ARG_REGEX
753
- end
754
- return sopt
755
- end
756
-
757
993
  end
758
994
 
759
995
  # Flag option. Has no arguments. Can be negated with "no-".
@@ -773,11 +1009,12 @@ end
773
1009
  class FloatOption < Option
774
1010
  register_alias :float, :double
775
1011
  def type_format ; "=<f>" ; end
1012
+ def as_type(param) ; param.to_f ; end
776
1013
  def parse(paramlist, _neg_given)
777
1014
  paramlist.map do |pg|
778
1015
  pg.map do |param|
779
1016
  raise CommandlineError, "option '#{self.name}' needs a floating-point number" unless param.is_a?(Numeric) || param =~ FLOAT_RE
780
- param.to_f
1017
+ as_type(param)
781
1018
  end
782
1019
  end
783
1020
  end
@@ -787,11 +1024,12 @@ end
787
1024
  class IntegerOption < Option
788
1025
  register_alias :int, :integer, :fixnum
789
1026
  def type_format ; "=<i>" ; end
1027
+ def as_type(param) ; param.to_i ; end
790
1028
  def parse(paramlist, _neg_given)
791
1029
  paramlist.map do |pg|
792
1030
  pg.map do |param|
793
1031
  raise CommandlineError, "option '#{self.name}' needs an integer" unless param.is_a?(Numeric) || param =~ /^-?[\d_]+$/
794
- param.to_i
1032
+ as_type(param)
795
1033
  end
796
1034
  end
797
1035
  end
@@ -824,9 +1062,10 @@ end
824
1062
  # Option class for handling Strings.
825
1063
  class StringOption < Option
826
1064
  register_alias :string
1065
+ def as_type(val) ; val.to_s ; end
827
1066
  def type_format ; "=<s>" ; end
828
1067
  def parse(paramlist, _neg_given)
829
- paramlist.map { |pg| pg.map(&:to_s) }
1068
+ paramlist.map { |pg| pg.map { |param| as_type(param) } }
830
1069
  end
831
1070
  end
832
1071
 
@@ -924,7 +1163,22 @@ end
924
1163
  ## ## if called with --monkey
925
1164
  ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
926
1165
  ##
927
- ## See more examples at http://optimist.rubyforge.org.
1166
+ ## Settings:
1167
+ ## Optimist::options and Optimist::Parser.new accept +settings+ to control how
1168
+ ## options are interpreted. These settings are given as hash arguments, e.g:
1169
+ ##
1170
+ ## opts = Optimist::options(ARGV, exact_match: false) do
1171
+ ## opt :foobar, 'messed up'
1172
+ ## opt :forget, 'forget it'
1173
+ ## end
1174
+ ##
1175
+ ## +settings+ include:
1176
+ ## * :exact_match : (default=true) Allow minimum unambigous number of characters to match a long option
1177
+ ## * :suggestions : (default=true) Enables suggestions when unknown arguments are given and DidYouMean is installed. DidYouMean comes standard with Ruby 2.3+
1178
+ ## * :implicit_short_opts : (default=true) Short options will only be created where explicitly defined. If you do not like short-options, this will prevent having to define :short=> :none for all of your options.
1179
+ ## Because Optimist::options uses a default argument for +args+, you must pass that argument when using the settings feature.
1180
+ ##
1181
+ ## See more examples at https://www.manageiq.org/optimist
928
1182
  def options(args = ARGV, *a, &b)
929
1183
  @last_parser = Parser.new(*a, &b)
930
1184
  with_standard_exception_handling(@last_parser) { @last_parser.parse args }