configurable 0.7.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Help/Command Line.rdoc +141 -0
  2. data/Help/Config Syntax.rdoc +229 -0
  3. data/Help/Config Types.rdoc +143 -0
  4. data/{History → History.rdoc} +9 -0
  5. data/MIT-LICENSE +1 -1
  6. data/README.rdoc +144 -0
  7. data/lib/configurable.rb +7 -270
  8. data/lib/configurable/class_methods.rb +344 -367
  9. data/lib/configurable/config_classes.rb +3 -0
  10. data/lib/configurable/config_classes/list_config.rb +26 -0
  11. data/lib/configurable/config_classes/nest_config.rb +50 -0
  12. data/lib/configurable/config_classes/scalar_config.rb +91 -0
  13. data/lib/configurable/config_hash.rb +87 -112
  14. data/lib/configurable/config_types.rb +6 -0
  15. data/lib/configurable/config_types/boolean_type.rb +22 -0
  16. data/lib/configurable/config_types/float_type.rb +11 -0
  17. data/lib/configurable/config_types/integer_type.rb +11 -0
  18. data/lib/configurable/config_types/nest_type.rb +39 -0
  19. data/lib/configurable/config_types/object_type.rb +58 -0
  20. data/lib/configurable/config_types/string_type.rb +15 -0
  21. data/lib/configurable/conversions.rb +91 -0
  22. data/lib/configurable/module_methods.rb +0 -1
  23. data/lib/configurable/version.rb +1 -5
  24. metadata +73 -30
  25. data/README +0 -207
  26. data/lib/cdoc.rb +0 -413
  27. data/lib/cdoc/cdoc_html_generator.rb +0 -38
  28. data/lib/cdoc/cdoc_html_template.rb +0 -42
  29. data/lib/config_parser.rb +0 -563
  30. data/lib/config_parser/option.rb +0 -108
  31. data/lib/config_parser/switch.rb +0 -44
  32. data/lib/config_parser/utils.rb +0 -177
  33. data/lib/configurable/config.rb +0 -97
  34. data/lib/configurable/indifferent_access.rb +0 -35
  35. data/lib/configurable/nest_config.rb +0 -78
  36. data/lib/configurable/ordered_hash_patch.rb +0 -85
  37. data/lib/configurable/utils.rb +0 -186
  38. data/lib/configurable/validation.rb +0 -768
@@ -1,359 +1,202 @@
1
+ require 'lazydoc'
1
2
  require 'configurable/config_hash'
2
- require 'configurable/indifferent_access'
3
- require 'configurable/validation'
4
-
5
- autoload(:ConfigParser, 'config_parser')
3
+ require 'configurable/conversions'
6
4
 
7
5
  module Configurable
8
6
 
7
+ # Hash of default config types (bool, integer, float, string).
8
+ DEFAULT_CONFIG_TYPES = {
9
+ :bool => ConfigTypes::BooleanType,
10
+ :integer => ConfigTypes::IntegerType,
11
+ :float => ConfigTypes::FloatType,
12
+ :string => ConfigTypes::StringType,
13
+ :nest => ConfigTypes::NestType,
14
+ :obj => ConfigTypes::ObjectType
15
+ }
16
+
9
17
  # ClassMethods extends classes that include Configurable and provides methods
10
18
  # for declaring configurations.
11
19
  module ClassMethods
12
- CONFIGURATIONS_CLASS = Hash
20
+ include ConfigClasses
21
+ include ConfigTypes
13
22
 
14
- # A hash of (key, Config) pairs tracking configs defined on self. See
15
- # configurations for all configs declared across all ancestors.
23
+ # A hash of (key, Config) pairs tracking configs defined on self. See the
24
+ # configs method for all configs declared across all ancestors.
16
25
  attr_reader :config_registry
17
26
 
