optimist 3.0.1 → 3.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.
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,9 +8,7 @@
6
8
  require 'date'
7
9
 
8
10
  module Optimist
9
- # note: this is duplicated in gemspec
10
- # please change over there too
11
- VERSION = "3.0.1"
11
+ VERSION = "3.2.0"
12
12
 
13
13
  ## Thrown by Parser in the event of a commandline error. Not needed if
14
14
  ## you're using the Optimist::options entry.
@@ -37,6 +37,50 @@ FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
37
37
  ## Regex for parameters
38
38
  PARAM_RE = /^-(-|\.$|[^\d\.])/
39
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
+
40
84
  ## The commandline parser. In typical usage, the methods in this class
41
85
  ## will be handled internally by Optimist::options. In this case, only the
42
86
  ## #opt, #banner and #version, #depends, and #conflicts methods will
@@ -70,8 +114,6 @@ class Parser
70
114
  return @registry[lookup].new
71
115
  end
72
116
 
73
- INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
74
-
75
117
  ## The values from the commandline that were not interpreted by #parse.
76
118
  attr_reader :leftovers
77
119
 
@@ -84,6 +126,12 @@ class Parser
84
126
  ## ignore options that it does not recognize.
85
127
  attr_accessor :ignore_invalid_options
86
128
 
129
+ DEFAULT_SETTINGS = {
130
+ exact_match: true,
131
+ implicit_short_opts: true,
132
+ suggestions: true
133
+ }
134
+
87
135
  ## Initializes the parser, and instance-evaluates any block given.
88
136
  def initialize(*a, &b)
89
137
  @version = nil
@@ -99,8 +147,17 @@ class Parser
99
147
  @synopsis = nil
100
148
  @usage = nil
101
149
 
102
- # instance_eval(&b) if b # can't take arguments
103
- 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?
104
161
  end
105
162
 
106
163
  ## Define an option. +name+ is the option name, a unique identifier
@@ -116,6 +173,7 @@ class Parser
116
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+.
117
174
  ## [+:required+] If set to +true+, the argument must be provided on the commandline.
118
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.
119
177
  ##
120
178
  ## Note that there are two types of argument multiplicity: an argument
121
179
  ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
@@ -150,10 +208,19 @@ class Parser
150
208
  o = Option.create(name, desc, opts)
151
209
 
152
210
  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?
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
+
157
224
  @specs[o.name] = o
158
225
  @order << [:opt, o.name]
159
226
  end
@@ -190,13 +257,19 @@ class Parser
190
257
  ## better modeled with Optimist::die.
191
258
  def depends(*syms)
192
259
  syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
193
- @constraints << [:depends, syms]
260
+ @constraints << DependConstraint.new(syms)
194
261
  end
195
262
 
196
263
  ## Marks two (or more!) options as conflicting.
197
264
  def conflicts(*syms)
198
265
  syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
199
- @constraints << [:conflicts, syms]
266
+ @constraints << ConflictConstraint.new(syms)
267
+ end
268
+
269
+ ## Marks two (or more!) options as required but mutually exclusive.
270
+ def either(*syms)
271
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
272
+ @constraints << EitherConstraint.new(syms)
200
273
  end
201
274
 
202
275
  ## Defines a set of words which cause parsing to terminate when
@@ -226,6 +299,56 @@ class Parser
226
299
  @educate_on_error = true
227
300
  end
228
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
+
229
352
  ## Parses the commandline. Typically called by Optimist::options,
230
353
  ## but you can call it directly if you need more control.
231
354
  ##
@@ -243,7 +366,7 @@ class Parser
243
366
  vals[sym] = [] if opts.multi && !opts.default # multi arguments default to [], not nil
244
367
  end
245
368
 
246
- resolve_default_short_options!
369
+ resolve_default_short_options! if @settings[:implicit_short_opts]
247
370
 
248
371
  ## resolve symbols
249
372
  given_args = {}
@@ -261,10 +384,15 @@ class Parser
261
384
  else raise CommandlineError, "invalid argument syntax: '#{arg}'"
262
385
  end
263
386
 
264
- 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
265
393
 
266
394
  next nil if ignore_invalid_options && !sym
267
- raise CommandlineError, "unknown argument '#{arg}'" unless sym
395
+ handle_unknown_argument(arg, @long.keys, @settings[:suggestions]) unless sym
268
396
 
269
397
  if given_args.include?(sym) && !@specs[sym].multi?
270
398
  raise CommandlineError, "option '#{arg}' specified multiple times"
@@ -296,20 +424,12 @@ class Parser
296
424
  raise HelpNeeded if given_args.include? :help
297
425
 
298
426
  ## 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
