configurable 0.3.0 → 0.4.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,10 @@
1
+ == 0.4.0 / 2009-03-05
2
+
3
+ Reworked nesting. Changes are not backward compatible.
4
+
5
+ * Nesting syntax reworked
6
+ * Delegates no longer accept nil reader/writers
7
+
1
8
  == 0.3.0 / 2009-02-17
2
9
 
3
10
  Significant rework of the 0.1.0 release:
data/lib/configurable.rb CHANGED
@@ -111,18 +111,14 @@ require 'configurable/class_methods'
111
111
  # alt.set_sym('two')
112
112
  # alt.config[:sym] # => :two
113
113
  #
114
- # Idiosyncratically, true, false, and nil may also be provided as reader/writer
115
- # options.
114
+ # Idiosyncratically, true and false may also be provided as reader/writer
115
+ # values.
116
116
  #
117
- # true Same as using the defaults, accessors are defined.
118
- #
119
- # false Sets the default reader/writer but does not define
117
+ # true:: Same as using the defaults, accessors are defined.
118
+ # false:: Sets the default reader/writer but does not define
120
119
  # the accessors (think 'define reader/writer' => false).
121
120
  #
122
- # nil Does not define a reader/writer, and does not define
123
- # the accessors. In effect this will define a config
124
- # that does not map to the instance, but will be
125
- # present in instance.config
121
+ # Nil is not allowed as a value.
126
122
  #
127
123
  # ==== Non-reader/writer attributes
128
124
  #
@@ -133,223 +133,224 @@ module Configurable
133
133
  # end
134
134
  # end
135
135
  #
136
+ # === Attributes
137
+ #
138
+ # Several attributes may be specified to modify how a config is constructed.
139
+ # Attribute keys should be specified as symbols.
140
+ #
141
+ # Attribute:: Description
142
+ # reader:: The method used to read the configuration.
143
+ # (default: key)
144
+ # writer:: The method used to write the configuration
145
+ # (default: "#{key}=")
146
+ #
147
+ # Neither attribute may be set to nil, but they may be set to non-default
148
+ # values. In that case, config_attr will register the method names as
149
+ # provided, but it will not define the methods themselves. Specifying true
150
+ # uses and defines the default methods. Specifying false uses the default
151
+ # method name, but does not define the method itself.
152
+ #
153
+ # Any additional attributes are registered with the configuration.
136
154
  def config_attr(key, value=nil, attributes={}, &block)
137
155
  attributes = merge_attributes(block, attributes)
138
156
 
139
- # define the default public reader method
140
- reader = attributes.delete(:reader)
141
-
142
- case reader
143
- when true
144
- reader = key
145
- attr_reader(key)
146
- public(key)
147
- when false
148
- reader = key
157
+ # define the reader
158
+ reader = define_attribute_method(:reader, attributes, key) do |attribute|
159
+ attr_reader(attribute)
160
+ public(attribute)
149
161
  end
150
-
151
- # define the default public writer method
152
- writer = attributes.delete(:writer)
153
-
154
- if block_given? && writer != true
162
+
163
+ # define the writer
164
+ if block_given? && attributes[:writer] != true
155
165
  raise ArgumentError, "a block may not be specified without writer == true"
156
166
  end
157
-
158
- case writer
159
- when true
160
- writer = "#{key}="
161
- block_given? ? define_method(writer, &block) : attr_writer(key)
162
- public writer
163
- when false
164
- writer = "#{key}="
167
+
168
+ writer = define_attribute_method(:writer, attributes, "#{key}=") do |attribute|
169
+ block_given? ? define_method(attribute, &block) : attr_writer(key)
170
+ public(attribute)
165
171
  end
166
-
172
+
167
173
  configurations[key] = Delegate.new(reader, writer, value, attributes)
168
174
  end
169
-
170
- # Adds a configuration to self accessing the configurations for the
171
- # configurable class. Unlike config_attr and config, nest does not
172
- # create accessors; the configurations must be accessed through
173
- # the instance config method.
175
+
176
+ # Adds nested configurations to self. Nest creates a new configurable
177
+ # class using the block, and provides accessors to an instance of the
178
+ # new class. Everything is set up so you can access configs through
179
+ # the instance or through config.
174
180
  #
175
181
  # class A
176
182
  # include Configurable
177
- # config :key, 'value'
178
183
  #
179
- # def initialize(overrides={})
180
- # initialize_config(overrides)
184
+ # config :key, 'one'
185
+ # nest :nest do
186
+ # config :key, 'two'
181
187
  # end
