configurable 0.5.0 → 0.6.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/History CHANGED
@@ -1,3 +1,22 @@
1
+ == 0.6.0 / 2009-12-05
2
+
3
+ * minor bug fixes in interface
4
+ * added on! to ConfigParser to specify override options
5
+ * updates to use latest Lazydoc
6
+ * added scrub to DelegateHash#to_hash, to remove keys
7
+ set to the default value
8
+ * added strbol validation
9
+ * nil long options are now respected
10
+ * refactored default_config to defaults in ConfigParser
11
+ * refactored Validation register syntax
12
+ * added scan method to ConfigParser
13
+ * ConfigParser can no longer ignore unknown options
14
+ * added configurable option breaks to ConfigParser
15
+ * refactored Delegate to Config, and simplified
16
+ implementation for nesting to use NestConfig
17
+ * refactored :duplicate_default attribute to :dup
18
+ * added undef_config and remove_config methods
19
+
1
20
  == 0.5.0 / 2009-05-25
2
21
 
3
22
  * fixed io validation to not duplicate IOs
@@ -1,19 +1,21 @@
1
1
  Copyright (c) 2008-2009, Regents of the University of Colorado.
2
2
 
3
- Permission is hereby granted, free of charge, to any person obtaining a copy of this
4
- software and associated documentation files (the "Software"), to deal in the Software
5
- without restriction, including without limitation the rights to use, copy, modify, merge,
6
- publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7
- to whom the Software is furnished to do so, subject to the following conditions:
3
+ Copyright (c) 2009, Simon Chiang.
8
4
 
9
- The above copyright notice and this permission notice shall be included in all copies or
10
- substantial portions of the Software.
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
11
 
12
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
16
- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
17
- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
19
- OTHER DEALINGS IN THE SOFTWARE.
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README CHANGED
@@ -9,45 +9,103 @@ are inheritable, delegate to methods, and have hash-like access. Configurable
9
9
  maps configurations to the command line through ConfigParser and is used by
