configurable 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -66,7 +66,7 @@ class ConfigParser
66
66
  end
67
67
  block ? block.call(value) : value
68
68
  else
69
- raise "value specified for flag" if value
69
+ raise "value specified for flag: #{switch}" if value
70
70
  block ? block.call : nil
71
71
  end
72
72
  end
@@ -75,7 +75,7 @@ class ConfigParser
75
75
  def to_s
76
76
  lines = Lazydoc::Utils.wrap(desc.to_s, 43)
77
77
 
78
- header = " #{short_str} #{long_str} #{arg_name}"
78
+ header = " #{short_str}#{long_str} #{arg_name}"
79
79
  header = header.length > 36 ? header.ljust(80) : (LINE_FORMAT % [header, lines.shift])
80
80
 
81
81
  if lines.empty?
@@ -90,7 +90,14 @@ class ConfigParser
90
90
 
91
91
  # helper returning short formatted for to_s
92
92
  def short_str # :nodoc:
93
- short ? short + ',' : ' '
93
+ case
94
+ when short && long
95
+ "#{short}, "
96
+ when short
97
+ "#{short}"
98
+ else
99
+ ' '
100
+ end
94
101
  end
95
102
 
96
103
  # helper returning long formatted for to_s
@@ -29,7 +29,7 @@ class ConfigParser
29
29
  # or calls the block with true in all other cases. Raises an
30
30
  # error if a value is specified.
31
31
  def parse(switch, value, argv)
32
- raise "value specified for switch" if value
32
+ raise "value specified for switch: #{switch}" if value
33
33
  value = (switch == negative_long ? false : true)
34
34
  block ? block.call(value) : value
35
35
  end
@@ -12,25 +12,25 @@ class ConfigParser
12
12
  # the match:
13
13
  #
14
14
  # $1:: the switch
15
- # $3:: the value
15
+ # $2:: the value
16
16
  #
17
- LONG_OPTION = /^(--[A-z].*?)(=(.*))?$/
17
+ LONG_OPTION = /^(--[A-z].*?)(?:=(.*))?$/
18
18
 
19
19
  # Matches a nested short option, with or without a value
20
20
  # (ex: '-o', '-n:o', '-o=value'). After the match:
21
21
  #
22
22
  # $1:: the switch
23
- # $4:: the value
23
+ # $2:: the value
24
24
  #
25
- SHORT_OPTION = /^(-[A-z](:[A-z])*)(=(.*))?$/
25
+ SHORT_OPTION = /^(-[A-z](?::[A-z])*)(?:=(.*))?$/
26
26
 
27
27
  # Matches the alternate syntax for short options
28
28
  # (ex: '-n:ovalue', '-ovalue'). After the match:
29
29
  #
30
30
  # $1:: the switch
31
- # $3:: the value
31
+ # $2:: the value
32
32
  #
33
- ALT_SHORT_OPTION = /^(-[A-z](:[A-z])*)(.+)$/
33
+ ALT_SHORT_OPTION = /^(-[A-z](?::[A-z])*)(.+)$/
34
34
 
35
35
  # Turns the input string into a short-format option. Raises
36
36
  # an error if the option does not match SHORT_OPTION. Nils
@@ -44,7 +44,7 @@ class ConfigParser
44
44
 
45
45
  str = str.to_s
46
46
  str = "-#{str}" unless str[0] == ?-
47
- unless str =~ SHORT_OPTION && $3 == nil
47
+ unless str =~ SHORT_OPTION && $2 == nil
48
48
  raise ArgumentError, "invalid short option: #{str}"
49
49
  end
50
50
  str
@@ -64,7 +64,7 @@ class ConfigParser
64
64
  str = str.to_s
65
65
  str = "--#{str}" unless str =~ /^--/
66
66
  str.gsub!(/_/, '-')
67
- unless str =~ LONG_OPTION && $3 == nil
67
+ unless str =~ LONG_OPTION && $2 == nil
68
68
  raise ArgumentError, "invalid long option: #{str}"
69
69
  end
70
70
  str
@@ -82,15 +82,48 @@ class ConfigParser
82
82
  "--#{switch.join(':')}"
83
83
  end
84
84
 