182
188
  # end
183
189
  #
184
- # class B
185
- # include Configurable
186
- # nest :a, A
190
+ # a = A.new
191
+ # a.key # => 'one'
192
+ # a.config[:key] # => 'one'
187
193
  #
188
- # def initialize(overrides={})
189
- # initialize_config(overrides)
190
- # end
191
- # end
194
+ # a.nest.key # => 'two'
195
+ # a.config[:nest][:key] # => 'two'
196
+ #
197
+ # a.nest.key = 'TWO'
198
+ # a.config[:nest][:key] # => 'TWO'
192
199
  #
193
- # b = B.new
194
- # b.config[:a] # => {:key => 'value'}
200
+ # a.config[:nest][:key] = 2
201
+ # a.nest.key # => 2
195
202
  #
196
- # Nest may be provided a block which initializes an instance of
197
- # configurable_class. In this case accessors for the instance
198
- # are created and access becomes quite natural.
203
+ # a.config.to_hash # => {:key => 'one', :nest => {:key => 2}}
204
+ # a.nest.config.to_hash # => {:key => 2}
205
+ # a.nest.class # => A::Nest
199
206
  #
200
- # class C
207
+ # An existing configurable class may be provided instead of using the block
208
+ # to define a new configurable class. Recursive nesting is supported.
209
+ #
210
+ # class B
201
211
  # include Configurable
202
- # nest(:a, A) {|overrides| A.new(overrides) }
203
212
  #
204
- # def initialize(overrides={})
205
- # initialize_config(overrides)
213
+ # config :key, 1, &c.integer
214
+ # nest :nest do
215
+ # config :key, 2, &c.integer
216
+ # nest :nest do
217
+ # config :key, 3, &c.integer
218
+ # end
206
219
  # end
207
220
  # end
208
221
  #
209
- # c = C.new
210
- # c.a.key # => "value"
211
- #
212
- # c.a.key = "one"
213
- # c.config[:a].to_hash # => {:key => 'one'}
214
- #
215
- # c.config[:a][:key] = 'two'
216
- # c.a.key # => "two"
217
- #
218
- # c.config[:a] = {:key => 'three'}
219
- # c.a.key # => "three"
220
- #
221
- # The initialize block executes in class context, much like config.
222
- #
223
- # # An equivalent class to illustrate class-context
224
- # class EquivalentClass
225
- # attr_reader :a, A
226
- #
227
- # INITIALIZE_BLOCK = lambda {|overrides| A.new(overrides) }
228
- #
229
- # def initialize(overrides={})
230
- # @a = INITIALIZE_BLOCK.call(overrides[:a] || {})
231
- # end
222
+ # class C
223
+ # include Configurable
224
+ # nest :a, A
225
+ # nest :b, B
232
226
  # end
233
227
  #