18
- def self.initialize(base) # :nodoc:
27
+ # A hash of (key, ConfigType) pairs tracking config_types defined on self.
28
+ # See the config_types method for all config_types declared across all
29
+ # ancestors.
30
+ attr_reader :config_type_registry
31
+
32
+ def self.initialize(base) # :nodoc:
33
+ base.reset_configs
19
34
  unless base.instance_variable_defined?(:@config_registry)
20
- base.instance_variable_set(:@config_registry, CONFIGURATIONS_CLASS.new)
35
+ base.instance_variable_set(:@config_registry, {})
21
36
  end
22
37
 
23
- unless base.instance_variable_defined?(:@use_indifferent_access)
24
- base.instance_variable_set(:@use_indifferent_access, true)
38
+ base.reset_config_types
39
+ unless base.instance_variable_defined?(:@config_type_registry)
40
+ base.instance_variable_set(:@config_type_registry, {})
25
41
  end
26
42
 
27
- unless base.instance_variable_defined?(:@configurations)
28
- base.instance_variable_set(:@configurations, nil)
43
+ unless base.instance_variable_defined?(:@config_type_context)
44
+ base.instance_variable_set(:@config_type_context, base)
29
45
  end
30
46
  end
31
47
 
32
- # Parses configurations from argv in a non-destructive manner by generating
33
- # a ConfigParser using the configurations for self. Returns an array like
34
- # [args, config] where the args are the arguments that remain after parsing,
35
- # and config is a hash of the parsed configs. The parser is yielded to
36
- # the block, if given, to register additonal options.
37
- #
38
- # See ConfigParser#parse for more information.
39
- def parse(argv=ARGV, options={}) # :yields: parser
40
- parse!(argv.dup, options)
41
- end
42
-
43
- # Same as parse, but removes parsed args from argv.
44
- def parse!(argv=ARGV, options={})
45
- parser = ConfigParser.new
46
- parser.add(configurations)
47
-
48
- args = parser.parse!(argv, options)
49
- [args, parser.config]
50
- end
51
-
52
- # A hash of (key, Config) pairs representing all configurations defined
53
- # on this class or inherited from ancestors. The configurations hash is
54
- # generated on each call to ensure it accurately reflects any configs
55
- # added on ancestors. This slows down initialization and config access
56
- # through instance.config.
57
- #
58
- # Call cache_configurations after all configs have been declared in order
59
- # to prevent regeneration of configurations and to significantly improve
60
- # performance.
61
- def configurations
62
- return @configurations if @configurations
48
+ # A hash of (key, Config) pairs representing all configs defined on this
49
+ # class or inherited from ancestors. The configs hash is memoized for
50
+ # performance. Call reset_configs if configs needs to be recalculated for
51
+ # any reason.
52
+ #
53
+ # Configs is extended with the Conversions module.
54
+ def configs
55
+ @configs ||= begin
56
+ configs = {}
63
57
 
64
- configurations = CONFIGURATIONS_CLASS.new
65
- configurations.extend(IndifferentAccess) if @use_indifferent_access
66
-
67
- ancestors.reverse.each do |ancestor|
68
- next unless ancestor.kind_of?(ClassMethods)
69
- ancestor.config_registry.each_pair do |key, value|
70
- if value.nil?
71
- configurations.delete(key)
72
- else
73
- configurations[key] = value
58
+ ancestors.reverse.each do |ancestor|
59
+ next unless ancestor.kind_of?(ClassMethods)
60
+ ancestor.config_registry.each_pair do |key, value|
61
+ if value.nil?
62
+ configs.delete(key)
63
+ else
64
+ configs[key] = value
65
+ end
74
66
  end
75
67
  end
68
+
69
+ configs.extend Conversions
70
+ configs
76
71
  end
77
-
78
- configurations
79
72
  end
80
73
 
81
- # Caches the configurations hash so as to improve peformance. Call
82
- # with on set to false to turn off caching.
83
- def cache_configurations(on=true)
84
- @configurations = nil
85
- @configurations = self.configurations if on
74
+ # Resets configs such that they will be recalculated.
75
+ def reset_configs
76
+ @configs = nil
86
77
  end