85
+ # Infers the default long using key and adds it to attributes. Returns
86
+ # attributes.
87
+ #
88
+ # infer_long(:key, {}) # => {:long => '--key'}
89
+ #
90
+ def infer_long(key, attributes)
91
+ unless attributes.has_key?(:long)
92
+ attributes[:long] = "--#{key}"
93
+ end
94
+
95
+ attributes
96
+ end
97
+
98
+ # Infers the default argname from attributes[:long] and sets it in
99
+ # attributes. Returns attributes.
100
+ #
101
+ # infer_arg_name(:key, {:long => '--opt'}) # => {:long => '--opt', :arg_name => 'OPT'}
102
+ # infer_arg_name(:key, {}) # => {:arg_name => 'KEY'}
103
+ #
104
+ def infer_arg_name(key, attributes)
105
+ if attributes.has_key?(:arg_name)
106
+ return attributes
107
+ end
108
+
109
+ if long = attributes[:long]
110
+ long.to_s =~ /^(?:--)?(.*)$/
111
+ attributes[:arg_name] = $1.upcase
112
+ else
113
+ attributes[:arg_name] = key.to_s.upcase
114
+ end
115
+
116
+ attributes
117
+ end
118
+
85
119
  # Attributes:
86
120
  #
87
121
  # :long the long key ("--key")
88
122
  # :arg_name the argument name ("KEY")
89
123
  #
90
124
  def setup_option(key, attributes={})
91
- attributes[:long] ||= "--#{key}"
92
- attributes[:long].to_s =~ /^(--)?(.*)$/
93
- attributes[:arg_name] ||= $2.upcase
125
+ infer_long(key, attributes)
126
+ infer_arg_name(key, attributes)
94
127
 
95
128
  lambda {|value| config[key] = value }
96
129
  end
@@ -100,7 +133,7 @@ class ConfigParser
100
133
  # :long the long key ("--key")
101
134
  #
102
135
  def setup_flag(key, default=true, attributes={})
103
- attributes[:long] ||= "--#{key}"
136
+ infer_long(key, attributes)
104
137
 
105
138
  lambda {config[key] = !default }
106
139
  end
@@ -110,9 +143,11 @@ class ConfigParser
110
143
  # :long the long key ("--[no-]key")
111
144
  #
112
145
  def setup_switch(key, default=true, attributes={})
113
- attributes[:long] ||= "--#{key}"
114
- attributes[:long].to_s =~ /^(--)?(\[no-\])?(.*)$/
115
- attributes[:long] = "--[no-]#{$3}" unless $2
146
+ infer_long(key, attributes)
147
+
148
+ if attributes[:long].to_s =~ /^(?:--)?(\[no-\])?(.*)$/
149
+ attributes[:long] = "--[no-]#{$2}" unless $1
150
+ end
116
151
 
117
152
  lambda {|value| config[key] = value }
118
153
  end
@@ -124,9 +159,8 @@ class ConfigParser
124
159
  # :split the split character
125
160
  #
126
161
  def setup_list(key, attributes={})
127
- attributes[:long] ||= "--#{key}"
128
- attributes[:long].to_s =~ /^(--)?(.*)$/
129
- attributes[:arg_name] ||= $2.upcase
162
+ infer_long(key, attributes)
163
+ infer_arg_name(key, attributes)
130
164
 
131
165
  split = attributes[:split]
132
166
  n = attributes[:n]
@@ -1,3 +1,4 @@
1
+ require 'configurable/version'
1
2
  require 'configurable/module_methods'
2
3
 
3
4
  # Configurable enables the specification of configurations within a class
@@ -5,57 +6,56 @@ require 'configurable/module_methods'
5
6
  #
6
7
  # class ConfigClass
7
8
  # include Configurable
8
- #
9
9
  # config :one, 'one'
10
10
  # config :two, 'two'
11
11
  # config :three, 'three'
12
- #
13
12
  # end
14
13
  #
15
14
  # c = ConfigClass.new
16
- # c.config.class # => Configurable::DelegateHash
17
- # c.config # => {:one => 'one', :two => 'two', :three => 'three'}
15
+ # c.config.class # => Configurable::ConfigHash
16
+ # c.config # => {:one => 'one', :two => 'two', :three => 'three'}
18
17
  #