234
- # Nest checks for recursive nesting and raises an error if a recursive nest
235
- # is detected.
236
- #
237
- # ==== Attributes
238
- #
239
- # Nesting with an initialization block creates the public accessor for the
240
- # instance, private methods to read and write the instance configurations,
241
- # and a private method to initialize the instance. The default names
242
- # for these methods are listed with the attributes to override them:
243
- #
244
- # :instance_reader key
245
- # :instance_writer "#{key}="
246
- # :instance_initializer "#{key}_initialize"
247
- # :reader "#{key}_config_reader"
248
- # :writer "#{key}_config_writer"
249
- #
250
- # These attributes are ignored if no block is given; true/false/nil
251
- # values are meaningless and will be treated as the default.
252
- #
253
- def nest(key, configurable_class, attributes={}, &block)
228
+ # c = C.new
229
+ # c.b.key = 7
230
+ # c.b.nest.key = "8"
231
+ # c.config[:b][:nest][:nest][:key] = "9"
232
+ #
233
+ # c.config.to_hash
234
+ # # => {
235
+ # # :a => {
236
+ # # :key => 'one',
237
+ # # :nest => {:key => 'two'}
238
+ # # },
239
+ # # :b => {
240
+ # # :key => 7,
241
+ # # :nest => {
242
+ # # :key => 8,
243
+ # # :nest => {:key => 9}
244
+ # # }
245
+ # # }}
246
+ #
247
+ # === Attributes
248
+ #
249
+ # Nest provides a number of attributes that can modify how a nest is
250
+ # constructed. Attribute keys should be specified as symbols.
251
+ #
252
+ # Attribute:: Description
253
+ # const_name:: Determines the constant name of the configurable
254
+ # class within the nesting class. May be nil.
255
+ # (default: key.to_s.capitalize)
256
+ # instance_reader:: The method accessing the nested instance. (default: key)
257
+ # instance_writer:: The method to set the nested instance. (default: "#{key}=")
258
+ # instance_initializer:: The method that initializes the instance.
259
+ # (default: "initialize_#{key}")
260
+ # reader:: The method used to read the instance configuration.
261
+ # (default: "#{key}_config_reader")
262
+ # writer:: The method used to initialize or reconfigure the
263
+ # instance. (default: "#{key}_config_writer")
264
+ #
265
+ # Except for const_name, these attributes are used to define methods
266
+ # required for nesting to work properly. None of the method attributes may
267
+ # be set to nil, but they may be set to non-default values. In that case,
268
+ # nest will register the method names as provided, but it will not define
269
+ # the methods themselves. The user must define methods with the following
270
+ # functionality:
271
+ #
272
+ # Attribute:: Function
273
+ # instance_reader:: Returns the instance of the configurable class
274
+ # instance_writer:: Inputs and sets the instance of the configurable class
275
+ # instance_initializer:: Receives the initial config and return an instance of
276
+ # configurable class
277
+ # reader:: Returns instance.config
278
+ # writer:: Reconfigures instance using the input overrides,
279
+ # or uses instance_initializer and instance_writer to
280
+ # initialize and set the instance.
281
+ #
282
+ # Methods can be public or otherwise. Specifying true uses and defines the
283
+ # default methods. Specifying false uses the default method name, but does
284
+ # not define the method itself.
285
+ #
286
+ # Any additional attributes are registered with the configuration.
287
+ def nest(key, configurable_class=nil, attributes={}, &block)
254
288
  attributes = merge_attributes(block, attributes)
289
+ attributes = {
290
+ :instance_reader => true,
291
+ :instance_writer => true,
292
+ :initializer => true
293
+ }.merge(attributes)
255
294
 
256
- if block_given?
257
- instance_variable = "@#{key}".to_sym
258
- nest_attr(key, configurable_class, attributes) do |input|
259
- instance_variable_set(instance_variable, yield(input))
260
- end
295
+ # define the nested configurable
296
+ if configurable_class
297
+ raise "a block is not allowed when a configurable class is specified" if block_given?
261
298
  else
262
- nest_attr(key, configurable_class, attributes)
299
+ configurable_class = Class.new { include Configurable }
300
+ configurable_class.class_eval(&block) if block_given?
263
301
  end
264
- end
265
-
266
- # Same as nest, except the initialize block executes in instance-context.
267
- #
268
- # class C
269
- # include Configurable
270
- # nest(:a, A) {|overrides| A.new(overrides) }
271
- #
272
- # def initialize(overrides={})
273
- # initialize_config(overrides)
274
- # end
275
- # end
276
- #
277
- # # An equivalent class to illustrate instance-context
278
- # class EquivalentClass
279
- # attr_reader :a, A
280
- #
281
- # def a_initialize(overrides)
282
- # A.new(overrides)
283
- # end
284
- #
285
- # def initialize(overrides={})
286
- # @a = send(:a_initialize, overrides[:a] || {})
287
- # end
288
- # end
289
- #
290
- def nest_attr(key, configurable_class, attributes={}, &block)
291
- unless configurable_class.kind_of?(Configurable::ClassMethods)
292
- raise ArgumentError, "not a Configurable class: #{configurable_class}"
302
+
303
+ # set the new constant
304
+ const_name = if attributes.has_key?(:const_name)
305
+ attributes.delete(:const_name)
306
+ else
307
+ key.to_s.capitalize
293
308
  end
309
+ const_set(const_name, configurable_class) if const_name
294
310
 
295
- attributes = merge_attributes(block, attributes)
311
+ # define instance reader
312
+ instance_reader = define_attribute_method(:instance_reader, attributes, key) do |attribute|
313
+ attr_reader(key)
314
+ public(key)
315
+ end
316
+
317
+ # define instance writer
318
+ instance_writer = define_attribute_method(:instance_writer, attributes, "#{key}=") do |attribute|
319
+ attr_writer(key)
320
+ public(attribute)
321
+ end
296
322
 
297
- # add some tracking attributes
298
- attributes[:receiver] ||= configurable_class
323
+ # define initializer
324
+ initializer = define_attribute_method(:initializer, attributes, "initialize_#{key}") do |attribute|
325
+ define_method(attribute) {|config| configurable_class.new.reconfigure(config) }
326
+ private(attribute)
327
+ end
299
328
 