427
+ @constraints.each do |const|
428
+ const.validate(given_args: given_args, specs: @specs)
309
429
  end
310
430
 
311
431
  required.each do |sym, val|
312
- 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
313
433
  end
314
434
 
315
435
  ## parse parameters
@@ -322,6 +442,12 @@ class Parser
322
442
  params << (opts.array_default? ? opts.default.clone : [opts.default])
323
443
  end
324
444
 
445
+ if params.first && opts.permitted
446
+ params.first.each do |val|
447
+ opts.validate_permitted(arg, val)
448
+ end
449
+ end
450
+
325
451
  vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
326
452
 
327
453
  vals[sym] = opts.parse(params, negative_given)
@@ -382,7 +508,7 @@ class Parser
382
508
 
383
509
  spec = @specs[opt]
384
510
  stream.printf " %-#{leftcol_width}s ", left[opt]
385
- desc = spec.description_with_default
511
+ desc = spec.full_description
386
512
 
387
513
  stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
388
514
  end
@@ -427,7 +553,7 @@ class Parser
427
553
  def die(arg, msg = nil, error_code = nil)
428
554
  msg, error_code = nil, msg if msg.kind_of?(Integer)
429
555
  if msg
430
- $stderr.puts "Error: argument --#{@specs[arg].long} #{msg}."
556
+ $stderr.puts "Error: argument --#{@specs[arg].long.long} #{msg}."
431
557
  else
432
558
  $stderr.puts "Error: #{arg}."
433
559
  end
@@ -450,7 +576,7 @@ private
450
576
  until i >= args.length
451
577
  return remains += args[i..-1] if @stop_words.member? args[i]
452
578
  case args[i]
453
- when /^--$/ # arg terminator
579
+ when "--" # arg terminator
454
580
  return remains += args[(i + 1)..-1]
455
581
  when /^--(\S+?)=(.*)$/ # long argument with equals
456
582
  num_params_taken = yield "--#{$1}", [$2]
@@ -475,7 +601,7 @@ private
475
601
  end
476
602
  i += 1
477
603
  when /^-(\S+)$/ # one or more short arguments
478
- short_remaining = ""
604
+ short_remaining = []
479
605
  shortargs = $1.split(//)
480
606
  shortargs.each_with_index do |a, j|
481
607
  if j == (shortargs.length - 1)
@@ -485,7 +611,7 @@ private
485
611
  unless num_params_taken
486
612
  short_remaining << a
487
613
  if @stop_on_unknown
488
- remains << "-#{short_remaining}"
614
+ remains << "-#{short_remaining.join}"
489
615
  return remains += args[i + 1..-1]
490
616
  end
491
617
  else
@@ -495,8 +621,8 @@ private
495
621
  unless yield "-#{a}", []
496
622
  short_remaining << a
497
623
  if @stop_on_unknown
498
- short_remaining += shortargs[j + 1..-1].join
499
- remains << "-#{short_remaining}"
624
+ short_remaining << shortargs[j + 1..-1].join
625
+ remains << "-#{short_remaining.join}"
500
626
  return remains += args[i + 1..-1]
501
627
  end
502
628
  end
@@ -504,7 +630,7 @@ private
504
630
  end
505
631
 
506
632
  unless short_remaining.empty?
507
- remains << "-#{short_remaining}"
633
+ remains << "-#{short_remaining.join}"
508
634
  end
509
635
  i += 1
510
636
  else
@@ -533,11 +659,10 @@ private
533
659
  def resolve_default_short_options!
534
660
  @order.each do |type, name|
535
661
  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) }
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) }
539
664
  if c # found a character to use
540
- opts.short = c
665
+ opts.short.add c
541
666
  @short[c] = name
542
667
  end
543
668
  end
@@ -563,30 +688,94 @@ private
563
688
  ret
564
689
  end
565
690
 
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
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}"
706
+ end
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
574
760
  end
575
761
  end
762
+
576
763
  end
577
764
 
578
765
  class Option
579
766
 
580
- attr_accessor :name, :short, :long, :default
767
+ attr_accessor :name, :short, :long, :default, :permitted, :permitted_response
581
768
  attr_writer :multi_given
582
769
 
583
770
  def initialize
584
- @long = nil
585
- @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
586
773
  @name = nil
587
774
  @multi_given = false
588
775
  @hidden = false
589
776
  @default = nil
777
+ @permitted = nil
778
+ @permitted_response = "option '%{arg}' only accepts %{valid_string}"
590
779
  @optshash = Hash.new()
591
780
  end
592
781
 
@@ -613,7 +802,7 @@ class Option
613
802
 
614
803
  def array_default? ; self.default.kind_of?(Array) ; end
615
804
 
616
- def short? ; short && short != :none ; end
805
+ def doesnt_need_autogen_short ; !short.auto || short.chars.any? ; end
617
806
 
618
807
  def callback ; opts(:callback) ; end
619
808
  def desc ; opts(:desc) ; end
@@ -628,23 +817,96 @@ class Option
628
817
  def type_format ; "" ; end
629
818
 
630
819
  def educate
631
- (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}" : "")
824
+ end
825
+
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
632
842
  end
633
843
 
634
- ## Format the educate-line description including the default-value(s)
635
- def description_with_default
636
- return desc unless default
844
+ ## Generate the default value string for the educate line
845
+ private def default_description_str str
637
846
  default_s = case default
638
- when $stdout then '<stdout>'
639
- when $stdin then '<stdin>'
640
- when $stderr then '<stderr>'
641
847
  when Array
642
848
  default.join(', ')
643
849
  else
644
- default.to_s
850
+ format_stdio(default).to_s
645
851
  end
646
- defword = desc.end_with?('.') ? 'Default' : 'default'
647
- 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})"
648
910
  end
649
911
 
650
912
  ## Provide a way to register symbol aliases to the Parser
@@ -671,10 +933,10 @@ class Option
671
933
  opt_inst = (opttype || opttype_from_default || Optimist::BooleanOption.new)
672
934
 
673
935
  ## fill in :long
674
- opt_inst.long = handle_long_opt(opts[:long], name)
936
+ opt_inst.long.set(name, opts[:long], opts[:alt])
675
937
 
676
938
  ## fill in :short
677
- opt_inst.short = handle_short_opt(opts[:short])
939
+ opt_inst.short.add opts[:short]
678
940
 
679
941
  ## fill in :multi
680
942
  multi_given = opts[:multi] || false
@@ -683,8 +945,13 @@ class Option
683
945
  ## fill in :default for flags
684
946
  defvalue = opts[:default] || opt_inst.default
685
947
 
948
+ ## fill in permitted values
949
+ permitted = opts[:permitted] || nil
950
+
686
951
  ## autobox :default for :multi (multi-occurrence) arguments
687
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]
688
955
  opt_inst.default = defvalue
689
956
  opt_inst.name = name
690
957
  opt_inst.opts = opts
@@ -723,29 +990,6 @@ class Option
723
990
  return Optimist::Parser.registry_getopttype(type_from_default)
724
991
  end
725
992
 
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
993
  end
750
994
 
751
995
  # Flag option. Has no arguments. Can be negated with "no-".
@@ -765,11 +1009,12 @@ end
765
1009
  class FloatOption < Option
766
1010
  register_alias :float, :double
767
1011
  def type_format ; "=<f>" ; end
1012
+ def as_type(param) ; param.to_f ; end
768
1013
  def parse(paramlist, _neg_given)
769
1014
  paramlist.map do |pg|
770
1015
  pg.map do |param|
771
1016
  raise CommandlineError, "option '#{self.name}' needs a floating-point number" unless param.is_a?(Numeric) || param =~ FLOAT_RE
772
- param.to_f
1017
+ as_type(param)
773
1018
  end
774
1019
  end
775
1020
  end
@@ -779,11 +1024,12 @@ end
779
1024
  class IntegerOption < Option
780
1025
  register_alias :int, :integer, :fixnum
781
1026
  def type_format ; "=<i>" ; end
1027
+ def as_type(param) ; param.to_i ; end
782
1028
  def parse(paramlist, _neg_given)
783
1029
  paramlist.map do |pg|
784
1030
  pg.map do |param|
785
1031
  raise CommandlineError, "option '#{self.name}' needs an integer" unless param.is_a?(Numeric) || param =~ /^-?[\d_]+$/
786
- param.to_i
1032
+ as_type(param)
787
1033
  end
788
1034
  end
789
1035
  end
@@ -816,9 +1062,10 @@ end
816
1062
  # Option class for handling Strings.
817
1063
  class StringOption < Option
818
1064
  register_alias :string
1065
+ def as_type(val) ; val.to_s ; end
819
1066
  def type_format ; "=<s>" ; end
820
1067
  def parse(paramlist, _neg_given)
821
- paramlist.map { |pg| pg.map(&:to_s) }
1068
+ paramlist.map { |pg| pg.map { |param| as_type(param) } }
822
1069
  end
823
1070
  end
824
1071
 
@@ -916,7 +1163,22 @@ end
916
1163
  ## ## if called with --monkey
917
1164
  ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
918
1165
  ##
919
- ## 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
920
1182
  def options(args = ARGV, *a, &b)
921
1183
  @last_parser = Parser.new(*a, &b)
922
1184
  with_standard_exception_handling(@last_parser) { @last_parser.parse args }