jlawler-activeconfig 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,389 @@
1
+ require 'socket'
2
+ require 'yaml'
3
+ require 'active_config/hash_weave' # Hash#weave
4
+ # REMOVE DEPENDENCY ON active_support.
5
+ require 'rubygems'
6
+ require 'active_support'
7
+ require 'active_support/core_ext'
8
+ require 'active_support/core_ext/hash/indifferent_access'
9
+ require 'active_config/hash_config'
10
+ require 'active_config/suffixes'
11
+ require 'erb'
12
+
13
+
14
+ ##
15
+ # See LICENSE.txt for details
16
+ #
17
+ #=ActiveConfig
18
+ #
19
+ # * Provides dottable, hash, array, and argument access to YAML
20
+ # configuration files
21
+ # * Implements multilevel caching to reduce disk accesses
22
+ # * Overlays multiple configuration files in an intelligent manner
23
+ #
24
+ # Config file access example:
25
+ # Given a configuration file named test.yaml and test_local.yaml
26
+ # test.yaml:
27
+ # ...
28
+ # hash_1:
29
+ # foo: "foo"
30
+ # bar: "bar"
31
+ # bok: "bok"
32
+ # ...
33
+ # test_local.yaml:
34
+ # ...
35
+ # hash_1:
36
+ # foo: "foo"
37
+ # bar: "baz"
38
+ # zzz: "zzz"
39
+ # ...
40
+ #
41
+ # irb> ActiveConfig.test
42
+ # => {"array_1"=>["a", "b", "c", "d"], "perform_caching"=>true,
43
+ # "default"=>"yo!", "lazy"=>true, "hash_1"=>{"zzz"=>"zzz", "foo"=>"foo",
44
+ # "bok"=>"bok", "bar"=>"baz"}, "secure_login"=>true, "test_mode"=>true}
45
+ #
46
+ # --Notice that the hash produced is the result of merging the above
47
+ # config files in a particular order
48
+ #
49
+ # The overlay order of the config files is defined by ActiveConfig._get_file_suffixes:
50
+ # * nil
51
+ # * _local
52
+ # * _config
53
+ # * _local_config
54
+ # * _{environment} (.i.e _development)
55
+ # * _{environment}_local (.i.e _development_local)
56
+ # * _{hostname} (.i.e _whiskey)
57
+ # * _{hostname}_config_local (.i.e _whiskey_config_local)
58
+ #
59
+ # ------------------------------------------------------------------
60
+ # irb> ActiveConfig.test_local
61
+ # => {"hash_1"=>{"zzz"=>"zzz", "foo"=>"foo", "bar"=>"baz"}, "test_mode"=>true}
62
+ #
63
+
64
+ class ActiveConfig
65
+ EMPTY_ARRAY = [ ].freeze unless defined? EMPTY_ARRAY
66
+ def _suffixes
67
+ @suffixes_obj
68
+ end
69
+ # ActiveConfig.new take options from a hash (or hash like) object.
70
+ # Valid keys are:
71
+ # :path : Where it can find the config files, defaults to ENV['ACTIVE_CONFIG_PATH'], or RAILS_ROOT/etc
72
+ # :root_file : Defines the file that holds "top level" configs. (ie active_config.key). Defaults to "global"
73
+ # :suffixes : Either a suffixes object, or an array of suffixes symbols with their priority. See the ActiveConfig::Suffixes object
74
+ # :config_refresh : How often we should check for update config files
75
+ #
76
+ #
77
+ #FIXME TODO
78
+ def initialize opts={}
79
+ @config_path=opts[:path] || ENV['ACTIVE_CONFIG_PATH'] || (defined?(RAILS_ROOT) ? File.join(RAILS_ROOT,'etc') : nil)
80
+ @root_file=opts[:root_file] || 'global'
81
+ if ActiveConfig::Suffixes===opts[:suffixes]
82
+ @suffixes_obj = opts[:suffixes]
83
+ end
84
+ @suffixes_obj ||= Suffixes.new self, opts[:suffixes]
85
+ @suffixes_obj.ac_instance=self
86
+ @config_refresh =
87
+ (opts.has_key?(:config_refresh) ? opts[:config_refresh].to_i : 300)
88
+ @on_load = { }
89
+ self._flush_cache
90
+ end
91
+ def _config_path
92
+ @config_path_ary ||=
93
+ begin
94
+ path_sep = (@config_path =~ /;/) ? /;/ : /:/ # Make Wesha happy
95
+ path = @config_path.split(path_sep).reject{ | x | x.empty? }
96
+ path.map!{|x| x.freeze }.freeze
97
+ end
98
+ end
99
+
100
+ # DON'T CALL THIS IN production.
101
+ def _flush_cache *types
102
+ if types.size == 0 or types.include? :hash
103
+ @cache_hash = { }
104
+ @hash_times = Hash.new(0)
105
+ end
106
+ if types.size == 0 or types.include? :file
107
+ @file_times = Hash.new(0)
108
+ @file_cache = { }
109
+ end
110
+ self
111
+ end
112
+
113
+ def _reload_disabled=(x)
114
+ @reload_disabled = x.nil? ? false : x
115
+ end
116
+
117
+ def _reload_delay=(x)
118
+ @config_refresh = x || 300
119
+ end
120
+
121
+ def _verbose=(x)
122
+ @verbose = x.nil? ? false : x;
123
+ end
124
+
125
+ ##
126
+ # Get each config file's yaml hash for the given config name,
127
+ # to be merged later. Files will only be loaded if they have
128
+ # not been loaded before or the files have changed within the
129
+ # last five minutes, or force is explicitly set to true.
130
+ #
131
+ # If file contains the comment:
132
+ #
133
+ # # ACTIVE_CONFIG:ERB
134
+ #
135
+ # It will be run through ERb before YAML parsing
136
+ # with the following object bound:
137
+ #
138
+ # active_config.config_file => <<the name of the config.yml file>>
139
+ # active_config.config_directory => <<the directory of the config.yml>>
140
+ # active_config.config_name => <<the config name>>
141
+ # active_config.config_files => <<Array of config files to be parsed>>
142
+ #
143
+ def _load_config_files(name, force=false)
144
+ name = name.to_s
145
+ now = Time.now
146
+
147
+ # Get array of all the existing files file the config name.
148
+ config_files = _config_files(name)
149
+
150
+ #$stderr.puts config_files.inspect
151
+ # Get all the data from all yaml files into as hashes
152
+ _fire_on_load(name)
153
+ hashes = config_files.collect do |f|
154
+ filename=f
155
+ val=nil
156
+ mod_time=nil
157
+ next unless File.exists?(filename)
158
+ next(@file_cache[filename]) unless (mod_time=File.stat(filename).mtime) != @file_times[filename]
159
+ begin
160
+ File.open( filename ) { | yf |
161
+ val = yf.read
162
+ }
163
+ # If file has a # ACTIVE_CONFIG:ERB comment,
164
+ # Process it as an ERb first.
165
+ if /^\s*#\s*ACTIVE_CONFIG\s*:\s*ERB/i.match(val)
166
+ # Prepare a object visible from ERb to
167
+ # allow basic substitutions into YAMLs.
168
+ active_config = HashWithIndifferentAccess.new({
169
+ :config_file => filename,
170
+ :config_directory => File.dirname(filename),
171
+ :config_name => name,
172
+ :config_files => config_files,
173
+ })
174
+ val = ERB.new(val).result(binding)
175
+ end
176
+ # Read file data as YAML.
177
+ val = YAML::load(val)
178
+ # STDERR.puts "ActiveConfig: loaded #{filename.inspect} => #{val.inspect}"
179
+ (@config_file_loaded ||= { })[name] = config_files
180
+ rescue Exception => e
181
+ end
182
+ @file_cache[filename]=val
183
+ @file_times[filename]=mod_time
184
+ @file_cache[filename]
185
+ end
186
+ hashes.compact
187
+ end
188
+
189
+
190
+ def get_config_file(name)
191
+ # STDERR.puts "get_config_file(#{name.inspect})"
192
+ name = name.to_s # if name.is_a?(Symbol)
193
+ now = Time.now
194
+ # $stderr.puts (!!@reload_disabled).inspect
195
+ # $stderr.puts (@hash_times[name.to_sym]).inspect
196
+ #$stderr.puts ( now.to_i - @hash_times[name.to_sym] > @config_refresh).inspect
197
+ #return cached if cached is still good
198
+ return @cache_hash[name.to_sym] if
199
+ (now.to_i - @hash_times[name.to_sym] < @config_refresh)
200
+ #return cached if we have something cached and no reload_disabled flag
201
+ return @cache_hash[name.to_sym] if @cache_hash[name.to_sym] and @reload_disabled
202
+ # $stderr.puts "NOT USING CACHED AND RELOAD DISABLED" if @reload_disabled
203
+ @cache_hash[name.to_sym]=begin
204
+ x = _config_hash(name)
205
+ @hash_times[name.to_sym]=now.to_i
206
+ x
207
+ end
208
+ end
209
+
210
+ ##
211
+ # Returns a list of all relavant config files as specified
212
+ # by the suffixes object.
213
+ def _config_files(name)
214
+ _suffixes.for(name).inject([]) do | files,name_x |
215
+ _config_path.reverse.inject(files) do |files, dir |
216
+ files << File.join(dir, name_x.to_s + '.yml')
217
+ end
218
+ end
219
+ end
220
+
221
+ def _config_hash(name)
222
+ unless result = @cache_hash[name]
223
+ result = @cache_hash[name] =
224
+ _make_indifferent_and_freeze(
225
+ _load_config_files(name).inject({ }) { | n, h | n.weave(h, false) })
226
+ end
227
+ #$stderr.puts result.inspect
228
+ result
229
+ end
230
+
231
+
232
+ ##
233
+ # Register a callback when a config has been reloaded.
234
+ #
235
+ # The config :ANY will register a callback for any config file change.
236
+ #
237
+ # Example:
238
+ #
239
+ # class MyClass
240
+ # @my_config = { }
241
+ # ActiveConfig.on_load(:global) do
242
+ # @my_config = { }
243
+ # end
244
+ # def my_config
245
+ # @my_config ||= something_expensive_thing_on_config(ACTIVEConfig.global.foobar)
246
+ # end
247
+ # end
248
+ #
249
+ def on_load(*args, &blk)
250
+ args << :ANY if args.empty?
251
+ proc = blk.to_proc
252
+
253
+ # Call proc on registration.
254
+ proc.call()
255
+
256
+ # Register callback proc.
257
+ args.each do | name |
258
+ name = name.to_s
259
+ (@on_load[name] ||= [ ]) << proc
260
+ end
261
+ end
262
+
263
+ # Do reload callbacks.
264
+ def _fire_on_load(name)
265
+ callbacks =
266
+ (@on_load['ANY'] || EMPTY_ARRAY) +
267
+ (@on_load[name] || EMPTY_ARRAY)
268
+ callbacks.uniq!
269
+ STDERR.puts "_fire_on_load(#{name.inspect}): callbacks = #{callbacks.inspect}" if @verbose && ! callbacks.empty?
270
+ callbacks.each do | cb |
271
+ cb.call()
272
+ end
273
+ end
274
+
275
+ def _check_config_changed(iname=nil)
276
+ iname=iname.nil? ? @cache_hash.keys.dup : [*iname]
277
+ ret=iname.map{ | name |
278
+ # STDERR.puts "ActiveConfig: config changed? #{name.inspect} reload_disabled = #{@reload_disabled}" if @verbose
279
+ if config_changed?(name) && ! @reload_disabled
280
+ STDERR.puts "ActiveConfig: config changed #{name.inspect}" if @verbose
281
+ if @cache_hash[name]
282
+ @cache_hash[name] = nil
283
+
284
+ # force on_load triggers.
285
+ name
286
+ end
287
+ end
288
+ }.compact
289
+ return nil if ret.empty?
290
+ ret
291
+ end
292
+
293
+ ##
294
+ # Recursively makes hashes into frozen IndifferentAccess ConfigFakerHash
295
+ # Arrays are also traversed and frozen.
296
+ #
297
+ def _make_indifferent_and_freeze(x)
298
+ return x if x.frozen?
299
+ case x
300
+ when HashConfig
301
+ x.each_pair do | k, v |
302
+ x[k] = _make_indifferent_and_freeze(v)
303
+ end
304
+ when Hash
305
+ x = HashConfig.new.merge!(x)
306
+ x.each_pair do | k, v |
307
+ x[k] = _make_indifferent_and_freeze(v)
308
+ end
309
+ # STDERR.puts "x = #{x.inspect}:#{x.class}"
310
+ when Array
311
+ x.collect! do | v |
312
+ _make_indifferent_and_freeze(v)
313
+ end
314
+ end
315
+ x.freeze
316
+ end
317
+
318
+
319
+ def with_file(name, *args)
320
+ # STDERR.puts "with_file(#{name.inspect}, #{args.inspect})"; result =
321
+ args.inject(get_config_file(name)) { | v, i |
322
+ # STDERR.puts "v = #{v.inspect}, i = #{i.inspect}"
323
+ case v
324
+ when Hash
325
+ v[i.to_s]
326
+ when Array
327
+ i.is_a?(Integer) ? v[i] : nil
328
+ else
329
+ nil
330
+ end
331
+ }
332
+ # STDERR.puts "with_file(#{name.inspect}, #{args.inspect}) => #{result.inspect}"; result
333
+ end
334
+
335
+ #If you are using this in production code, you fail.
336
+ def reload(force = false)
337
+ if force || ! @reload_disabled
338
+ _flush_cache
339
+ end
340
+ nil
341
+ end
342
+
343
+ ##
344
+ # Disables any reloading of config,
345
+ # executes &block,
346
+ # calls check_config_changed,
347
+ # returns result of block
348
+ #
349
+ def disable_reload(&block)
350
+ # This should increment @reload_disabled on entry, decrement on exit.
351
+ # -- kurt 2007/06/12
352
+ result = nil
353
+ reload_disabled_save = @reload_disabled
354
+ begin
355
+ @reload_disabled = true
356
+ result = yield
357
+ ensure
358
+ @reload_disabled = reload_disabled_save
359
+ _check_config_changed unless @reload_disabled
360
+ end
361
+ result
362
+ end
363
+
364
+ ##
365
+ # Gets a value from the global config file
366
+ #
367
+ def [](key, file=_root_file)
368
+ get_config_file(file)[key]
369
+ end
370
+
371
+ ##
372
+ # Short-hand access to config file by its name.
373
+ #
374
+ # Example:
375
+ #
376
+ # ActiveConfig.global(:foo) => ActiveConfig.with_file(:global).foo
377
+ # ActiveConfig.global.foo => ActiveConfig.with_file(:global).foo
378
+ #
379
+ def method_missing(method, *args)
380
+ if method.to_s=~/^_(.*)/
381
+ _flush_cache
382
+ return @suffixes.send($1, *args)
383
+ else
384
+ value = with_file(method, *args)
385
+ value
386
+ end
387
+ end
388
+ end
389
+
data/lib/cnu_config.rb ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'active_config'
3
+
4
+ class ActiveConfig::SuffixesWithOverlay < ActiveConfig::Suffixes
5
+ def overlay= new_overlay
6
+ symbols[:overlay]=(new_overlay.respond_to?(:upcase) ? new_overlay.upcase : new_overlay)
7
+ end
8
+ def initialize(*args)
9
+ super
10
+ @symbols[:overlay]=proc { |sym_table| ENV['ACTIVE_CONFIG_OVERLAY']}
11
+ @priority=[
12
+ nil,
13
+ [:overlay, nil],
14
+ [:local],
15
+ [:overlay, [:local]],
16
+ :config,
17
+ [:overlay, :config],
18
+ :local_config,
19
+ [:overlay, :local_config],
20
+ :hostname,
21
+ [:overlay, :hostname],
22
+ [:hostname, :local_config],
23
+ [:overlay, [:hostname, :local_config]]
24
+ ]
25
+ end
26
+ end
27
+ class CnuConfigClass < ActiveConfig
28
+ attr :_overlay
29
+ def _suffixes
30
+ @_suffixes_obj||=Suffixes.new
31
+ end
32
+ def _overlay= x
33
+ _suffixes.priority=[
34
+ nil,
35
+ [:overlay, nil],
36
+ [:local],
37
+ [:overlay, [:local]],
38
+ :config,
39
+ [:overlay, :config],
40
+ :local_config,
41
+ [:overlay, :local_config],
42
+ :hostname,
43
+ [:overlay, :hostname],
44
+ [:hostname, :config_local],
45
+ [:overlay, [:hostname, :config_local]]
46
+ ]
47
+ super
48
+ end
49
+ def _config_path
50
+ @config_path||= ENV['CNU_CONFIG_PATH']
51
+ end
52
+ end
53
+ CnuConfig=CnuConfigClass.new
54
+
55
+
@@ -0,0 +1 @@
1
+ using_array_index: 101
@@ -0,0 +1,14 @@
1
+ #test_mode: false
2
+ secure_login: true
3
+ perform_caching: true
4
+ lazy: true
5
+
6
+ default: "yo!" # Test for #default override for .default notation
7
+
8
+ hash_1:
9
+ foo: "foo"
10
+ bar: "bar"
11
+ bok: "bok"
12
+
13
+
14
+ array_1: [ a, b, c, d ]
@@ -0,0 +1,13 @@
1
+ #test_mode: false
2
+ secure_login: true
3
+ perform_caching: true
4
+ lazy: true
5
+
6
+ default: "yo!" # Test for #default override for .default notation
7
+
8
+ hash_1:
9
+ foo: "foo"
10
+ bar: "bar"
11
+ bok: "GB"
12
+ gb: "GB"
13
+
@@ -0,0 +1,15 @@
1
+ #test_mode: false
2
+ secure_login: true
3
+ perform_caching: true
4
+ lazy: true
5
+
6
+ default: "yo!" # Test for #default override for .default notation
7
+
8
+ hash_1:
9
+ foo: "foo"
10
+ bar: "bar"
11
+ bok: "US"
12
+ gb: ~
13
+
14
+
15
+
File without changes
@@ -0,0 +1,9 @@
1
+ test_mode: true
2
+
3
+
4
+ hash_1:
5
+ foo: "foo"
6
+ bar: "baz"
7
+ zzz: "zzz"
8
+
9
+
@@ -0,0 +1 @@
1
+ secure_login: false