300
- # remove method attributes
301
- instance_reader = attributes.delete(:instance_reader)
302
- instance_writer = attributes.delete(:instance_writer)
303
- initializer = attributes.delete(:instance_initializer)
304
- reader = attributes.delete(:reader)
305
- writer = attributes.delete(:writer)
329
+ # define the reader
330
+ reader = define_attribute_method(:reader, attributes, "#{key}_config_reader") do |attribute|
331
+ define_method(attribute) { send(instance_reader).config }
332
+ private(attribute)
333
+ end
306
334
 
307
- if block_given?
308
- # define instance accessor methods
309
- instance_reader = boolean_select(instance_reader, key)
310
- instance_writer = boolean_select(instance_writer, "#{key}=")
311
- instance_var = "@#{instance_reader}".to_sym
312
-
313
- initializer = boolean_select(reader, "#{key}_initialize")
314
- reader = boolean_select(reader, "#{key}_config_reader")
315
- writer = boolean_select(writer, "#{key}_config_writer")
316
-
317
- # the public accessor
318
- attr_reader instance_reader
319
-
320
- define_method(instance_writer) do |value|
321
- instance_variable_set(instance_var, value)
322
- end
323
- public(instance_reader, instance_writer)
324
-
325
- # the initializer
326
- define_method(initializer, &block)
327
-
328
- # the reader returns the config for the instance
329
- define_method(reader) do
330
- instance_variable_get(instance_var).config
331
- end
332
-
333
- # the writer initializes the instance if necessary,
334
- # or reconfigures the instance if it already exists
335
- define_method(writer) do |value|
336
- if instance_variable_defined?(instance_var)
337
- instance_variable_get(instance_var).reconfigure(value)
335
+ # define the writer
336
+ writer = define_attribute_method(:writer, attributes, "#{key}_config_writer") do |attribute|
337
+ define_method(attribute) do |value|
338
+ if instance = send(instance_reader)
339
+ instance.reconfigure(value)
338
340
  else
339
- instance_variable_set(instance_var, send(initializer, value))
341
+ send(instance_writer, send(initializer, value))
340
342
  end
341
343
  end
342
- private(reader, writer)
343
- else
344
- reader = writer = nil
344
+ private(attribute)
345
345
  end
346
346
 
347
- value = DelegateHash.new(configurable_class.configurations)
348
- configurations[key] = Delegate.new(reader, writer, value, attributes)
349
-
347
+ # define the configuration
348
+ nested_config = DelegateHash.new(configurable_class.configurations)
349
+ configurations[key] = Delegate.new(reader, writer, nested_config, attributes)
350
+
350
351
  check_infinite_nest(configurable_class.configurations)
351
- end
352
-
352
+ end
353
+
353
354
  # Alias for Validation
354
355
  def c
355
356
  Validation
@@ -357,13 +358,30 @@ module Configurable
357
358
 
358
359
  private
359
360
 
360
- # a helper to select a value or the default, if the default is true,
361
- # false, or nil. used by nest_attr to handle attributes
362
- def boolean_select(value, default) # :nodoc:
363
- case value
364
- when true, false, nil then default
365
- else value
361
+ # a helper to define methods that may be overridden in attributes.
362
+ # yields the default to the block if the default is supposed to
363
+ # be defined. returns the symbolized method name.
364
+ def define_attribute_method(name, attributes, default) # :nodoc:
365
+ attribute = attributes.delete(name)
366
+
367
+ case attribute
368
+ when true
369
+ # true means use the default and define the method
370
+ attribute = default
371
+ yield(attribute)
372
+
373
+ when false
374
+ # false means use the default, but let the user define the method
375
+ attribute = default
376
+
377
+ when nil
378
+ # nil is not allowed
379
+ raise "#{name.inspect} attribute cannot be nil"
366
380
  end
381
+ # ... all other values specify what the method should be,
382
+ # and lets the user define the method.
383
+
384
+ attribute.to_sym
367
385
  end
368
386
 
369
387
  # a helper to initialize configurations for the first time,
@@ -27,8 +27,7 @@ module Configurable
27
27
  # line, in a web form, or a desktop app).
28
28
  attr_reader :attributes
29
29
 
30
- # Initializes a new Delegate with the specified key
31
- # and default value.
30
+ # Initializes a new Delegate with the specified key and default value.
32
31
  def initialize(reader, writer="#{reader}=", default=nil, attributes={})
