help_parser 1.0.2

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.
@@ -0,0 +1,30 @@
1
+ @edge
2
+ Feature: Edge cases
3
+
4
+ Scenario: Redefinition of dictionary
5
+ For example, --help already maps to -h,
6
+ so if help text says different
7
+ we should get an ImplementationError.
8
+ * Given command "./examples/implementation_error_redefinition"
9
+ * Given option ""
10
+ * When we run command
11
+ * Then exception is "HELP_PARSER::ImplementationError"
12
+ * Then message matches "redefinition"
13
+
14
+ Scenario: Terminal
15
+ For example, -h defines the "user wants help" state.
16
+ If the help text tries to create another terminal state for -h,
17
+ we should get an ImplementationError.
18
+ * Given command "./examples/implementation_error_terminal"
19
+ * Given option ""
20
+ * When we run command
21
+ * Then exception is "HELP_PARSER::ImplementationError"
22
+ * Then message matches "terminal"
23
+
24
+ Scenario: Redifinition in help text
25
+ For example, it the help text says --zebra is -z and -a.
26
+ * Given command "./examples/implementation_error_duplicate_key"
27
+ * Given option ""
28
+ * When we run command
29
+ * Then exception is "HELP_PARSER::ImplementationError"
30
+ * Then message matches "redefinition"
@@ -0,0 +1,125 @@
1
+ @simple
2
+ Feature: Testing examples/simple
3
+
4
+ Background:
5
+ * Given command "./examples/simple"
6
+
7
+ Scenario: Version, short option: -v
8
+ * Given option "-v"
9
+ * When we run command
10
+ * Then exception is "HELP_PARSER::VersionException"
11
+ * Then message is "1.2.3"
12
+ * Then options[v] is "true"
13
+ * Then options[version] is "true"
14
+
15
+ Scenario: Version, long option: --version
16
+ * Given option "--version"
17
+ * When we run command
18
+ * Then exception is "HELP_PARSER::VersionException"
19
+ * Then message is "1.2.3"
20
+ * Then options[v] is "true"
21
+ * Then options[version] is "true"
22
+
23
+ Scenario: Help, short option: -h
24
+ * Given option "-h"
25
+ * When we run command
26
+ * Then exception is "HELP_PARSER::HelpException"
27
+ * Then message matches "^Usage:"
28
+ * Then options[h] is "true"
29
+ * Then options[help] is "true"
30
+
31
+ Scenario: Help, long option: --help
32
+ * Given option "--help"
33
+ * When we run command
34
+ * Then exception is "HELP_PARSER::HelpException"
35
+ * Then message matches "^Usage:"
36
+ * Then options[h] is "true"
37
+ * Then options[help] is "true"
38
+
39
+ Scenario: Duplicate with long option: -h --help
40
+ * Given option "-h --help"
41
+ * When we run command
42
+ * Then exception is "HELP_PARSER::UsageError"
43
+ * Then message matches "^duplicate"
44
+
45
+ Scenario: Duplicate with short option: --help -h
46
+ * Given option "-h --help"
47
+ * When we run command
48
+ * Then exception is "HELP_PARSER::UsageError"
49
+ * Then message matches "^duplicate"
50
+
51
+ Scenario: No options: ""
52
+ * Given option ""
53
+ * When we run command
54
+ * Then exception is ""
55
+ * Then message is ""
56
+ * Then options.empty?
57
+
58
+ Scenario: No comment: --nocomment
59
+ We don't need to comment a command line option in the help.
60
+ * Given option "--nocomment"
61
+ * When we run command
62
+ * Then exception is ""
63
+ * Then message is ""
64
+ * Then options[nocomment] is "true"
65
+ * Then options.length is 1
66
+
67
+ Scenario: Long option values, String: --long=aStringValue
68
+ * Given option "--long=aStringValue"
69
+ * When we run command
70
+ * Then options[long] is "aStringValue"
71
+
72
+ Scenario: Long option values, Integer: --long=42
73
+ * Given option "--long=42"
74
+ * When we run command
75
+ * Then options[long] is "42"
76
+ * Then options[long] kind of Integer
77
+
78
+ Scenario: Long option values, default true: --long
79
+ * Given option "--long"
80
+ * When we run command
81
+ * Then options[long] is "true"
82
+
83
+ Scenario: Long option values, negated: --no-long
84
+ * Given option "--no-long"
85
+ * When we run command
86
+ * Then options[long] is "false"
87
+
88
+ Scenario: Long option values, Array: long=1,2,3
89
+ * Given option "--long=1,2,3"
90
+ * When we run command
91
+ * Then options[long] kind of Array
92
+
93
+ Scenario: Three non synonim-ed keys: -k, -V, --nocomment
94
+ * Given options "--nocomment -k -V"
95
+ * When we run command
96
+ * Then options.length is 3
97
+ * Then options[k] is "true"
98
+ * Then options[V] is "true"
99
+ * Then options[nocomment] is "true"
100
+
101
+ Scenario: One synonim-ed key: -s
102
+ * Given options "-s"
103
+ * When we run command
104
+ * Then options.length is 2
105
+ * Then options[s] is "true"
106
+ * Then options[long] is "true"
107
+
108
+ Scenario: One synonim-ed key: --long=abc
109
+ * Given options "--long=abc"
110
+ * When we run command
111
+ * Then options.length is 2
112
+ * Then options[s] is "abc"
113
+ * Then options[long] is "abc"
114
+
115
+ Scenario: Attempting to set a negation: --no-long=NotAllowed
116
+ * Given options "--no-long=NotAllowed"
117
+ * When we run command
118
+ * Then exception is "HELP_PARSER::UsageError"
119
+ * Then message matches "no-long=NotAllowed"
120
+
121
+ Scenario: User passed undefined option: --cacahuates
122
+ * Given option "--cacahuates"
123
+ * When we run command
124
+ * Then exception is "HELP_PARSER::UsageError"
125
+ * Then message matches "not.*valid.*cacahuates"
@@ -0,0 +1,80 @@
1
+ require 'json'
2
+ require 'open3'
3
+
4
+ Given /Given command "([^"]*)"/ do |command|
5
+ @command = command
6
+ end
7
+
8
+ Given /Given option(s?) "([^"]*)"/ do |p, options|
9
+ @options = options
10
+ end
11
+
12
+ When /When we run command/ do
13
+ @stdout, @stderr, @status = Open3.capture3("#{@command} #{@options}")
14
+ begin
15
+ @json = JSON.parse @stdout
16
+ rescue StandardError
17
+ @json = {}
18
+ end
19
+ end
20
+
21
+ Then /Then exit status is "(\d+)"/ do |status|
22
+ unless @status.exit_status == status.to_i
23
+ raise "Got #{@status.exit_status} instead of #{status}"
24
+ end
25
+ end
26
+
27
+ Then /Then (\w+) is "([^"]*)"/ do |key, expected|
28
+ value = @json[key].to_s
29
+ unless value == expected
30
+ raise "Got '#{value}' instead of '#{expected}'"
31
+ end
32
+ end
33
+
34
+ Then /Then (\w+) matches "([^"]*)"/ do |key, expected|
35
+ value = @json[key]
36
+ unless value =~ /#{expected}/
37
+ raise "'#{expected}' did not match:\n#{value}"
38
+ end
39
+ end
40
+
41
+ Then /Then options\[(\w+)\] is "([^*]*)"/ do |key,expected|
42
+ options = @json['options']
43
+ value = (options)? options[key].to_s : ''
44
+ unless expected == value
45
+ raise "Got '#{value}' instead of '#{expected}'"
46
+ end
47
+ end
48
+
49
+ Then /Then options\[(\w+)\] kind of (\w+)/ do |key, type|
50
+ options = @json['options']
51
+ value = options[key]
52
+ case type
53
+ when 'Integer'
54
+ raise "Not an integer" unless value.kind_of?(Integer)
55
+ when 'Array'
56
+ raise "Not an array" unless value.kind_of?(Array)
57
+ else
58
+ raise "Unrecognized type #{type}"
59
+ end
60
+ end
61
+
62
+ Then /Then options.empty\?/ do
63
+ options = @json['options']
64
+ raise "options not empty!" unless options.empty?
65
+ end
66
+
67
+ Then /Then options.length is (\d+)/ do |expected|
68
+ expected = expected.to_i
69
+ options = @json['options']
70
+ actual = options.length
71
+ raise "Length was #{actual}, not #{expected}." unless actual == expected
72
+ end
73
+
74
+ =begin
75
+ Then // do ||
76
+ unless ==
77
+ raise "Got '#{@json['']}' instead of '#{}'"
78
+ end
79
+ end
80
+ =end
@@ -0,0 +1,131 @@
1
+ @suite
2
+ Feature: Testing examples/command_suite
3
+
4
+ Background:
5
+ * Given command "./examples/command_suite"
6
+
7
+ Scenario: Version: -v
8
+ * Given option "-v"
9
+ * When we run command
10
+ * Then message is "10.02.3"
11
+ * Then exception is "HELP_PARSER::VersionException"
12
+ * Then options[v] is "true"
13
+ * Then options[version] is "true"
14
+ * Then command is ""
15
+
16
+ Scenario: Help: --help
17
+ * Given option "--help"
18
+ * When we run command
19
+ * Then message matches "^This is a test script"
20
+ * Then exception is "HELP_PARSER::HelpException"
21
+ * Then options[h] is "true"
22
+ * Then options[help] is "true"
23
+ * Then command is ""
24
+
25
+ Scenario: First Version: first --version
26
+ * Given option "first --version"
27
+ * When we run command
28
+ * Then message is "10.02.3"
29
+ * Then exception is "HELP_PARSER::VersionException"
30
+ * Then options[v] is "true"
31
+ * Then options[version] is "true"
32
+ * Then command is "first"
33
+
34
+ Scenario: First Help: first -h
35
+ * Given option "first --h"
36
+ * When we run command
37
+ * Then message matches "^command_suite-first"
38
+ * Then exception is "HELP_PARSER::HelpException"
39
+ * Then options[h] is "true"
40
+ * Then options[help] is "true"
41
+ * Then command is "first"
42
+
43
+ Scenario: Second Version: second --version
44
+ * Given option "second --version"
45
+ * When we run command
46
+ * Then message is "10.02.3"
47
+ * Then exception is "HELP_PARSER::VersionException"
48
+ * Then options[v] is "true"
49
+ * Then options[version] is "true"
50
+ * Then command is "second"
51
+
52
+ Scenario: Second Help: second -h
53
+ * Given option "second --h"
54
+ * When we run command
55
+ * Then message matches "^command_suite-second"
56
+ * Then exception is "HELP_PARSER::HelpException"
57
+ * Then options[h] is "true"
58
+ * Then options[help] is "true"
59
+ * Then command is "second"
60
+
61
+ Scenario: ""
62
+ * Given option ""
63
+ * When we run command
64
+ * Then message is "Need command."
65
+ * Then exception is "HELP_PARSER::UsageError"
66
+
67
+ Scenario: "first"
68
+ * Given option "first"
69
+ * When we run command
70
+ #
71
+ * Then string is "string1"
72
+ * Then float is "1.0"
73
+ * Then integer is "1"
74
+ #
75
+ * Then cstring is "string1"
76
+ * Then cfloat is "1.0"
77
+ * Then cinteger is "1"
78
+ #
79
+ * Then command is "first"
80
+
81
+ Scenario: "first --string --float --integer"
82
+ * Given options "first --string --float --integer"
83
+ * When we run command
84
+ #
85
+ * Then string is "string1"
86
+ * Then float is "1.0"
87
+ * Then integer is "1"
88
+ #
89
+ * Then cstring is "string2"
90
+ * Then cfloat is "2.0"
91
+ * Then cinteger is "2"
92
+ #
93
+ * Then command is "first"
94
+
95
+ Scenario: "--string --float --integer second --string=string3 --float=3.0 --integer=3"
96
+ * Given options "--string --float --integer second --string=string3 --float=3.0 --integer=3"
97
+ * When we run command
98
+ #
99
+ * Then string is "string2"
100
+ * Then float is "2.0"
101
+ * Then integer is "2"
102
+ #
103
+ * Then cstring is "string3"
104
+ * Then cfloat is "3.0"
105
+ * Then cinteger is "3"
106
+ #
107
+ * Then command is "second"
108
+
109
+ Scenario: "--string=1 second"
110
+ * Given options "--string=1 second"
111
+ * When we run command
112
+ * Then exception is "HELP_PARSER::UsageError"
113
+ * Then message matches "^type error.*String"
114
+
115
+ Scenario: "second --integer=string"
116
+ * Given options "second --integer=string"
117
+ * When we run command
118
+ * Then exception is "HELP_PARSER::UsageError"
119
+ * Then message matches "^type error.*Fixnum"
120
+
121
+ Scenario: "first --float=1"
122
+ * Given options "first --float=1"
123
+ * When we run command
124
+ * Then exception is "HELP_PARSER::UsageError"
125
+ * Then message matches "^type error.*Float"
126
+
127
+ Scenario: "--array=seven first"
128
+ * Given options "--array=seven first"
129
+ * When we run command
130
+ * Then exception is "HELP_PARSER::UsageError"
131
+ * Then message matches "^type error.*Array"
@@ -0,0 +1,52 @@
1
+ Gem::Specification.new do |s|
2
+
3
+ s.name = 'help_parser'
4
+ s.version = '1.0.2'
5
+
6
+ s.homepage = 'https://github.com/carlosjhr64/help_parser'
7
+
8
+ s.author = 'CarlosJHR64'
9
+ s.email = 'carlosjhr64@gmail.com'
10
+
11
+ s.date = '2013-12-13'
12
+ s.licenses = ['MIT']
13
+
14
+ s.description = <<DESCRIPTION
15
+ _HelpParser_ - Parses your help text to define your command line options.
16
+ DESCRIPTION
17
+
18
+ s.summary = <<SUMMARY
19
+ _HelpParser_ - Parses your help text to define your command line options.
20
+ SUMMARY
21
+
22
+ s.extra_rdoc_files = ['README.rdoc']
23
+ s.rdoc_options = ["--main", "README.rdoc"]
24
+
25
+ s.require_paths = ["lib"]
26
+ s.files = %w(
27
+ History.txt
28
+ Manifest.txt
29
+ README.rdoc
30
+ Rakefile
31
+ TODO.txt
32
+ examples/command_suite
33
+ examples/implementation_error_duplicate_key
34
+ examples/implementation_error_redefinition
35
+ examples/implementation_error_terminal
36
+ examples/simple
37
+ features/edge.feature
38
+ features/simple.feature
39
+ features/step_definitions/help_parser_steps.rb
40
+ features/suite.feature
41
+ help_parser.gemspec
42
+ lib/help_parser.rb
43
+ lib/help_parser/help_parser.rb
44
+ lib/help_parser/version.rb
45
+ tasks/gemspecker.rb
46
+ tasks/manifester.rb
47
+ tasks/syntaxer.rb
48
+ tasks/unit_tests.rb
49
+ test/help_parser.rb
50
+ )
51
+
52
+ end
@@ -0,0 +1,2 @@
1
+ require 'help_parser/version.rb'
2
+ require 'help_parser/help_parser.rb'
@@ -0,0 +1,285 @@
1
+ module HELP_PARSER
2
+
3
+ # **UsageError** is an error when the user passed command
4
+ # line options that are inconsistent with expectations.
5
+ # The raise in a class method is as follows:
6
+ # raise UsageError, [message, self]
7
+ # The object that raised the error will be available as:
8
+ # $!.obj
9
+ class UsageError < StandardError
10
+ attr_accessor :obj
11
+ def initialize(array)
12
+ self.obj = array.pop
13
+ msg = array.pop
14
+ super(msg)
15
+ end
16
+ end
17
+ # **ImplementationError** is an error due to a
18
+ # inconsistent help text. The raise in a class method is
19
+ # as follows:
20
+ # raise ImplementationError, [message, self]
21
+ # The object that raised the error will be available as:
22
+ # $!.obj
23
+ class ImplementationError < StandardError
24
+ attr_accessor :obj
25
+ def initialize(array)
26
+ self.obj = array.pop
27
+ msg = array.pop
28
+ super(msg)
29
+ end
30
+ end
31
+
32
+ # UsageException is an actual exception to normal flow.
33
+ # For example, when a user asks for help or version
34
+ # number in the command line options. The raise in a
35
+ # class method is as follows:
36
+ # raise UsageException, [message, self]
37
+ # The object that raised the exception will be available
38
+ # as:
39
+ # $!.obj
40
+ class UsageException < Exception
41
+ attr_accessor :obj
42
+ def initialize(array)
43
+ self.obj = array.pop
44
+ msg = array.pop
45
+ super(msg)
46
+ end
47
+ end
48
+
49
+ # HelpException is a type of UsageException, where the
50
+ # user asks for help in the command line options. The
51
+ # raise in a class method is as follows:
52
+ # raise HelpException, self
53
+ # The object that raised the exception will be available
54
+ # as:
55
+ # $!.obj
56
+ # The message will be the help text:
57
+ # $!.message == $!.obj.help # => true
58
+ class HelpException < UsageException
59
+ def initialize(help_parser)
60
+ super([help_parser.help, help_parser])
61
+ end
62
+ end
63
+
64
+ # VersionException is a type of UsageException, where the
65
+ # user asks for version in the command line options. The
66
+ # raise in a class method is as follows:
67
+ # raise VersionException, self
68
+ # The object that raised the exception will be available
69
+ # as:
70
+ # $!.obj
71
+ # The message will be the version text:
72
+ # $!.message == $!.obj.version # => true
73
+ class VersionException < UsageException
74
+ def initialize(help_parser)
75
+ super([help_parser.version, help_parser])
76
+ end
77
+ end
78
+
79
+ module Errors
80
+ # HelpParser#usage_error will have make the HelpParser
81
+ # object raise a UsageError error with the given message.
82
+ def usage_error(msg)
83
+ raise UsageError, [msg, self]
84
+ end
85
+
86
+ # HelpParser#implementation_error will have make the
87
+ # HelpParser object raise a UsageError error with the
88
+ # given message.
89
+ def implementation_error(msg)
90
+ raise ImplementationError, [msg, self]
91
+ end
92
+ end
93
+
94
+ class HelpParser < Hash
95
+ include Errors
96
+
97
+ # I'm wrapping class functions in a Function module.
98
+ module Function
99
+
100
+ # I'm wrapping Function's helper functions in a Private
101
+ # module.
102
+ module Private
103
+ # HelpParser::Function::Private.to_value
104
+ def to_value(string)
105
+ case string
106
+ when /^-?\d+$/
107
+ return string.to_i
108
+ when /^-?\d+\.\d+$/
109
+ return string.to_f
110
+ when /,/
111
+ return string.split(',').map{|s| to_value(s)}
112
+ end
113
+ return string
114
+ end
115
+ end
116
+ extend Private
117
+
118
+ # HelpParser::Function.str2sym converts String keys to
119
+ # Symbol. Symbol keys can have '-', but it's cumbersome
120
+ # notation:
121
+ # :"a-b"
122
+ # So I translate it to underscore:
123
+ # :a_b
124
+ def str2sym(str)
125
+ str.gsub('-','_').to_sym
126
+ end
127
+
128
+ # HelpParser#parse parses the @argv for command line
129
+ # options, shifting them out, and passes on the key-value
130
+ # pairs to the block. The keys are translated to their
131
+ # short version if available, and the values are converted
132
+ # to their best interpreted form as per Funtion.to_value.
133
+ # Furthermore, no-key values are negated.
134
+ def parse(argv, obj, &block)
135
+ while argv[0] and argv[0][0]=='-'
136
+ option = argv.shift[1..-1]
137
+ if option[0]=='-'
138
+ loption, value = option[1..-1].split('=', 2)
139
+ if loption=~/no-(\w.*)$/
140
+ loption = $1
141
+ if value
142
+ raise UsageError, ["no-key should not have value: no-#{loption}=#{value}", obj]
143
+ end
144
+ value = false
145
+ end
146
+ if value.nil?
147
+ value = true
148
+ block.call loption, value
149
+ else
150
+ block.call loption, Function.to_value(value)
151
+ end
152
+ else
153
+ # These are single character options...
154
+ option.each_char do |c|
155
+ block.call c, true
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+ extend Function
162
+
163
+ def parse(&block)
164
+ HelpParser.parse(@argv, self, &block)
165
+ end
166
+
167
+
168
+ # HelpParser.new(version, help, argv=ARGV, dict={})
169
+ # Not necessarily in that order, see the code.
170
+ attr_reader :version, :help, :dict
171
+ def initialize(*parameters)
172
+ super()
173
+
174
+ # Figure out what parameters you were given...
175
+ @argv = parameters.detect{|parameter| parameter.class == Array} || ARGV
176
+ @dict = parameters.detect{|parameter| parameter.class == Hash } || {}
177
+ parameters = parameters.select{|parameter| parameter.class == String}
178
+ @version = parameters.shift
179
+ @help = parameters.pop
180
+
181
+ raise "Need help" unless @help
182
+ raise "Need version" unless @version
183
+
184
+ # We ensure the following two mappings...
185
+ @dict[:help] = :h
186
+ @dict[:version] = :v
187
+
188
+ # Using the help for the options definition
189
+ @help.split("\n").select{|l|
190
+ (l=~/^\s*-/)
191
+ }.map{|l|
192
+ l.strip.split(/[,\s]+/)[0..1].select{|o|
193
+ o[0]=='-'
194
+ }.map{|o|
195
+ HelpParser.str2sym o.sub(/--?/,'').sub(/=.*$/,'')
196
+ }.reverse
197
+ }.each do |k,v|
198
+ unless @dict[k].nil?
199
+ # This is an implementation error. The help text is inconsistent.
200
+ # Note that we may have been given @dict, and we're verifying the help.
201
+ implementation_error "#{k} redefinition from #{@dict[k]} to #{v}." unless @dict[k]==v
202
+ end
203
+ @dict[k]=v
204
+ end
205
+
206
+ # Values must be terminal states, that is for example:
207
+ # if :version => :v, then :v => nil.
208
+ terminals = []
209
+ @dict.each do |k,v|
210
+ next if v.nil?
211
+ if @dict.has_key?(v)
212
+ unless @dict[v].nil?
213
+ implementation_error "Expected terminal #{v}: #{k} => #{v} => #{@dict[v]}"
214
+ end
215
+ else
216
+ terminals.push(v)
217
+ end
218
+ end
219
+ terminals.each{|v| @dict[v] = nil}
220
+
221
+ # Parsing command line options
222
+ parse do |opt, value|
223
+ key = HelpParser.str2sym opt
224
+ # Translate key to the terminal key.
225
+ key = @dict[key] unless @dict[key].nil?
226
+ # Ensure user only uses the defined keys.
227
+ usage_error "not a valid option: #{opt}" unless @dict.has_key?(key)
228
+ # As we parse ARGV, we should only be setting keys once.
229
+ usage_error "duplicate command line option: #{opt}" if self.has_key?(key)
230
+ self[key] = value
231
+ end
232
+
233
+ # The parse takes all options to its terminal value.
234
+ # Now set the synonims...
235
+ @dict.each do |synonim, terminal|
236
+ next if terminal.nil?
237
+ if self.has_key?(terminal)
238
+ # I did not mean for the synonyms to have been set yet, so if I do,
239
+ # I have a bug somewhere.
240
+ raise "unexpected key overwrite" if self.has_key?(synonim)
241
+ self[synonim] = self[terminal]
242
+ end
243
+ end
244
+
245
+ # Did the user ask for help or version?
246
+ raise HelpException, self if self[:h]==true
247
+ raise VersionException, self if self[:v]==true
248
+ end
249
+
250
+ # HelpParser#defaults is a helper method to []
251
+ def defaults(symbol, default1, default2=default1)
252
+ type = (default2 || default1).class
253
+ a = self[symbol]
254
+ value = (a.nil?)? default1 : a
255
+ value = default2 if value==true
256
+ # !value is true if value is false or nil.
257
+ usage_error "type error: #{symbol} needs to be #{type}" unless !value or value.class == type
258
+ return value
259
+ end
260
+
261
+ # HelpParser#defaults! is used to replace value with a
262
+ # default in the hash, self.
263
+ def defaults!(symbol, default1, default2)
264
+ self[symbol] = defaults(symbol, default1, default2)
265
+ end
266
+
267
+ # HelpParser#[k, default1=nil, default2=nil]
268
+ # Basically, for #[k, default1, default2]:
269
+ # value = help_parser[k]
270
+ # case value
271
+ # when nil then default1
272
+ # when true then default2
273
+ # else value
274
+ # end
275
+ # For #[k, default1]:
276
+ # value = help_parser[k] || default1
277
+ # For #[k]:
278
+ # help_parser[k]
279
+ # Raises a UsageError if the value is not of the expected
280
+ # type as given by the last default.
281
+ def [](k, *defaults12)
282
+ (defaults12.length==0)? super(k) : defaults(k, *defaults12)
283
+ end
284
+ end
285
+ end