87
78
 
88
- protected
89
-
90
- # Sets configurations to symbolize keys for AGET ([]) and ASET([]=)
91
- # operations, or not. By default, configurations will use
92
- # indifferent access.
93
- def use_indifferent_access(input=true)
94
- @use_indifferent_access = input
95
- return unless @configurations
79
+ # A hash of (key, ConfigType) pairs representing all config_types defined
80
+ # on this class or inherited from ancestors. The config_types hash is
81
+ # memoized for performance. Call reset_config_types if config_types needs
82
+ # to be recalculated for any reason.
83
+ def config_types
84
+ @config_types ||= begin
85
+ config_types = {}
86
+
87
+ registries = []
88
+ each_registry do |ancestor, registry|
89
+ registries.unshift(registry)
90
+ end
91
+
92
+ registries.each do |registry|
93
+ registry.each_pair do |key, value|
94
+ if value.nil?
95
+ config_types.delete(key)
96
+ else
97
+ config_types[key] = value
98
+ end
99
+ end
100
+ end
96
101
 
97
- if @use_indifferent_access
98
- @configurations.extend(IndifferentAccess)
99
- else
100
- @configurations = @configurations.dup
102
+ config_types
101
103
  end
102
104
  end
103
-
104
- # Declares a class configuration and generates the associated accessors.
105
- # If a block is given, the <tt>key=</tt> method will set <tt>@key</tt>
106
- # to the return of the block, which executes in class-context.
107
- #
108
- # class SampleClass
109
- # include Configurable
110
- #
111
- # config :str, 'value'
112
- # config(:upcase, 'value') {|input| input.upcase }
113
- # end
114
- #
115
- # # An equivalent class to illustrate class-context
116
- # class EquivalentClass
117
- # attr_accessor :str
118
- # attr_reader :upcase
119
- #
120
- # UPCASE_BLOCK = lambda {|input| input.upcase }
121
- #
122
- # def upcase=(input)
123
- # @upcase = UPCASE_BLOCK.call(input)
124
- # end
125
- # end
126
- #
127
- def config(key, value=nil, attributes={}, &block)
128
- attributes = merge_attributes(block, attributes)
129
-
130
- if block_given?
131
- instance_variable = "@#{key}".to_sym
132
- config_attr(key, value, attributes) do |input|
133
- instance_variable_set(instance_variable, yield(input))
134
- end
135
- else
136
- config_attr(key, value, attributes)
137
- end
105
+
106
+ # Resets config_types such that they will be recalculated.
107
+ def reset_config_types
108
+ @config_types = nil
138
109
  end
139
-
140
- # Declares a class configuration and generates the associated accessors.
141
- # If a block is given, the <tt>key=</tt> method will perform the block
142
- # with instance-context.
143
- #
144
- # class SampleClass
145
- # include Configurable
146
- #
147
- # def initialize
148
- # initialize_config
149
- # end
150
- #
151
- # config_attr :str, 'value'
152
- # config_attr(:upcase, 'value') {|input| @upcase = input.upcase }
153
- # end
154
- #
155
- # # An equivalent class to illustrate instance-context
156
- # class EquivalentClass
157
- # attr_accessor :str
158
- # attr_reader :upcase
159
- #
160
- # def upcase=(input)
161
- # @upcase = input.upcase
162
- # end
163
- # end
164
- #
165
- # === Attributes
166
- #
167
- # Several attributes may be specified to modify how a config is constructed.
168
- # Attribute keys should be specified as symbols.
169
- #
170
- # Attribute:: Description
171
- # init:: When set to false the config will not initialize
172
- # during initialize_config. (default: true)
173
- # reader:: The method used to read the configuration.
174
- # (default: key)
175
- # writer:: The method used to write the configuration
176
- # (default: "#{key}=")
177
- #
178
- # Neither reader nor writer may be set to nil, but they may be set to
179
- # non-default values. In that case, config_attr will register the method
180
- # names as provided, but it will not define the methods themselves.
181
- # Specifying true defines the default methods. Specifying false makes
182
- # the config expect the default method name, but does not define the method
183
- # itself.
184
- #
185
- # Any additional attributes are registered with the configuration.
186
- def config_attr(key, value=nil, attributes={}, &block)
187
- attributes = merge_attributes(block, attributes)
110
+
111
+ protected
112
+
113
+ attr_accessor :config_type_context
114
+
115
+ # Defines and registers an instance of config_class with the specified key
116
+ # and attrs. Unless attrs specifies a :reader or :writer, the
117
+ # corresponding attr accessors will be defined for the config name (which
118
+ # by default is the key).
119
+ def define_config(key, attrs={}, config_class=ScalarConfig)
120
+ reader = attrs[:reader]
121
+ writer = attrs[:writer]
188
122
 
189
- # define the reader
190
- reader = define_attribute_method(:reader, attributes, key) do |attribute|
191
- attr_reader(attribute)
192
- public(attribute)
193
- end
123
+ config = config_class.new(key, attrs)
194
124
 
195
- # define the writer
196
- if block_given? && attributes[:writer] != true
197
- raise ArgumentError, "a block may not be specified without writer == true"
125
+ unless reader
126
+ attr_reader(config.name)
127
+ public(config.name)
198
128
  end
199
129
 
200
- writer = define_attribute_method(:writer, attributes, "#{key}=") do |attribute|
201
- block_given? ? define_method(attribute, &block) : attr_writer(key)
202
- public(attribute)
130
+ unless writer
131
+ attr_writer(config.name)
132
+ public("#{config.name}=")
203
133
  end
204
134
 
205
- # define the configuration
206
- init = attributes.has_key?(:init) ? attributes.delete(:init) : true
207
- dup = attributes.has_key?(:dup) ? attributes.delete(:dup) : nil
208
- config_registry[key] = Config.new(reader, writer, value, attributes, init, dup)
135
+ config_registry[config.key] = config
136
+ reset_configs
137
+ config
209
138
  end
210
139
 
211
- # Adds nested configurations to self. Nest creates a new configurable
212
- # class using the block, and provides accessors to an instance of the
213
- # new class. Everything is set up so you can access configs through
214
- # the instance or through config.
215
- #
216
- # class A
217
- # include Configurable
218
- #
219
- # config :key, 'one'
220
- # nest :nest do
221
- # config :key, 'two'
222
- # end
223
- # end
224
- #
225
- # a = A.new
226
- # a.key # => 'one'
227
- # a.config[:key] # => 'one'
228
- #
229
- # a.nest.key # => 'two'
230
- # a.config[:nest][:key] # => 'two'
231
- #
232
- # a.nest.key = 'TWO'
233
- # a.config[:nest][:key] # => 'TWO'
234
- #
235
- # a.config[:nest][:key] = 2
236
- # a.nest.key # => 2
237
- #
238
- # a.config.to_hash # => {:key => 'one', :nest => {:key => 2}}
239
- # a.nest.config.to_hash # => {:key => 2}
240
- # a.nest.class # => A::Nest
241
- #
242
- # An existing configurable class may be provided instead of using the block
243
- # to define a new configurable class. Recursive nesting is supported.
244
- #
245
- # class B
246
- # include Configurable
247
- #
248
- # config :key, 1, &c.integer
249
- # nest :nest do
250
- # config :key, 2, &c.integer
251
- # nest :nest do
252
- # config :key, 3, &c.integer
253
- # end
254
- # end
255
- # end
256
- #
257
- # class C
258
- # include Configurable
259
- # nest :a, A
260
- # nest :b, B
261
- # end
262
- #
263
- # c = C.new
264
- # c.b.key = 7
265
- # c.b.nest.key = "8"
266
- # c.config[:b][:nest][:nest][:key] = "9"
267
- #
268
- # c.config.to_hash
269
- # # => {
270
- # # :a => {
271
- # # :key => 'one',
272
- # # :nest => {:key => 'two'}
273
- # # },
274
- # # :b => {
275
- # # :key => 7,
276
- # # :nest => {
277
- # # :key => 8,
278
- # # :nest => {:key => 9}
279
- # # }
280
- # # }}
281
- #
282
- # === Attributes
283
- #
284
- # Nest uses the same attributes as config_attr, with a couple additions:
285
- #
286
- # Attribute:: Description
287
- # const_name:: Determines the constant name of the configurable
288
- # class within the nesting class. May be nil.
289
- # (default: key.to_s.capitalize)
290
- # type:: By default set to :nest.
291
- #
292
- def nest(key, configurable_class=nil, attributes={}, &block)
293
- attributes = merge_attributes(block, attributes)
294
- attributes = {
295
- :reader => true,
296
- :writer => true,
297
- :type => :nest
298
- }.merge(attributes)
140
+ # Defines a config after guessing or setting some standard values into
141
+ # attrs. Specifically:
142
+ #
143
+ # * :default is the default
144
+ # * :caster is the caster block (if provided)
145
+ # * :desc is set using Lazydoc (unless already set)
146
+ # * :list is set to true for array defaults (unless already set)
147
+ #
148
+ # In addition config also guesses the type of a config (if not manually
149
+ # specified by :type) and merges in any attributes for the corresponding
150
+ # config_type. The class of the config is guessed from the attrs, based
151
+ # on the :list and :options attributes using this logic:
152
+ #
153
+ # :list :otions config_class
154
+ # ---------------------------
155
+ # false false Config
156
+ # true false List
157
+ # false true Select
158
+ # true true ListSelect
159
+ #
160
+ # == Usage Note
161
+ #
162
+ # Config is meant to be a convenience method. It gets most things right
163
+ # but if the attrs logic is too convoluted (and at times it is) then
164
+ # define configs manually with the define_config method.
165
+ def config(key, default=nil, attrs={}, &block)
166
+ orig_attrs = attrs.dup
299
167
 