19
18
  # Instances have a <tt>config</tt> object that acts like a forwarding hash;
20
19
  # configuration keys delegate to accessors while undeclared key-value pairs
21
20
  # are stored internally:
22
21
  #
23
22
  # c.config[:one] = 'ONE'
24
- # c.one # => 'ONE'
23
+ # c.one # => 'ONE'
25
24
  #
26
25
  # c.one = 1
27
- # c.config # => {:one => 1, :two => 'two', :three => 'three'}
26
+ # c.config # => {:one => 1, :two => 'two', :three => 'three'}
28
27
  #
29
28
  # c.config[:undeclared] = 'value'
30
- # c.config.store # => {:undeclared => 'value'}
29
+ # c.config.store # => {:undeclared => 'value'}
31
30
  #
32
31
  # The writer for a configuration can be defined by providing a block to config.
33
32
  # The Validation module provides a number of common validation/transform
34
33
  # blocks accessible through the class method 'c':
35
34
  #
36
- # class SubClass < ConfigClass
35
+ # class ValidationClass
36
+ # include Configurable
37
37
  # config(:one, 'one') {|v| v.upcase }
38
38
  # config :two, 2, &c.integer
39
39
  # end
40
40
  #
41
- # s = SubClass.new
42
- # s.config # => {:one => 'ONE', :two => 2, :three => 'three'}
41
+ # c = ValidationClass.new
42
+ # c.config # => {:one => 'ONE', :two => 2}
43
43
  #
44
- # s.one = 'aNothER'
45
- # s.one # => 'ANOTHER'
44
+ # c.one = 'aNothER'
45
+ # c.one # => 'ANOTHER'
46
46
  #
47
- # s.two = -2
48
- # s.two # => -2
49
- # s.two = "3"
50
- # s.two # => 3
51
- # s.two = nil # !> ValidationError
52
- # s.two = 'str' # !> ValidationError
47
+ # c.two = -2
48
+ # c.two # => -2
49
+ # c.two = "3"
50
+ # c.two # => 3
51
+ # c.two = nil # !> ValidationError
52
+ # c.two = 'str' # !> ValidationError
53
53
  #
54
54
  # Note that config blocks are defined in class-context and will have access
55
55
  # to variables outside the block (as you would expect). For instance, these
56
56
  # are analagous declarations:
57
57
  #
58
- # class ClassConfig
58
+ # class ExampleClass
59
59
  # config :key, 'value' do |input|
60
60
  # input.upcase
61
61
  # end
@@ -73,13 +73,51 @@ require 'configurable/module_methods'
73
73
  # Blocks provided to config_attr will have instance context and must set
74
74
  # the instance variable themselves.
75
75
  #
76
- # class ConfigClass
76
+ # class LiteralClass
77
77
  # config_attr :key, 'value' do |input|
78
78
  # @key = input.upcase
79
79
  # end
80
80
  # end
81
81
  #
82
- # Configurations are inherited and may be overridden in subclasses.
82
+ # Configurations are inherited and may be overridden in subclasses. They may
83
+ # also be included from a module:
84
+ #
85
+ # module A
86
+ # include Configurable
87
+ # config :a, 'a'
88
+ # config :b, 'b'
89
+ # end
90
+ #
91
+ # class B
92
+ # include A
93
+ # end
94
+ #
95
+ # class C < B
96
+ # config :b, 'B'
97
+ # config :c, 'C'
98
+ # end
99
+ #
100
+ # B.new.config.to_hash # => {:a => 'a', :b => 'b'}
101
+ # C.new.config.to_hash # => {:a => 'a', :b => 'B', :c => 'C'}
102
+ #
103
+ # Lastly, configurable classes may be nested through the nest method. Nesting
104
+ # creates a configurable class with the configs defined in the nest block;
105
+ # nested configs may be accessed by chaining method calls, or through nested
106
+ # calls to config.
107
+ #
108
+ # class NestingClass
109
+ # include Configurable
110
+ # config :one, 'one'
111
+ # nest :two do
112
+ # config :three, 'three'
113
+ # end
114
+ # end
115
+ #
116
+ # c = NestingClass.new
117
+ # c.config.to_hash # => {:one => 'one', :two => {:three => 'three'}}
118
+ #
119
+ # c.two.three = 'THREE'
120
+ # c.config[:two][:three] # => 'THREE'
83
121
  #
