configurable 0.5.0 → 0.6.0

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