300
- # define the nested configurable
301
- if configurable_class
302
- if block_given?
303
- configurable_class = Class.new(configurable_class)
304
- configurable_class.class_eval(&block)
305
- end
306
- else
307
- configurable_class = Class.new { include Configurable }
308
- configurable_class.class_eval(&block) if block_given?
168
+ if nest_class = guess_nest_class(default, block)
169
+ default = nest_class.new
309
170
  end
310
171
 
311
- # set the new constant
312
- const_name = if attributes.has_key?(:const_name)
313
- attributes.delete(:const_name)
314
- else
315
- key.to_s.capitalize
172
+ if default.kind_of?(Configurable)
173
+ attrs[:configurable] = default
174
+ default = default.config.to_hash
316
175
  end
317
176
 
318
- if const_name
319
- # this prevents a warning in cases where the nesting
320
- # class defines the configurable_class
321
- unless const_defined?(const_name) && const_get(const_name) == configurable_class
322
- const_set(const_name, configurable_class)
323
- end
324
- end
325
- const_name = nil
177
+ attrs[:default] = default
178
+ attrs[:type] = guess_config_type(attrs).new(attrs)
179
+ attrs[:metadata] = guess_config_metadata(Lazydoc.register_caller).merge!(orig_attrs)
180
+ config_class = guess_config_class(attrs)
326
181
 
327
- # define the reader.
328
- reader = define_attribute_method(:reader, attributes, key) do |attribute|
329
- attr_reader attribute
330
- public(attribute)
331
- end
182
+ config = define_config(key, attrs, config_class)
332
183
 
333
- # define the writer. the default the writer validates the
334
- # instance is the correct class then sets the instance variable
335
- instance_variable = "@#{key}".to_sym
336
- writer = define_attribute_method(:writer, attributes, "#{key}=") do |attribute|
337
- define_method(attribute) do |value|
338
- Validation.validate(value, [configurable_class])
339
- instance_variable_set(instance_variable, value)
184
+ if nest_class
185
+ const_name = attrs[:const_name] || guess_nest_const_name(config)
186
+ unless const_defined?(const_name)
187
+ const_set(const_name, nest_class)
340
188
  end