84
122
  # === Attributes
85
123
  #
@@ -127,19 +165,35 @@ require 'configurable/module_methods'
127
165
  # needs. A few attributes are used internally by Configurable.
128
166
  #
129
167
  # Attribute:: Use::
130
- # set_default:: When set to false, the delegate will not map a default value
131
- # during bind. Specify when you manually initialize a config
132
- # variable.
168
+ # init:: When set to false, the config will not initialize itself.
169
+ # Specify when you manually initialize a config.
133
170
  # type:: Specifies the type of option ConfigParser generates for this
134
- # Delegate (ex: :switch, :flag, :list, :hidden)
171
+ # Config (ex: :switch, :flag, :list, :hidden)
135
172
  # desc:: The description string used in the ConfigParser help
136
173
  # long:: The long option (default: key)
137
174
  # short:: The short option.
138
175
  #
176
+ # Validation blocks have default attributes already assigned to them (ex type).
177
+ # In cases where a user-defined block gets used multiple times, it may be useful
178
+ # to register default attributes for that block. To do so, use this pattern:
179
+ #
180
+ # class AttributesClass
181
+ # include Configurable
182
+ # block = c.register(:type => :upcase) {|v| v.upcase }
183
+ #
184
+ # config :a, 'A', &block
185
+ # config :b, 'B', &block
186
+ # end
187
+ #
188
+ # AttributesClass.configurations[:a][:type] # => :upcase
189
+ # AttributesClass.configurations[:b][:type] # => :upcase
190
+ #
139
191
  module Configurable
140
192
  autoload(:Utils, 'configurable/utils')
141
193
 
142
- # A DelegateHash bound to self
194
+ # A ConfigHash bound to self. Accessing configurations through config
195
+ # is much slower (although sometimes more convenient) than through the
196
+ # config accessors.
143
197
  attr_reader :config
144
198
 
145
199
  # Initializes config, if necessary, and then calls super. If initialize
@@ -150,8 +204,8 @@ module Configurable
150
204
  super
151
205
  end
152
206
 
153
- # Reconfigures self with the given overrides. Only the
154
- # specified configs are modified.
207
+ # Reconfigures self with the given overrides. Only the specified configs
208
+ # are modified.
155
209
  #
156
210
  # Returns self.
157
211
  def reconfigure(overrides={})
@@ -159,12 +213,11 @@ module Configurable
159
213
  self
160
214
  end
161
215
 
162
- # Reinitializes configurations in the copy such that
163
- # the new object has it's own set of configurations,
164
- # separate from the original object.
216
+ # Reinitializes configurations in the copy such that the new object has
217
+ # it's own set of configurations, separate from the original object.
165
218
  def initialize_copy(orig)
166
219
  super
167
- initialize_config(orig.config.dup)
220
+ @config = ConfigHash.new(self, orig.config.store.dup, false)
168
221
  end
169
222
 
170
223
  protected
@@ -207,10 +260,49 @@ module Configurable
207
260
  end
208
261
  end
209
262
 
210
- # Initializes config. Default config values
211
- # are overridden as specified by overrides.
263
+ # Initializes config. Default config values are overridden as specified.
212
264
  def initialize_config(overrides={})
213
- @config = overrides.kind_of?(DelegateHash) ? overrides : DelegateHash.new(self.class.configurations, overrides)
214
- @config.bind(self)
265
+ @config = ConfigHash.new(self, overrides, false)
266
+
267
+ # cache as configs (equivalent to self.class.configurations)
268
+ # as an optimization
269
+ configs = @config.configs
270
+
271
+ # hash overrides by delegate so they may be set
272
+ # in the correct order below
273
+ initial_values = {}
274
+ overrides.each_key do |key|
275
+ if config = configs[key]
276
+
277
+ # check that the config may be initialized
278
+ unless config.init?
279
+ key = configs.keys.find {|k| configs[k] == config }
280
+ raise "initialization values are not allowed for: #{key.inspect}"
281
+ end
282
+
283
+ # check that multiple overrides are not specified for a
284
+ # single config, as may happen with indifferent access
285
+ # (ex 'key' and :key)
286
+ if initial_values.has_key?(config)
287
+ key = configs.keys.find {|k| configs[k] == config }
288
+ raise "multiple values map to config: #{key.inspect}"
289
+ end
290
+
291
+ # since overrides are used as the ConfigHash store,
292
+ # the overriding values must be removed, not read
293
+ initial_values[config] = overrides.delete(key)
294
+ end
295
+ end
296
+
297
+ # now initialize configs in order
298
+ configs.each_pair do |key, config|
299
+ next unless config.init?
300
+
301
+ if initial_values.has_key?(config)
302
+ config.set(self, initial_values[config])
303
+ else
304
+ config.init(self)
305
+ end
306
+ end
215
307
  end