10
10
  the Tap[http://tap.rubyforge.org] framework.
11
11
 
12
- Check out these links for development, and bug tracking.
12
+ Check out these links for development and bug tracking.
13
13
 
14
14
  * Website[http://tap.rubyforge.org/configurable]
15
15
  * Github[http://github.com/bahuvrihi/configurable/tree/master]
16
- * Lighthouse[http://bahuvrihi.lighthouseapp.com/projects/21202-configurable/tickets?q=state%3Aopen]
17
16
  * {Google Group}[http://groups.google.com/group/ruby-on-tap]
18
17
 
19
- == Usage
18
+ ==== Minimal Example
20
19
 
21
- Use the config method to declare class configurations. A block may be provided
22
- to validate inputs, and many standard validations are available through the 'c'
23
- method (an alias for the {Validation}[link:classes/Configurable/Validation.html]
24
- module).
20
+ Include Configurable and declare configurations using the config method.
21
+ Configs now have accessors and initialize with the default value.
25
22
 
26
23
  class ConfigClass
27
24
  include Configurable
25
+ config :key, 'default', :short => 'k' # a sample config
26
+ end
27
+
28
+ c = ConfigClass.new
29
+ c.key # => 'default'
30
+ c.key = 'new value'
31
+ c.config[:key] # => 'new value'
32
+
33
+ Use a ConfigParser to parse configurations from the command line. Non-class
34
+ options may be defined with (mostly) the same syntax as {OptionParser}[http://www.ruby-doc.org/core/classes/OptionParser.html]:
35
+
36
+ parser = ConfigParser.new
37
+
38
+ # add class configurations
39
+ parser.add(ConfigClass.configurations)
40
+
41
+ # define an option a-la OptionParser
42
+ parser.on '-s', '--long ARGUMENT', 'description' do |value|
43
+ parser[:long] = value
44
+ end
45
+
46
+ parser.parse "one two --key value -s VALUE three"
47
+ # => ['one', 'two', 'three']
48
+
49
+ parser.config
50
+ # => {
51
+ # :key => 'value',
52
+ # :long => 'VALUE'
53
+ # }
54
+
55
+ "\n" + parser.to_s
56
+ # => %Q{
57
+ # -k, --key KEY a sample config
58
+ # -s, --long ARGUMENT description
59
+ # }
28
60
 
29
- config :key, 'default', :short => 'k' # a simple config with short
30
- config :flag, false, &c.flag # a flag config
31
- config :switch, false, &c.switch # a --[no-]switch config
32
- config :num, 10, &c.integer # integer only
33
- config :range, 1..10, &c.range # range only
34
- config :upcase, 'default' do |value| # custom transformation
61
+ == Usage
62
+
63
+ The config method is used to declare class configurations. A block may be
64
+ provided to validate/transform inputs; many standard validations are available
65
+ through the 'c' method (an alias for the
66
+ {Validation}[link:classes/Configurable/Validation.html] module).
67
+
68
+ class ConfigClass
69
+ include Configurable
70
+
71
+ # basic #
72
+
73
+ config :key, 'default' # a simple config
74
+ config :flag, false, &c.flag # a flag config
75
+ config :switch, false, &c.switch # a --[no-]switch config
76
+ config :num, 10, &c.integer # integer only
77
+
78
+ # fancy #
79
+
80
+ config :range, 1..10, &c.range # specifies a range
81
+ config :select, 'a', &c.select('a','b') # value must be 'a' or 'b'
82
+ config :list, [], &c.list # allows a list of entries
83
+
84
+ # custom #
85
+
86
+ config :upcase, 'default' do |value| # custom transformation
35
87
  value.upcase
36
88
  end
37
-
38
- def initialize(overrides={})
39
- initialize_config(overrides)
89
+
90
+ config :alt, 'default', # alternative flags
91
+ :short => 's',
92
+ :long => 'long',
93
+ :arg_name => 'CUSTOM'
94
+
95
+ # Initializes a new instance, setting the overriding configs.
96
+ def initialize(config={})
97
+ initialize_config(config)
40
98
  end
41
99
  end
42
100
 
43
- A ConfigParser can parse configurations from command line arguments, and turn
44
- them into a documented help string:
101
+ ConfigParser uses the config declarations to parse configurations and to make
102
+ a documented help string:
45
103
 
46
104
  parser = ConfigParser.new
47
105
  parser.add(ConfigClass.configurations)
48
106
 
49
- parser.parse "one two --key=value --flag --no-switch --num 8 --range a..z three"
50
- # => ['one', 'two', 'three']
107
+ parser.parse "a b --key=value --flag --no-switch --num 8 c"
108
+ # => ['a', 'b', 'c']
51
109
 
52
110
  parser.config
53
111
  # => {
@@ -55,40 +113,49 @@ them into a documented help string:
55
113
  # :flag => true,
56
114
  # :switch => false,
57
115
  # :num => '8',
58
- # :range => 'a..z',
59
- # :upcase => 'default'
116
+ # :range => 1..10,
117
+ # :select => 'a',
118
+ # :list => [],
119
+ # :upcase => 'default',
120
+ # :alt => 'default'
60
121
  # }
61
122
 
62
123
  "\n" + parser.to_s
63
124
  # => %Q{
64
- # -k, --key KEY a simple config with short
125
+ # --key KEY a simple config
65
126
  # --flag a flag config
66
127
  # --[no-]switch a --[no-]switch config
67
128
  # --num NUM integer only
68
- # --range RANGE range only
129
+ # --range RANGE specifies a range
130
+ # --select SELECT value must be 'a' or 'b'
131
+ # --list LIST allows a list of entries
69
132
  # --upcase UPCASE custom transformation
133
+ # -s, --long CUSTOM alternative flags
70
134
  # }
71
135
 
72
136
  Configurable classes typically call initialize_config to set configurations
73
137
  during initialization. The validation/transformation blocks are called as
74
- configurations are set. Notice how the :range and :upcase values have been
75
- transformed from the input config.
138
+ configurations are set. Notice how the :num and :upcase configs are translated
139
+ on the instance:
76
140
 
77
141
  c = ConfigClass.new(parser.config)
78
142
  c.config.to_hash
79
143
  # => {
80
144
  # :key => 'value',
81
- # :flag => true,
82
- # :switch => false,
83
- # :num => 8,
84
- # :range => 'a'..'z', # notice these values
85
- # :upcase => 'DEFAULT' # have been transformed
145
+ # :flag => true,
146
+ # :switch => false,
147
+ # :num => 8, # no longer a string
148
+ # :range => 1..10,
149
+ # :select => 'a',
150
+ # :list => [],
151
+ # :upcase => 'DEFAULT', # no longer downcase
152
+ # :alt => 'default'
86
153
  # }
87
154
 
88
155
  Configurations automatically generate accessors (the blocks are basically
89
- writer methods), but they are also accessible through the hash-like config
90
- object. Configurations are validated every time they are set, regardless of
91
- whether they are set through an accessor or config.
156
+ writer methods), and may be accessed through the config object. Configurations
157
+ are validated every time they are set, regardless of whether they are set
158
+ through an accessor or config.
92
159
 
93
160
  c.upcase # => 'DEFAULT'
94
161
 
@@ -97,6 +164,10 @@ whether they are set through an accessor or config.
97
164
 
98
165
  c.upcase = 'fiNal Value'
99
166
  c.config[:upcase] # => 'FINAL VALUE'
167
+
168
+ c.select = 'b' # ok
169
+ c.select = 'c' # !> ValidationError
170
+ c.config[:select] = 'c' # !> ValidationError
100
171
 
101
172
  By default config treats string and symbol keys identically, making YAML an
102
173
  obvious choice for configuration files.
@@ -108,28 +179,29 @@ obvious choice for configuration files.
108
179
  }
109
180
 
110
181
  c.reconfigure(YAML.load(yaml_str))
111
- c.config.to_hash
182
+ c.config.to_hash
112
183
  # => {
113
184
  # :key => 'a new value',
114
185
  # :flag => false,
115
186
  # :switch => false,
116
187
  # :num => 8,
117
188
  # :range => 1..100,
118
- # :upcase => 'FINAL VALUE'
189
+ # :select => 'b',
190
+ # :list => [],
191
+ # :upcase => 'FINAL VALUE',
192
+ # :alt => 'default'
119
193
  # }
120
194
 
121
195
  See the Configurable module for more details.
122
196
 
123
197
  == Installation
124
198
 
125
- Configurable is available as a gem on RubyForge[http://rubyforge.org/projects/tap]. Use:
199
+ Configurable is available as a gem on Gemcutter[http://gemcutter.org/gems/configurable].
126
200
 
127
201
  % gem install configurable
128
202
 
129
203
  == Info
130
204
 
131
- Copyright (c) 2008-2009, Regents of the University of Colorado.
132
- Developer:: {Simon Chiang}[http://bahuvrihi.wordpress.com], {Biomolecular Structure Program}[http://biomol.uchsc.edu/], {Hansen Lab}[http://hsc-proteomics.uchsc.edu/hansenlab/]
133
- Support:: CU Denver School of Medicine Deans Academic Enrichment Fund
205
+ Developer:: {Simon Chiang}[http://bahuvrihi.wordpress.com]
134
206
  License:: {MIT-Style}[link:files/MIT-LICENSE.html]
135
207
 
@@ -147,10 +147,36 @@ class ConfigParser
147
147
 
148
148
  result
149
149
  end
150
+
151
+ # Generates a new parser bound to a specific config. All this really
152
+ # means is that each time the parser calls parse, configurations will
153
+ # be added to the config without clearing the config, or adding default
154
+ # values. This can be useful in signaling situations where a config
155
+ # needs to be updated multiple times.
156
+ #
157
+ # psr = ConfigParser.bind
158
+ # psr.define('a', 'default')
159
+ # psr.define('b', 'default')
160
+ #
161
+ # psr.parse %w{--a value}
162
+ # psr.config # => {"a" => "value"}
163
+ #
164
+ # psr.parse %w{--b value}
165
+ # psr.config # => {"a" => "value", "b" => "value"}
166
+ #
167
+ def bind(config={})
168
+ parser = new(config, :clear_config => false, :add_defaults => false)
169
+ yield(parser) if block_given?
170
+ parser
171
+ end
150
172
  end
151
173
 
152
174
  include Utils
153
-
175
+
176
+ # Returns an array of the options registered with self, in the order in
177
+ # which they were added. Separators are also stored in the registry.
178
+ attr_reader :registry
179
+
154
180
  # A hash of (switch, Option) pairs mapping command line
155
181
  # switches like '-s' or '--long' to the Option that
156
182
  # handles them.
@@ -159,16 +185,29 @@ class ConfigParser
159
185
  # The hash receiving configurations produced by parse.
160
186
  attr_accessor :config
161
187
 
162
- # A hash of default configurations merged into config during parse.
163
- attr_reader :default_config
164
-
188
+ # A hash of default configurations merged into config during parse. These
189
+ # defaults are defined as options are added to self (via define, add, etc)
190
+ # and do not need to be manually specified.
191
+ attr_reader :defaults
192
+
193
+ # A hash of default parsing options that adjust the behavior of parse
194
+ # (see parse).
195
+ attr_reader :default_parse_options
196
+
165
197
  # Initializes a new ConfigParser and passes it to the block, if given.
166
- def initialize(config={})
167
- @options = []
198
+ def initialize(config={}, default_parse_options={})
199
+ @registry = []
168
200
  @switches = {}
169
201
  @config = config
170
- @default_config = {}
171
-
202
+ @defaults = {}
203
+ @default_parse_options = {
204
+ :clear_config => true,
205
+ :add_defaults => true,
206
+ :ignore_unknown_options => false,
207
+ :option_break => OPTION_BREAK,
208
+ :keep_break => false
209
+ }.merge(default_parse_options)
210
+
172
211
  yield(self) if block_given?
173
212
  end
174
213
 
@@ -187,24 +226,36 @@ class ConfigParser
187
226
  def nested_config
188
227
  ConfigParser.nest(config)
189
228
  end
190
-
229
+
191
230
  # Returns an array of the options registered with self.
192
231
  def options
193
- @options.select do |opt|
232
+ @registry.select do |opt|
194
233
  opt.kind_of?(Option)
195
234
  end
196
235
  end
197
-
236
+
198
237
  # Adds a separator string to self, used in to_s.
199
238
  def separator(str)
200
- @options << str
239
+ @registry << str
201
240
  end
202
241
 
203
- # Registers the option with self by adding opt to options and mapping
204
- # the opt switches. Raises an error for conflicting switches.
205
- def register(opt)
206
- @options << opt unless @options.include?(opt)
207
-
242
+ # Registers the option with self by adding opt to options and mapping the
243
+ # opt switches. Raises an error for conflicting switches.
244
+ #
245
+ # If override is specified, options with conflicting switches are removed
246
+ # and no error is raised. Note that this may remove multiple options.
247
+ def register(opt, override=false)
248
+ if override
249
+ existing = opt.switches.collect do |switch|
250
+ @switches.delete(switch)
251
+ end
252
+ @registry -= existing
253
+ end
254
+
255
+ unless @registry.include?(opt)
256
+ @registry << opt
257
+ end
258
+
208
259
  opt.switches.each do |switch|
209
260
  case @switches[switch]
210
261
  when opt then next
@@ -253,46 +304,12 @@ class ConfigParser
253
304
  # end
254
305
  #
255
306
  def on(*args, &block)
256
- attributes = args.last.kind_of?(Hash) ? args.pop : {}
257
- args.each do |arg|
258
- # split switch arguments... descriptions
259
- # still won't match as a switch even
260
- # after a split
261
- switch, arg_name = arg.kind_of?(String) ? arg.split(' ', 2) : arg
262
-
263
- # determine the kind of argument specified
264
- key = case switch
265
- when SHORT_OPTION then :short
266
- when LONG_OPTION then :long
267
- else :desc
268
- end
269
-
270
- # check for conflicts
271
- if attributes[key]
272
- raise ArgumentError, "conflicting #{key} options: [#{attributes[key].inspect}, #{arg.inspect}]"
273
- end
274
-
275
- # set the option attributes
276
- case key
277
- when :long, :short
278
- attributes[key] = switch
279
- attributes[:arg_name] = arg_name if arg_name
280
- else
281
- attributes[key] = arg
282
- end
283
- end
284
-
285
- # check if a switch-style option is specified
286
- klass = case
287
- when attributes[:long].to_s =~ /^--\[no-\](.*)$/
288
- attributes[:long] = "--#{$1}"
289
- Switch
290
- else
291
- Option
292
- end
293
-
294
- # instantiate and register the new option
295
- register klass.new(attributes, &block)
307
+ register new_option(args, &block)
308
+ end
309
+
310
+ # Same as on, but overrides options with overlapping switches.
311
+ def on!(*args, &block)
312
+ register new_option(args, &block), true
296
313
  end
297
314
 
298
315
  # Defines and registers a config-style option with self. Define does not
@@ -345,10 +362,10 @@ class ConfigParser
345
362
  # key is already set by a different option.
346
363
  def define(key, default_value=nil, attributes={})
347
364
  # check for conflicts and register
348
- if default_config.has_key?(key)
365
+ if defaults.has_key?(key)
349
366
  raise ArgumentError, "already set by a different option: #{key.inspect}"
350
367
  end
351
- default_config[key] = default_value
368
+ defaults[key] = default_value
352
369
 
353
370
  # ensure setup does not modifiy input attributes
354
371
  attributes = attributes.dup
@@ -370,8 +387,7 @@ class ConfigParser
370
387
  end
371
388
 
372
389
  # Adds a hash of delegates (for example the configurations for a Configurable
373
- # class) to self. Delegates will be sorted by their :declaration_order
374
- # attribute, then added like:
390
+ # class) to self. Configs are added like:
375
391
  #
376
392
  # define(key, delegate.default, delegate.attributes)
377
393
  #
@@ -379,16 +395,18 @@ class ConfigParser
379
395
  #
380
396
  # When you nest Configurable classes, a special syntax is necessary to
381
397
  # specify nested configurations in a flat format compatible with the
382
- # command line. As such, nested delegates, ie delegates with a
383
- # DelegateHash as a default value, are recursively added with their
384
- # key as a prefix. For instance:
385
- #
386
- # delegate_hash = DelegateHash.new(:key => Delegate.new(:reader))
387
- # delegates = {}
388
- # delegates[:nest] = Delegate.new(:reader, :writer=, delegate_hash)
398
+ # command line. As such, nested delegates are recursively added with
399
+ # their key as a prefix. For instance:
389
400
  #
401
+ # class NestClass
402
+ # include Configurable
403
+ # nest :nest do
404
+ # config :key, 'value'
405
+ # end
406
+ # end
407
+ #
390
408
  # psr = ConfigParser.new
391
- # psr.add(delegates)
409
+ # psr.add(NestClass.configurations)
392
410
  # psr.parse('--nest:key value')
393
411
  #
394
412
  # psr.config # => {'nest:key' => 'value'}
@@ -402,14 +420,14 @@ class ConfigParser
402
420
  def add(delegates, nesting=nil)
403
421
  delegates.each_pair do |key, delegate|
404
422
  key = nesting ? "#{nesting}:#{key}" : key
405
- default = delegate.default(false)
406
423
 
407
- if delegate.is_nest?
408
- unless delegate[:type] == :hidden
409
- add(default.delegates, key)
410
- end
424
+ case delegate[:type]
425
+ when :hidden
426
+ next
427
+ when :nest
428
+ add(delegate.nest_class.configurations, key)
411
429
  else
412
- define(key, default, delegate.attributes)
430
+ define(key, delegate.default, delegate.attributes)
413
431
  end
414
432
  end
415
433
  end
@@ -432,63 +450,108 @@ class ConfigParser
432
450
 
433
451
  # Same as parse, but removes parsed args from argv.
434
452
  def parse!(argv=ARGV, options={})
435
- options = {
436
- :clear_config => true,
437
- :add_defaults => true,
438
- :ignore_unknown_options => false
439
- }.merge(options)
440
-
441
- config.clear if options[:clear_config]
442
453
  argv = Shellwords.shellwords(argv) if argv.kind_of?(String)
454
+
443
455
  args = []
456
+ remainder = scan(argv, options) {|arg| args << arg}
457
+ args.concat(remainder)
458
+ argv.replace(args)
459
+
460
+ argv
461
+ end
462
+
463
+ def scan(argv=ARGV, options={})
464
+ options = default_parse_options.merge(options)
465
+ config.clear if options[:clear_config]
444
466
 
467
+ option_break = options[:option_break]
445
468
  while !argv.empty?
446
469
  arg = argv.shift
447
470
 
448
471
  # determine if the arg is an option
449
472
  unless arg.kind_of?(String) && arg[0] == ?-
450
- args << arg
473
+ yield(arg)
451
474
  next
452
475
  end
453
476
 
454
477
  # add the remaining args and break
455
478
  # for the option break
456
- if arg == OPTION_BREAK
457
- args.concat(argv)
479
+ if option_break === arg
480
+ argv.unshift(arg) if options[:keep_break]
458
481
  break
459
482
  end
460
483
 
461
484
  # split the arg...
462
485
  # switch= $1
463
- # value = $4 || $3 (if arg matches SHORT_OPTION, value is $4 or $3 otherwise)
486
+ # value = $2
464
487
  arg =~ LONG_OPTION || arg =~ SHORT_OPTION || arg =~ ALT_SHORT_OPTION
465
488
 
466
489
  # lookup the option
467
490
  unless option = @switches[$1]
468
- if options[:ignore_unknown_options]
469
- args << arg
470
- next
471
- end
472
-
473
- raise "unknown option: #{$1}"
491
+ raise "unknown option: #{$1 || arg}"
474
492
  end
475
493
 
476
- option.parse($1, $4 || $3, argv)
494
+ option.parse($1, $2, argv)
477
495
  end
478
496
 
479
- default_config.each_pair do |key, default|
497
+ defaults.each_pair do |key, default|
480
498
  config[key] = default unless config.has_key?(key)
481
499
  end if options[:add_defaults]
482
500
 
483
- argv.replace(args)
484
501
  argv
485
502
  end
486
503
 
487
504
  # Converts the options and separators in self into a help string suitable for
488
505
  # display on the command line.
489
506
  def to_s
490
- @options.collect do |option|
507
+ @registry.collect do |option|
491
508
  option.to_s.rstrip
492
509
  end.join("\n") + "\n"
493
510
  end
511
+
512
+ protected
513
+
514
+ # helper to parse an option from an argv. new_option is used
515
+ # by on and on! to generate options
516
+ def new_option(argv, &block) # :nodoc:
517
+ attributes = argv.last.kind_of?(Hash) ? argv.pop : {}
518
+ argv.each do |arg|
519
+ # split switch arguments... descriptions
520
+ # still won't match as a switch even
521
+ # after a split
522
+ switch, arg_name = arg.kind_of?(String) ? arg.split(' ', 2) : arg
523
+
524
+ # determine the kind of argument specified
525
+ key = case switch
526
+ when SHORT_OPTION then :short
527
+ when LONG_OPTION then :long
528
+ else :desc
529
+ end
530
+
531
+ # check for conflicts
532
+ if attributes[key]
533
+ raise ArgumentError, "conflicting #{key} options: [#{attributes[key].inspect}, #{arg.inspect}]"
534
+ end
535
+
536
+ # set the option attributes
537
+ case key
538
+ when :long, :short
539
+ attributes[key] = switch
540
+ attributes[:arg_name] = arg_name if arg_name
541
+ else
542
+ attributes[key] = arg
543
+ end
544
+ end
545
+
546
+ # check if a switch-style option is specified
547
+ klass = case
548
+ when attributes[:long].to_s =~ /^--\[no-\](.*)$/
549
+ attributes[:long] = "--#{$1}"
550
+ Switch
551
+ else
552
+ Option
553
+ end
554
+
555
+ klass.new(attributes, &block)
556
+ end
494
557
  end