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.
- 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
|