216
308
  end
@@ -1,5 +1,4 @@
1
- require 'lazydoc'
2
- require 'configurable/delegate_hash'
1
+ require 'configurable/config_hash'
3
2
  require 'configurable/indifferent_access'
4
3
  require 'configurable/validation'
5
4
 
@@ -7,33 +6,33 @@ autoload(:ConfigParser, 'config_parser')
7
6
 
8
7
  module Configurable
9
8
 
10
- # ClassMethods extends classes that include Configurable and
11
- # provides methods for declaring configurations.
9
+ # ClassMethods extends classes that include Configurable and provides methods
10
+ # for declaring configurations.
12
11
  module ClassMethods
13
- include Lazydoc::Attributes
14
-
15
- # A hash of (key, Delegate) pairs defining the class configurations.
16
- attr_reader :configurations
17
-
18
- def inherited(child) # :nodoc:
19
- unless child.instance_variable_defined?(:@source_file)
20
- caller[0] =~ Lazydoc::CALLER_REGEXP
21
- child.instance_variable_set(:@source_file, File.expand_path($1))
12
+ CONFIGURATIONS_CLASS = Hash
13
+
14
+ # A hash of (key, Config) pairs tracking configs defined on self. See
15
+ # configurations for all configs declared across all ancestors.
16
+ attr_reader :config_registry
17
+
18
+ def self.initialize(base) # :nodoc:
19
+ unless base.instance_variable_defined?(:@config_registry)
20
+ base.instance_variable_set(:@config_registry, CONFIGURATIONS_CLASS.new)
22
21
  end
23
-
24
- # deep duplicate configurations
25
- unless child.instance_variable_defined?(:@configurations)
26
- duplicate = child.instance_variable_set(:@configurations, configurations.dup)
27
- duplicate.each_pair {|key, config| duplicate[key] = config.dup }
28
- duplicate.extend(IndifferentAccess) if configurations.kind_of?(IndifferentAccess)
22
+
23
+ unless base.instance_variable_defined?(:@use_indifferent_access)
24
+ base.instance_variable_set(:@use_indifferent_access, true)
25
+ end
26
+
27
+ unless base.instance_variable_defined?(:@configurations)
28
+ base.instance_variable_set(:@configurations, nil)
29
29
  end
30
- super
31
30
  end
32
31
 
33
32
  # Parses configurations from argv in a non-destructive manner by generating
34
33
  # a ConfigParser using the configurations for self. Returns an array like
35
34
  # [args, config] where the args are the arguments that remain after parsing,
36
- # and config is a hash of the parsed configs. The parser will is yielded to
35
+ # and config is a hash of the parsed configs. The parser is yielded to
37
36
  # the block, if given, to register additonal options.
38
37
  #
39
38
  # See ConfigParser#parse for more information.
@@ -50,16 +49,55 @@ module Configurable
50
49
  [args, parser.config]
51
50
  end
52
51
 
53
- protected
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
63
+
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
74
+ end
75
+ end
76
+ end
77
+
78
+ configurations
79
+ end
80
+
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
86
+ end
54
87
 
88
+ protected
89
+
55
90
  # Sets configurations to symbolize keys for AGET ([]) and ASET([]=)
56
91
  # operations, or not. By default, configurations will use
57
92
  # indifferent access.
58
93
  def use_indifferent_access(input=true)
59
- if input
94
+ @use_indifferent_access = input
95
+ return unless @configurations
96
+
97
+ if @use_indifferent_access
60
98
  @configurations.extend(IndifferentAccess)