341
- public(attribute)
342
189
  end
343
-
344
- # define the configuration
345
- init = attributes.has_key?(:init) ? attributes.delete(:init) : true
346
- config_registry[key] = NestConfig.new(configurable_class, reader, writer, attributes, init)
347
- check_infinite_nest(configurable_class)
348
- end
190
+
191
+ config
192
+ end
349
193
 
350
- # Removes a configuration much like remove_method removes a method. The
351
- # reader and writer for the config are likewise removed. Nested configs
352
- # can be removed using this method.
353
- #
354
- # Setting :reader or :writer to false in the options prevents those methods
355
- # from being removed.
194
+ # Removes a config much like remove_method removes a method. The reader
195
+ # and writer for the config are likewise removed. Nested configs can be
196
+ # removed using this method.
356
197
  #
198
+ # Setting :reader or :writer to false in the options prevents those
199
+ # methods from being removed.
357
200
  def remove_config(key, options={})
358
201
  unless config_registry.has_key?(key)
359
202
  raise NameError.new("#{key.inspect} is not a config on #{self}")
@@ -365,18 +208,20 @@ module Configurable
365
208
  }.merge(options)
366
209
 
367
210
  config = config_registry.delete(key)
368
- cache_configurations(@configurations != nil)
211
+ reset_configs
369
212
 
370
- undef_method(config.reader) if options[:reader]
371
- undef_method(config.writer) if options[:writer]
213
+ remove_method(config.reader) if options[:reader]
214
+ remove_method(config.writer) if options[:writer]
215
+
216
+ config
372
217
  end
373
218
 
374
- # Undefines a configuration much like undef_method undefines a method. The
219
+ # Undefines a config much like undef_method undefines a method. The
375
220
  # reader and writer for the config are likewise undefined. Nested configs
376
221
  # can be undefined using this method.
377
222
  #
378
- # Setting :reader or :writer to false in the options prevents those methods
379
- # from being undefined.
223
+ # Setting :reader or :writer to false in the options prevents those
224
+ # methods from being undefined.
380
225
  #
381
226
  # ==== Implementation Note
382
227
  #
@@ -384,12 +229,9 @@ module Configurable
384
229
  # Deleting the config is not sufficient because the registry needs to
385
230
  # convey to self and subclasses to not inherit the config from ancestors.
386
231
  #
387
- # This is unlike remove_config where the config is simply deleted from
388
- # the config_registry.
389
- #
232
+ # This is unlike remove_config where the config is simply deleted from the
233
+ # config_registry.
390
234
  def undef_config(key, options={})
391
- # temporarily cache as an optimization
392
- configs = configurations
393
235
  unless configs.has_key?(key)
394
236
  raise NameError.new("#{key.inspect} is not a config on #{self}")
395
237
  end
@@ -401,77 +243,212 @@ module Configurable
401
243
 
402
244
  config = configs[key]
403
245
  config_registry[key] = nil
404
- cache_configurations(@configurations != nil)
246
+ reset_configs
405
247
 
406
248
  undef_method(config.reader) if options[:reader]
407
249
  undef_method(config.writer) if options[:writer]
250
+
251
+ config
408
252
  end
409
253
 
410
- # Alias for Validation
411
- def c
412
- Validation
254
+ def define_config_type(name, config_type)
255
+ config_type_registry[name] = config_type
256
+ reset_config_types
257
+ config_type
413
258
  end