33
32
  self.default = default
34
33
  self.reader = reader
@@ -61,16 +60,16 @@ module Configurable
61
60
  duplicate && @duplicable ? @default.dup : @default
62
61
  end
63
62
 
64
- # Sets the reader for self. The reader is symbolized,
65
- # but may also be set to nil.
63
+ # Sets the reader for self.
66
64
  def reader=(value)
67
- @reader = value == nil ? value : value.to_sym
65
+ raise ArgumentError, "reader may not be nil" if value == nil
66
+ @reader = value.to_sym
68
67
  end
69
68
 
70
- # Sets the writer for self. The writer is symbolized,
71
- # but may also be set to nil.
69
+ # Sets the writer for self.
72
70
  def writer=(value)
73
- @writer = value == nil ? value : value.to_sym
71
+ raise ArgumentError, "writer may not be nil" if value == nil
72
+ @writer = value.to_sym
74
73
  end
75
74
 
76
75
  # Returns true if the default value is a kind of DelegateHash.
@@ -58,8 +58,8 @@ module Configurable
58
58
  end
59
59
 
60
60
  # Binds self to the specified receiver. Delegate values are removed from
61
- # store and sent to their writer method on receiver. If the store has no
62
- # value for a delegate key, the delegate default value will be used.
61
+ # store and sent to their writer on receiver. If the store has no value
62
+ # for a delegate key, the delegate default value will be used.
63
63
  def bind(receiver)
64
64
  raise ArgumentError, "receiver cannot be nil" if receiver == nil
65
65
 
@@ -89,15 +89,15 @@ module Configurable
89
89
  self
90
90
  end
91
91
 
92
- # Retrieves the value corresponding to the key. When bound, delegates with
93
- # readers pull values from the receiver; otherwise the value in store will
94
- # be returned. When unbound, if the store has no value for a delegate, the
95
- # delgate default value will be returned.
92
+ # Retrieves the value corresponding to the key. When bound, delegates pull
93
+ # values from the receiver using the delegate.reader method; otherwise the
94
+ # value in store will be returned. When unbound, if the store has no value
95
+ # for a delegate, the delgate default value will be returned.
96
96
  def [](key)
97
97
  return store[key] unless delegate = delegates[key]
98
98
 
99
99
  case
100
- when bound? && delegate.reader
100
+ when bound?
101
101
  receiver.send(delegate.reader)
102
102
  when store.has_key?(key)
103
103
  store[key]
@@ -106,17 +106,15 @@ module Configurable
106
106
  end
107
107
  end
108
108
 
109
- # Stores a value for the key. When bound, delegates with writers send the
110
- # value to the receiver; otherwise values are stored in store.
109
+ # Stores a value for the key. When bound, delegates set the value in the
110
+ # receiver using the delegate.writer method; otherwise values are stored in
111
+ # store.
111
112
  def []=(key, value)
112
113
  if bound? && delegate = delegates[key]
113
- if delegate.writer
114
- receiver.send(delegate.writer, value)
115
- return
116
- end
114
+ receiver.send(delegate.writer, value)
115
+ else
116
+ store[key] = value
117
117
  end
118
-
119
- store[key] = value
120
118
  end
121
119
 
122
120
  # Returns the union of delegate and store keys.
@@ -181,8 +179,6 @@ module Configurable
181
179
  # helper to map delegate values from source to the receiver
182
180
  def map(source) # :nodoc:
183
181
  delegates.each_pair do |key, delegate|
184
- next unless writer = delegate.writer
185
-
186
182
  # map the value; if no value is set in the source then use the
187
183
  # delegate default. if map_default is false, then simply skip...
188
184
  # this ensures each config is initialized to a value when bound
@@ -193,15 +189,14 @@ module Configurable
193
189
  else next
194
190
  end
195
191
 
196
- receiver.send(writer, value)
192
+ receiver.send(delegate.writer, value)
197
193
  end
198
194
  end
199
195
 
200
196
  # helper to unmap delegates from the receiver to a target hash
201
197
  def unmap(target) # :nodoc:
202
198
  delegates.each_pair do |key, delegate|
203
- next unless reader = delegate.reader
204
- target[key] = receiver.send(reader)
199
+ target[key] = receiver.send(delegate.reader)
205
200
  end
206
201
  end
207
202
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: configurable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Chiang
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-17 00:00:00 -07:00
12
+ date: 2009-03-05 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency