configurable 0.7.0 → 1.0.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.
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