414
-
259
+
260
+ def config_type(name, *matchers, &caster)
261
+ config_type = StringType.subclass(*matchers).cast(&caster)
262
+ const_name = guess_config_type_const_name(name)
263
+
264
+ unless const_defined?(const_name)
265
+ const_set(const_name, config_type)
266
+ end
267
+
268
+ define_config_type(name, config_type)
269
+ end
270
+
271
+ # Removes a config_type much like remove_method removes a method.
272
+ def remove_config_type(name)
273
+ unless config_type_registry.has_key?(name)
274
+ raise NameError.new("#{name.inspect} is not a config_type on #{self}")
275
+ end
276
+
277
+ config_type = config_type_registry.delete(name)
278
+ reset_config_types
279
+ config_type
280
+ end
281
+
282
+ # Undefines a config_type much like undef_method undefines a method.
283
+ #
284
+ # ==== Implementation Note
285
+ #
286
+ # ConfigClasses are undefined by setting the key to nil in the registry.
287
+ # Deleting the config_type is not sufficient because the registry needs to
288
+ # convey to self and subclasses to not inherit the config_type from
289
+ # ancestors.
290
+ #
291
+ # This is unlike remove_config_type where the config_type is simply
292
+ # deleted from the config_type_registry.
293
+ def undef_config_type(name)
294
+ unless config_types.has_key?(name)
295
+ raise NameError.new("#{name.inspect} is not a config_type on #{self}")
296
+ end
297
+
298
+ config_type = config_type_registry[name]
299
+ config_type_registry[name] = nil
300
+ reset_config_types
301
+ config_type
302
+ end
303
+
415
304
  private
416
305
 
417
306
  def inherited(base) # :nodoc:
418
- ClassMethods.initialize(base)
419
- super
307
+ ClassMethods.initialize(base)
308
+ super
309
+ end
310
+
311
+ def guess_nest_class(base, block) # :nodoc:
312
+ unless base.kind_of?(Hash) || block
313
+ return nil
314
+ end
315
+
316
+ nest_class = Class.new { include Configurable }
317
+ nest_class.config_type_context = self
318
+
319
+ if base.kind_of?(Hash)
320
+ base.each_pair do |key, value|
321
+ nest_class.send(:config, key, value)
322
+ end
323
+ end
324
+
325
+ if block
326
+ nest_class.class_eval(&block)
327
+ end
328
+
329
+ check_infinite_nest(nest_class)
330
+ nest_class
331
+ end
332
+
333
+ # helper to recursively check for an infinite nest
334
+ def check_infinite_nest(klass) # :nodoc:
335
+ raise "infinite nest detected" if klass == self
336
+
337
+ klass.configs.each_value do |config|
338
+ if config.type.kind_of?(NestType)
339
+ check_infinite_nest(config.type.configurable.class)
340
+ end
341
+ end
420
342
  end
421
343
 
422
- # a helper to define methods that may be overridden in attributes.
423
- # yields the default to the block if the default is supposed to
424
- # be defined. returns the symbolized method name.
425
- def define_attribute_method(name, attributes, default) # :nodoc:
426
- attribute = attributes.delete(name)
344
+ def each_registry # :nodoc:
345
+ # yield the registry for self first to take account of times when
346
+ # config_type_context.ancestors does not include self (otherwise
347
+ # config types defined on self are missed)
348
+ yield self, config_type_registry
427
349
 
428
- case attribute
429
- when true
430
- # true means use the default and define the method
431
- attribute = default
432
- yield(attribute)
433
-
434
- when false
435
- # false means use the default, but let the user define the method
436
- attribute = default
437
-
438
- when nil
439
- # nil is not allowed
440
- raise "#{name.inspect} attribute cannot be nil"
350
+ config_type_context.ancestors.each do |ancestor|
351
+ case
352
+ when ancestor == self
353
+ next
354
+
355
+ when ancestor.kind_of?(ClassMethods)
356
+ yield ancestor, ancestor.config_type_registry
357
+
358
+ when ancestor == Configurable
359
+ yield ancestor, Configurable::DEFAULT_CONFIG_TYPES
360
+ break
361
+
362
+ else next
363
+ end
441
364
  end
442
- # ... all other values specify what the method should be,
443
- # and lets the user define the method.
444
-
445
- attribute.to_sym
446
365
  end
447
366
 