61
99
  else
62
- @configurations = configurations.dup
100
+ @configurations = @configurations.dup
63
101
  end
64
102
  end
65
103
 
@@ -129,17 +167,20 @@ module Configurable
129
167
  # Several attributes may be specified to modify how a config is constructed.
130
168
  # Attribute keys should be specified as symbols.
131
169
  #
132
- # Attribute:: Description
170
+ # Attribute:: Description
171
+ # init:: When set to false the config will not initialize
172
+ # during initialize_config. (default: true)
133
173
  # reader:: The method used to read the configuration.
134
174
  # (default: key)
135
175
  # writer:: The method used to write the configuration
136
176
  # (default: "#{key}=")
137
177
  #
138
- # Neither attribute may be set to nil, but they may be set to non-default
139
- # values. In that case, config_attr will register the method names as
140
- # provided, but it will not define the methods themselves. Specifying true
141
- # uses and defines the default methods. Specifying false uses the default
142
- # method name, but does not define the method itself.
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.
143
184
  #
144
185
  # Any additional attributes are registered with the configuration.
145
186
  def config_attr(key, value=nil, attributes={}, &block)
@@ -161,7 +202,10 @@ module Configurable
161
202
  public(attribute)
162
203
  end
163
204
 
164
- configurations[key] = Delegate.new(reader, writer, value, attributes)
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)
165
209
  end
166
210
 
167
211
  # Adds nested configurations to self. Nest creates a new configurable
@@ -237,51 +281,28 @@ module Configurable
237
281
  #
238
282
  # === Attributes
239
283
  #
240
- # Nest provides a number of attributes that can modify how a nest is
241
- # constructed. Attribute keys should be specified as symbols.
284
+ # Nest uses the same attributes as config_attr, with a couple additions:
242
285
  #
243
286
  # Attribute:: Description
244
287
  # const_name:: Determines the constant name of the configurable
245
288
  # class within the nesting class. May be nil.
246
289
  # (default: key.to_s.capitalize)
247
- # instance_reader:: The method accessing the nested instance. (default: key)
248
- # instance_writer:: The method to set the nested instance. (default: "#{key}=")
249
- # reader:: The method used to read the instance config.
250
- # (default: "#{key}_config_reader")
251
- # writer:: The method used to reconfigure the instance.
252
- # (default: "#{key}_config_writer")
253
- #
254
- # Except for const_name, these attributes are used to define methods
255
- # required for nesting to work properly. None of the method attributes may
256
- # be set to nil, but they may be set to non-default values. In that case,
257
- # nest will register the method names as provided, but it will not define
258
- # the methods themselves. The user must define methods with the following
259
- # functionality:
260
- #
261
- # Attribute:: Function
262
- # instance_reader:: Returns the instance of the configurable class
263
- # (initializing if necessary, by default nest initializes
264
- # using configurable_class.new)
265
- # instance_writer:: Inputs and sets the instance of the configurable class
266
- # reader:: Returns instance.config
267
- # writer:: Reconfigures instance using the input overrides, or
268
- # sets instance if provided.
269
- #
270
- # Methods can be public or otherwise. Specifying true uses and defines the
271
- # default methods. Specifying false uses the default method name, but does
272
- # not define the method itself.
290
+ # type:: By default set to :nest.
273
291
  #
274
- # Any additional attributes are registered with the configuration.
275
292
  def nest(key, configurable_class=nil, attributes={}, &block)
276
293
  attributes = merge_attributes(block, attributes)
277
294
  attributes = {
278
- :instance_reader => true,
279
- :instance_writer => true,
295
+ :reader => true,
296
+ :writer => true,
297
+ :type => :nest
280
298
  }.merge(attributes)
281
299
 
282
300
  # define the nested configurable
283
301
  if configurable_class
284
- raise "a block is not allowed when a configurable class is specified" if block_given?
302
+ if block_given?
303
+ configurable_class = Class.new(configurable_class)
304
+ configurable_class.class_eval(&block)
305
+ end
285
306
  else
286
307
  configurable_class = Class.new { include Configurable }
287
308
  configurable_class.class_eval(&block) if block_given?
@@ -301,55 +322,90 @@ module Configurable
301
322
  const_set(const_name, configurable_class)
