configurable 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +19 -0
- data/MIT-LICENSE +17 -15
- data/README +112 -40
- data/lib/config_parser.rb +159 -96
- data/lib/config_parser/option.rb +10 -3
- data/lib/config_parser/switch.rb +1 -1
- data/lib/config_parser/utils.rb +52 -18
- data/lib/configurable.rb +128 -36
- data/lib/configurable/class_methods.rb +171 -192
- data/lib/configurable/config.rb +97 -0
- data/lib/configurable/config_hash.rb +198 -0
- data/lib/configurable/indifferent_access.rb +1 -1
- data/lib/configurable/module_methods.rb +7 -17
- data/lib/configurable/nest_config.rb +78 -0
- data/lib/configurable/ordered_hash_patch.rb +85 -0
- data/lib/configurable/utils.rb +25 -32
- data/lib/configurable/validation.rb +69 -31
- data/lib/configurable/version.rb +7 -0
- metadata +13 -8
- data/lib/configurable/delegate.rb +0 -103
- data/lib/configurable/delegate_hash.rb +0 -226
data/lib/config_parser/option.rb
CHANGED
@@ -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}
|
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
|
-
|
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
|
data/lib/config_parser/switch.rb
CHANGED
@@ -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
|
data/lib/config_parser/utils.rb
CHANGED
@@ -12,25 +12,25 @@ class ConfigParser
|
|
12
12
|
# the match:
|
13
13
|
#
|
14
14
|
# $1:: the switch
|
15
|
-
# $
|
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
|
-
# $
|
23
|
+
# $2:: the value
|
24
24
|
#
|
25
|
-
SHORT_OPTION = /^(-[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
|
-
# $
|
31
|
+
# $2:: the value
|
32
32
|
#
|
33
|
-
ALT_SHORT_OPTION = /^(-[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 && $
|
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 && $
|
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
|
92
|
-
attributes
|
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
|
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
|
114
|
-
|
115
|
-
attributes[:long]
|
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
|
128
|
-
attributes
|
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]
|
data/lib/configurable.rb
CHANGED
@@ -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
|
17
|
-
# c.config
|
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
|
23
|
+
# c.one # => 'ONE'
|
25
24
|
#
|
26
25
|
# c.one = 1
|
27
|
-
# c.config
|
26
|
+
# c.config # => {:one => 1, :two => 'two', :three => 'three'}
|
28
27
|
#
|
29
28
|
# c.config[:undeclared] = 'value'
|
30
|
-
# c.config.store
|
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
|
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
|
-
#
|
42
|
-
#
|
41
|
+
# c = ValidationClass.new
|
42
|
+
# c.config # => {:one => 'ONE', :two => 2}
|
43
43
|
#
|
44
|
-
#
|
45
|
-
#
|
44
|
+
# c.one = 'aNothER'
|
45
|
+
# c.one # => 'ANOTHER'
|
46
46
|
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
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
|
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
|
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
|
-
#
|
131
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
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 =
|
214
|
-
|
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 '
|
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
|
-
#
|
9
|
+
# ClassMethods extends classes that include Configurable and provides methods
|
10
|
+
# for declaring configurations.
|
12
11
|
module ClassMethods
|
13
|
-
|
14
|
-
|
15
|
-
# A hash of (key,
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
139
|
-
# values. In that case, config_attr will register the method
|
140
|
-
# provided, but it will not define the methods themselves.
|
141
|
-
#
|
142
|
-
# method name, but does not define the method
|
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
|
-
|
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
|
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
|
-
#
|
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
|
-
:
|
279
|
-
:
|
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
|
-
|
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
|
306
|
-
|
307
|
-
|
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
|
322
|
-
|
323
|
-
|
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
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
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
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
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
|
-
|
348
|
-
|
349
|
-
|
397
|
+
options = {
|
398
|
+
:reader => true,
|
399
|
+
:writer => true
|
400
|
+
}.merge(options)
|
350
401
|
|
351
|
-
|
352
|
-
|
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(
|
408
|
-
raise "infinite nest detected" if
|
462
|
+
def check_infinite_nest(klass) # :nodoc:
|
463
|
+
raise "infinite nest detected" if klass == self
|
409
464
|
|
410
|
-
|
411
|
-
if delegate.
|
412
|
-
check_infinite_nest(delegate.
|
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
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
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
|