448
- # a helper method to merge the default attributes for the block with
449
- # the input attributes. also registers a Trailer description.
450
- def merge_attributes(block, attributes) # :nodoc:
451
- defaults = DEFAULT_ATTRIBUTES[nil].dup
452
- defaults.merge!(DEFAULT_ATTRIBUTES[block]) if block
453
- defaults.merge!(attributes)
367
+ def guess_config_type_by_name(name) # :nodoc:
368
+ return name if name.nil?
454
369
 
455
- # register with Lazydoc
456
- defaults[:desc] ||= Lazydoc.register_caller(Lazydoc::Trailer, 2)
370
+ each_registry do |ancestor, registry|
371
+ if registry.has_key?(name)
372
+ return registry[name]
373
+ end
374
+ end
457
375
 
458
- defaults
376
+ raise "no such config type: #{type.inspect}"
459
377
  end
460
378
 
461
- # helper to recursively check for an infinite nest
462
- def check_infinite_nest(klass) # :nodoc:
463
- raise "infinite nest detected" if klass == self
379
+ def guess_config_type_by_value(value) # :nodoc:
380
+ each_registry do |ancestor, registry|
381
+ guesses = registry.values.select {|config_type| config_type.matches?(value) }
382
+
383
+ case guesses.length
384
+ when 0 then next
385
+ when 1 then return guesses.at(0)
386
+ else raise "multiple guesses for config type: #{guesses.inspect} (in: #{ancestor} default: #{default.inspect})"
387
+ end
388
+ end
464
389
 
465
- klass.configurations.each_value do |delegate|
466
- if delegate.kind_of?(NestConfig)
467
- check_infinite_nest(delegate.nest_class)
390
+ ObjectType
391
+ end
392
+
393
+ def guess_config_type(attrs) # :nodoc:
394
+ if attrs.has_key?(:type)
395
+ guess_config_type_by_name attrs[:type]
396
+ else
397
+ value = attrs[:default]
398
+ value = value.at(0) if value.kind_of?(Array)
399
+ guess_config_type_by_value value
400
+ end
401
+ end
402
+
403
+ def guess_config_class(attrs) # :nodoc:
404
+ if attrs.has_key?(:class)
405
+ attrs[:class]
406
+ else
407
+ case attrs[:default]
408
+ when Array then ListConfig
409
+ when Hash then NestConfig
410
+ else ScalarConfig
468
411
  end
469
412
  end
470
413
  end
414
+
415
+ def guess_config_metadata(comment) # :nodoc:
416
+ Hash.new do |hash, key|
417
+ comment.resolve
418
+
419
+ if trailer = comment.trailer
420
+ flags, desc = trailer.split(':', 2)
421
+
422
+ if desc.nil?
423
+ flags, desc = '', flags
424
+ elsif flags.strip.empty?
425
+ hash[:hidden] = true
426
+ else
427
+ hash[:long] = nil
428
+ hash[:short] = nil
429
+ hash[:arg_name] = nil
430
+ end
431
+
432
+ argv = flags.split(',').collect! {|arg| arg.strip }
433
+ argv << desc.strip
434
+
435
+ comment_attrs = ConfigParser::Utils.parse_attrs(argv)
436
+ comment_attrs.each_pair do |attr_key, attr_value|
437
+ hash[attr_key] = attr_value
438
+ end
439
+ end
440
+
441
+ hash[:help] = comment.content
442
+ hash.has_key?(key) ? hash[key] : nil
443
+ end
444
+ end
445
+
446
+ def guess_nest_const_name(config) # :nodoc:
447
+ config.name.gsub(/(?:^|_)(.)/) { $1.upcase }
448
+ end
449
+
450
+ def guess_config_type_const_name(name) # :nodoc:
451
+ "#{name}Type".gsub(/(?:^|_)(.)/) { $1.upcase }
452
+ end
471
453
  end
472
- end
473
-
474
- # Apply the ordered hash patch if necessary
475
- if RUBY_VERSION < '1.9'
476
- require 'configurable/ordered_hash_patch'
477
454
  end