302
323
  end
303
324
  end
325
+ const_name = nil
304
326
 
305
- # define instance reader
306
- instance_reader = define_attribute_method(:instance_reader, attributes, key) do |attribute|
307
- instance_variable = "@#{key}".to_sym
308
-
309
- # gets or initializes the instance
310
- define_method(attribute) do
311
- if instance_variable_defined?(instance_variable)
312
- instance_variable_get(instance_variable)
313
- else
314
- instance_variable_set(instance_variable, configurable_class.new)
315
- end
316
- end
317
-
318
- public(key)
327
+ # define the reader.
328
+ reader = define_attribute_method(:reader, attributes, key) do |attribute|
329
+ attr_reader attribute
330
+ public(attribute)
319
331
  end
320
332
 
321
- # define instance writer
322
- instance_writer = define_attribute_method(:instance_writer, attributes, "#{key}=") do |attribute|
323
- attr_writer(key)
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)
340
+ end
324
341
  public(attribute)
325
342
  end
326
343
 
327
- # define the reader
328
- reader = define_attribute_method(:reader, attributes, "#{key}_config_reader") do |attribute|
329
- define_method(attribute) do
330
- send(instance_reader).config
331
- end
332
- private(attribute)
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
349
+
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.
356
+ #
357
+ def remove_config(key, options={})
358
+ unless config_registry.has_key?(key)
359
+ raise NameError.new("#{key.inspect} is not a config on #{self}")
333
360
  end
334
361
 
335
- # define the writer
336
- writer = define_attribute_method(:writer, attributes, "#{key}_config_writer") do |attribute|
337
- define_method(attribute) do |value|
338
- if value.kind_of?(configurable_class)
339
- send(instance_writer, value)
340
- else
341
- send(instance_reader).reconfigure(value)
342
- end
343
- end
344
- private(attribute)
362
+ options = {
363
+ :reader => true,
364
+ :writer => true
365
+ }.merge(options)
366
+
367
+ config = config_registry.delete(key)
368
+ cache_configurations(@configurations != nil)
369
+
370
+ undef_method(config.reader) if options[:reader]
371
+ undef_method(config.writer) if options[:writer]
372
+ end
373
+
374
+ # Undefines a configuration much like undef_method undefines a method. The
375
+ # reader and writer for the config are likewise undefined. Nested configs
376
+ # can be undefined using this method.
377
+ #
378
+ # Setting :reader or :writer to false in the options prevents those methods
379
+ # from being undefined.
380
+ #
381
+ # ==== Implementation Note
382
+ #
383
+ # Configurations are undefined by setting the key to nil in the registry.
384
+ # Deleting the config is not sufficient because the registry needs to
385
+ # convey to self and subclasses to not inherit the config from ancestors.
386
+ #
387
+ # This is unlike remove_config where the config is simply deleted from
388
+ # the config_registry.
389
+ #
390
+ def undef_config(key, options={})
391
+ # temporarily cache as an optimization
392
+ configs = configurations
393
+ unless configs.has_key?(key)
394
+ raise NameError.new("#{key.inspect} is not a config on #{self}")
345
395
  end
346
396
 
347
- # define the configuration
348
- nested_config = DelegateHash.new(configurable_class.configurations)
349
- configurations[key] = Delegate.new(reader, writer, nested_config, attributes)
397
+ options = {
398
+ :reader => true,
399
+ :writer => true
400
+ }.merge(options)
350
401
 
351
- check_infinite_nest(configurable_class.configurations)
352
- end
402
+ config = configs[key]
403
+ config_registry[key] = nil
404
+ cache_configurations(@configurations != nil)
405
+
406
+ undef_method(config.reader) if options[:reader]
407
+ undef_method(config.writer) if options[:writer]
408
+ end
353
409
 
354
410
  # Alias for Validation
355
411
  def c
@@ -358,6 +414,11 @@ module Configurable
358
414
 
359
415
  private
360
416
 
417
+ def inherited(base) # :nodoc:
418
+ ClassMethods.initialize(base)
419
+ super
420
+ end
421
+
361
422
  # a helper to define methods that may be overridden in attributes.
362
423
  # yields the default to the block if the default is supposed to
