configurable 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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