363
424
  # be defined. returns the symbolized method name.
@@ -384,12 +445,6 @@ module Configurable
384
445
  attribute.to_sym
385
446
  end
386
447
 
387
- # a helper to initialize configurations for the first time,
388
- # mainly implemented as a hook for OrderedHashPatch
389
- def initialize_configurations # :nodoc:
390
- @configurations ||= {}
391
- end
392
-
393
448
  # a helper method to merge the default attributes for the block with
394
449
  # the input attributes. also registers a Trailer description.
395
450
  def merge_attributes(block, attributes) # :nodoc:
@@ -404,95 +459,19 @@ module Configurable
404
459
  end
405
460
 
406
461
  # helper to recursively check for an infinite nest
407
- def check_infinite_nest(delegates) # :nodoc:
408
- raise "infinite nest detected" if delegates == self.configurations
462
+ def check_infinite_nest(klass) # :nodoc:
463
+ raise "infinite nest detected" if klass == self
409
464
 
410
- delegates.each_pair do |key, delegate|
411
- if delegate.is_nest?
412
- check_infinite_nest(delegate.default(false).delegates)
465
+ klass.configurations.each_value do |delegate|
466
+ if delegate.kind_of?(NestConfig)
467
+ check_infinite_nest(delegate.nest_class)
413
468
  end
414
469
  end
415
470
  end
416
471
  end
417
472
  end
418
473
 
419
- module Configurable
420
-
421
- # Beginning with ruby 1.9, Hash tracks the order of insertion and methods
422
- # like each_pair return pairs in order. Configurable leverages this feature
423
- # to keep configurations in order for the command line documentation produced
424
- # by ConfigParser.
425
- #
426
- # Pre-1.9 ruby implementations require a patched Hash that tracks insertion
427
- # order. This very thin subclass of hash does that for ASET insertions and
428
- # each_pair. OrderedHashPatches are used as the configurations object in
429
- # Configurable classes for pre-1.9 ruby implementations and for nothing else.
430
- class OrderedHashPatch < Hash
431
- def initialize
432
- super
433
- @insertion_order = []
434
- end
435
-
436
- # ASET insertion, tracking insertion order.
437
- def []=(key, value)
438
- @insertion_order << key unless @insertion_order.include?(key)
439
- super
440
- end
441
-
442
- # Keys, sorted into insertion order
443
- def keys
444
- super.sort_by do |key|
445
- @insertion_order.index(key) || length
446
- end
447
- end
448
-
449
- # Yields each key-value pair to the block in insertion order.
450
- def each_pair
451
- keys.each do |key|
452
- yield(key, fetch(key))
453
- end
454
- end
455
-
456
- # Ensures the insertion order of duplicates is separate from parents.
457
- def initialize_copy(orig)
458
- super
459
- @insertion_order = orig.instance_variable_get(:@insertion_order).dup
460
- end
461
-
462
- # Overridden to load an array of [key, value] pairs in order (see to_yaml).
463
- # The default behavior for loading from a hash of key-value pairs is
464
- # preserved, but the insertion order will not be preserved.
465
- def yaml_initialize( tag, val )
466
- @insertion_order ||= []
467
-
468
- if Array === val
469
- val.each do |k, v|
470
- self[k] = v
471
- end
472
- else
473
- super
474
- end
475
- end
476
-
477
- # Overridden to preserve insertion order by serializing self as an array
478
- # of [key, value] pairs.
479
- def to_yaml( opts = {} )
480
- YAML::quick_emit( object_id, opts ) do |out|
481
- out.seq( taguri, to_yaml_style ) do |seq|
482
- each_pair do |key, value|
483
- seq.add( [key, value] )
484
- end
485
- end
486
- end
487
- end
488
- end
489
-
490
- module ClassMethods
491
- undef_method :initialize_configurations
492
-
493
- # applies OrderedHashPatch
494
- def initialize_configurations # :nodoc:
495
- @configurations ||= OrderedHashPatch.new
496
- end
497
- end
498
- end if RUBY_VERSION < '1.9'
474
+ # Apply the ordered hash patch if necessary
475
+ if RUBY_VERSION < '1.9'
476
+ require 'configurable/ordered_hash_patch'
477
+ end