activeconfig 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rdoc", "~> 3.12"
5
+ gem "bundler", "> 1.0.0"
6
+ gem "jeweler", "~> 1.8.3"
7
+ end
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+
16
+ require 'rubygems'
17
+
18
+ #require 'rubygems/package_task'
19
+ require 'jeweler'
20
+ Jeweler::Tasks.new do |s|
21
+ s.name = 'activeconfig'
22
+ s.author = 'Jeremy Lawler'
23
+ s.email = 'jeremylawler@gmail.com'
24
+ s.homepage = 'http://jlawler.github.com/activeconfig/'
25
+ s.summary = 'An extremely flexible configuration system'
26
+ s.description = 'An extremely flexible configuration system.
27
+ s the ability for certain values to be "overridden" when conditions are met.
28
+ r example, you could have your production API keys only get read when the Rails.env == "production"'
29
+ s.authors = ["Jeremy Lawler"]
30
+ end
31
+ Jeweler::RubygemsDotOrgTasks.new
32
+
33
+
34
+
35
+ task :rdoc do
36
+ sh "rm -rf #{File.dirname(__FILE__)}/doc"
37
+ sh "cd lib && rdoc -o ../doc "
38
+ end
39
+
40
+ task :test do
41
+ Dir['*/*_test.rb'].each do |f|
42
+ puts `ruby -I lib #{f}`
43
+ end
44
+ end
45
+
46
+ task :default => :test
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 5
4
+ :patch: 5
5
+ :build:
@@ -0,0 +1,73 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "activeconfig"
8
+ s.version = "0.5.5"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jeremy Lawler"]
12
+ s.date = "2012-05-30"
13
+ s.description = "An extremely flexible configuration system.\ns the ability for certain values to be \"overridden\" when conditions are met.\nr example, you could have your production API keys only get read when the Rails.env == \"production\""
14
+ s.email = "jeremylawler@gmail.com"
15
+ s.executables = ["active_config"]
16
+ s.files = [
17
+ "Gemfile",
18
+ "Rakefile",
19
+ "VERSION.yml",
20
+ "activeconfig.gemspec",
21
+ "bin/active_config",
22
+ "lib/active_config.rb",
23
+ "lib/active_config/hash_config.rb",
24
+ "lib/active_config/hash_weave.rb",
25
+ "lib/active_config/suffixes.rb",
26
+ "pkg/activeconfig-0.1.4.gem",
27
+ "pkg/activeconfig-0.2.0.gem",
28
+ "pkg/activeconfig-0.3.0.gem",
29
+ "pkg/activeconfig-0.4.0.gem",
30
+ "pkg/activeconfig-0.4.1.gem",
31
+ "pkg/activeconfig-0.5.0.gem",
32
+ "pkg/activeconfig-0.5.1.gem",
33
+ "test/active_config_collision_test.rb",
34
+ "test/active_config_multi_test.rb",
35
+ "test/active_config_test.rb",
36
+ "test/active_config_test/global.yml",
37
+ "test/active_config_test/test.yml",
38
+ "test/active_config_test/test_GB.yml",
39
+ "test/active_config_test/test_US.yml",
40
+ "test/active_config_test/test_config.yml",
41
+ "test/active_config_test/test_local.yml",
42
+ "test/active_config_test/test_production.yml",
43
+ "test/active_config_test_collision/patha/test.yml",
44
+ "test/active_config_test_collision/pathb/test.yml",
45
+ "test/active_config_test_collision/pathb/test_local.yml",
46
+ "test/active_config_test_multi/patha/test.yml",
47
+ "test/active_config_test_multi/pathb/test_local.yml",
48
+ "test/env_test.rb"
49
+ ]
50
+ s.homepage = "http://jlawler.github.com/activeconfig/"
51
+ s.require_paths = ["lib"]
52
+ s.rubygems_version = "1.8.11"
53
+ s.summary = "An extremely flexible configuration system"
54
+
55
+ if s.respond_to? :specification_version then
56
+ s.specification_version = 3
57
+
58
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
59
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
60
+ s.add_development_dependency(%q<bundler>, ["> 1.0.0"])
61
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
62
+ else
63
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
64
+ s.add_dependency(%q<bundler>, ["> 1.0.0"])
65
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
69
+ s.add_dependency(%q<bundler>, ["> 1.0.0"])
70
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
71
+ end
72
+ end
73
+
data/bin/active_config ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ =begin rdoc
4
+
5
+ # Prints info from to STDOUT.
6
+ #
7
+ # Example:
8
+ #
9
+ # > active_config httpd.httpd.domain.portal.primary httpd.httpd.domain.frontend.primary
10
+ # portal.cashnetusa.com
11
+ # www.cashnetusa.com
12
+ #
13
+ # > ACTIVE_CONFIG_OVERLAY=gb active_config httpd.httpd.domain.portal.primary httpd.httpd.domain.frontend.primary
14
+ # portaluk.cashnetusa.com
15
+ # www.quickquid.co.uk
16
+ #
17
+ # > ACTIVE_CONFIG_OVERLAY=gb RAILS_ENV=production active_config --PRODDB 'database[RAILS_ENV].database'
18
+ # PRODDB=activeapp_dev_uk
19
+ # > ACTIVE_CONFIG_OVERLAY=us RAILS_ENV=production active_config --PRODDB 'database[RAILS_ENV].database'
20
+ # PRODDB=activeapp_dev
21
+
22
+ =end
23
+
24
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/lib/ruby')
25
+
26
+ require 'rubygems'
27
+
28
+ # Yuck!
29
+ RAILS_ENV = ENV['RAILS_ENV'] || 'development'
30
+
31
+ require 'active_config'
32
+
33
+ name = ''
34
+
35
+ ARGV.each do | x |
36
+ if x =~ /^--(.+)/
37
+ name = "#{$1}="
38
+ else
39
+ puts(name + (eval(".#{x}").to_s))
40
+ end
41
+ end
42
+
43
+ exit 0
44
+
@@ -0,0 +1,384 @@
1
+ require 'socket'
2
+ require 'yaml'
3
+ require 'active_config/hash_weave' # Hash#weave
4
+ require 'rubygems'
5
+ require 'active_config/hash_config'
6
+ require 'active_config/suffixes'
7
+ require 'erb'
8
+
9
+
10
+ ##
11
+ # See LICENSE.txt for details
12
+ #
13
+ #=ActiveConfig
14
+ #
15
+ # * Provides dottable, hash, array, and argument access to YAML
16
+ # configuration files
17
+ # * Implements multilevel caching to reduce disk accesses
18
+ # * Overlays multiple configuration files in an intelligent manner
19
+ #
20
+ # Config file access example:
21
+ # Given a configuration file named test.yaml and test_local.yaml
22
+ # test.yaml:
23
+ # ...
24
+ # hash_1:
25
+ # foo: "foo"
26
+ # bar: "bar"
27
+ # bok: "bok"
28
+
29
+ # ...
30
+ # test_local.yaml:
31
+ # ...
32
+ # hash_1:
33
+ # foo: "foo"
34
+ # bar: "baz"
35
+ # zzz: "zzz"
36
+ # ...
37
+ #
38
+ # irb> ActiveConfig.test
39
+ # => {"array_1"=>["a", "b", "c", "d"], "perform_caching"=>true,
40
+ # "default"=>"yo!", "lazy"=>true, "hash_1"=>{"zzz"=>"zzz", "foo"=>"foo",
41
+ # "bok"=>"bok", "bar"=>"baz"}, "secure_login"=>true, "test_mode"=>true}
42
+ #
43
+ # --Notice that the hash produced is the result of merging the above
44
+ # config files in a particular order
45
+ #
46
+ # The overlay order of the config files is defined by ActiveConfig._get_file_suffixes:
47
+ # * nil
48
+ # * _local
49
+ # * _config
50
+ # * _local_config
51
+ # * _{environment} (.i.e _development)
52
+ # * _{environment}_local (.i.e _development_local)
53
+ # * _{hostname} (.i.e _whiskey)
54
+ # * _{hostname}_config_local (.i.e _whiskey_config_local)
55
+ #
56
+ # ------------------------------------------------------------------
57
+ # irb> ActiveConfig.test_local
58
+ # => {"hash_1"=>{"zzz"=>"zzz", "foo"=>"foo", "bar"=>"baz"}, "test_mode"=>true}
59
+ #
60
+
61
+ class ActiveConfig
62
+ class ActiveConfig::DuplicateConfig < Exception; end
63
+ EMPTY_ARRAY = [ ].freeze unless defined? EMPTY_ARRAY
64
+ def _suffixes
65
+ @suffixes_obj
66
+ end
67
+ # ActiveConfig.new take options from a hash (or hash like) object.
68
+ # Valid keys are:
69
+ # :path : Where it can find the config files, defaults to ENV['ACTIVE_CONFIG_PATH'], or RAILS_ROOT/etc
70
+ # :root_file : Defines the file that holds "top level" configs. (ie active_config.key). Defaults to "global"
71
+ # :suffixes : Either a suffixes object, or an array of suffixes symbols with their priority. See the ActiveConfig::Suffixes object
72
+ # :config_refresh : How often we should check for update config files
73
+ #
74
+ #
75
+ #FIXME TODO
76
+ def initialize opts={}
77
+ @config_path=opts[:path] || ENV['ACTIVE_CONFIG_PATH'] || (defined?(RAILS_ROOT) ? File.join(RAILS_ROOT,'etc') : nil)
78
+ @opts=opts
79
+ if opts[:one_file]
80
+ @root_file=@config_path
81
+ else
82
+ @root_file=opts[:root_file] || 'global'
83
+ if ActiveConfig::Suffixes===opts[:suffixes]
84
+ @suffixes_obj = opts[:suffixes]
85
+ end
86
+ end
87
+ @suffixes_obj ||= Suffixes.new self, opts[:suffixes]
88
+ @suffixes_obj.ac_instance=self
89
+ @config_refresh =
90
+ (opts.has_key?(:config_refresh) ? opts[:config_refresh].to_i : 300)
91
+ @on_load = { }
92
+ self._flush_cache
93
+ dups_h=Hash.new{|h,e|h[e]=[]}
94
+ self._config_path.map{|e|
95
+ if File.exists?(e) and File.directory?(e)
96
+ Dir[e + '/*'].map{|f|
97
+ if File.file?(f)
98
+ dups_h[File.basename(f)] << f
99
+ end
100
+ }
101
+ else
102
+ STDERR.puts "WARNING: Active Config Path NOT FOUND #{e}" unless opts[:quiet]
103
+ end
104
+ }
105
+ dups = dups_h.to_a.select{|k,v|v.size>=2}
106
+ raise ActiveConfig::DuplicateConfig.new(dups.map{|e|"Duplicate file #{e.first} found in \n#{e.last.map{|el|"\t"+el}.join("\n")}"}.join("\n")) if dups.size>0
107
+ end
108
+ def _config_path
109
+ @config_path_ary ||=
110
+ begin
111
+ path_sep = (@config_path =~ /;/) ? /;/ : /:/ # Make Wesha happy
112
+ path = @config_path.split(path_sep).reject{ | x | x.empty? }
113
+ path.map!{|x| x.freeze }.freeze
114
+ end
115
+ end
116
+
117
+ # DON'T CALL THIS IN production.
118
+ def _flush_cache *types
119
+ if types.size == 0 or types.include? :hash
120
+ @cache_hash = { }
121
+ @hash_times = Hash.new(0)
122
+ end
123
+ if types.size == 0 or types.include? :file
124
+ @file_times = Hash.new(0)
125
+ @file_cache = { }
126
+ end
127
+ self
128
+ end
129
+
130
+ def _reload_disabled=(x)
131
+ @reload_disabled = x.nil? ? false : x
132
+ end
133
+
134
+ def _reload_delay=(x)
135
+ @config_refresh = x || 300
136
+ end
137
+
138
+ def _verbose=(x)
139
+ @verbose = x.nil? ? false : x;
140
+ end
141
+
142
+ ##
143
+ # Get each config file's yaml hash for the given config name,
144
+ # to be merged later. Files will only be loaded if they have
145
+ # not been loaded before or the files have changed within the
146
+ # last five minutes, or force is explicitly set to true.
147
+ #
148
+ # If file contains the comment:
149
+ #
150
+ # # ACTIVE_CONFIG:ERB
151
+ #
152
+ # It will be run through ERb before YAML parsing
153
+ # with the following object bound:
154
+ #
155
+ # active_config.config_file => <<the name of the config.yml file>>
156
+ # active_config.config_directory => <<the directory of the config.yml>>
157
+ # active_config.config_name => <<the config name>>
158
+ # active_config.config_files => <<Array of config files to be parsed>>
159
+ #
160
+ def _load_config_files(name, force=false)
161
+ name = name.to_s
162
+ now = Time.now
163
+
164
+ # Get array of all the existing files file the config name.
165
+ config_files = _config_files(name)
166
+
167
+ #$stderr.puts config_files.inspect
168
+ # Get all the data from all yaml files into as hashes
169
+ _fire_on_load(name)
170
+ hashes = config_files.collect do |f|
171
+ filename=f
172
+ val=nil
173
+ mod_time=nil
174
+ next unless File.exists?(filename)
175
+ next(@file_cache[filename]) unless (mod_time=File.stat(filename).mtime) != @file_times[filename]
176
+ begin
177
+ File.open( filename ) { | yf |
178
+ val = yf.read
179
+ }
180
+ # If file has a # ACTIVE_CONFIG:ERB comment,
181
+ # Process it as an ERb first.
182
+ if /^\s*#\s*ACTIVE_CONFIG\s*:\s*ERB/i.match(val)
183
+ # Prepare a object visible from ERb to
184
+ # allow basic substitutions into YAMLs.
185
+ active_config = HashConfig.new({
186
+ :config_file => filename,
187
+ :config_directory => File.dirname(filename),
188
+ :config_name => name,
189
+ :config_files => config_files,
190
+ })
191
+ val = ERB.new(val).result(binding)
192
+ end
193
+ # Read file data as YAML.
194
+ val = YAML::load(val)
195
+ # STDERR.puts "ActiveConfig: loaded #{filename.inspect} => #{val.inspect}"
196
+ (@config_file_loaded ||= { })[name] = config_files
197
+ rescue Exception => e
198
+ raise
199
+ end
200
+ @file_cache[filename]=val
201
+ @file_times[filename]=mod_time
202
+ @file_cache[filename]
203
+ end
204
+ hashes.compact
205
+ end
206
+
207
+
208
+ def get_config_file(name)
209
+ # STDERR.puts "get_config_file(#{name.inspect})"
210
+ name = name.to_s # if name.is_a?(Symbol)
211
+ now = Time.now
212
+ return @cache_hash[name.to_sym] if
213
+ (now.to_i - @hash_times[name.to_sym] < @config_refresh)
214
+ # return cached if we have something cached and no reload_disabled flag
215
+ return @cache_hash[name.to_sym] if @cache_hash[name.to_sym] and @reload_disabled
216
+ # $stderr.puts "NOT USING CACHED AND RELOAD DISABLED" if @reload_disabled
217
+ @cache_hash[name.to_sym]=begin
218
+ x = _config_hash(name)
219
+ @hash_times[name.to_sym]=now.to_i
220
+ x
221
+ end
222
+ end
223
+
224
+ ##
225
+ # Returns a list of all relavant config files as specified
226
+ # by the suffixes object.
227
+ def _config_files(name)
228
+ return [name] if File.exists?(name) and not File.directory?(name)
229
+ _suffixes.for(name).inject([]) do | files,name_x |
230
+ _config_path.reverse.inject(files) do |files, dir |
231
+ files << File.join(dir, name_x.to_s + '.yml')
232
+ end
233
+ end
234
+ end
235
+
236
+ def _config_hash(name)
237
+ unless result = @cache_hash[name]
238
+ result = @cache_hash[name] =
239
+ HashConfig._make_indifferent_and_freeze(
240
+ _load_config_files(name).inject({ }) { | n, h | n.weave(h, false) })
241
+ end
242
+ #$stderr.puts result.inspect
243
+ result
244
+ end
245
+
246
+
247
+ ##
248
+ # Register a callback when a config has been reloaded.
249
+ #
250
+ # The config :ANY will register a callback for any config file change.
251
+ #
252
+ # Example:
253
+ #
254
+ # class MyClass
255
+ # @my_config = { }
256
+ # ActiveConfig.on_load(:global) do
257
+ # @my_config = { }
258
+ # end
259
+ # def my_config
260
+ # @my_config ||= something_expensive_thing_on_config(ACTIVEConfig.global.foobar)
261
+ # end
262
+ # end
263
+ #
264
+ def on_load(*args, &blk)
265
+ args << :ANY if args.empty?
266
+ proc = blk.to_proc
267
+
268
+ # Call proc on registration.
269
+ proc.call()
270
+
271
+ # Register callback proc.
272
+ args.each do | name |
273
+ name = name.to_s
274
+ (@on_load[name] ||= [ ]) << proc
275
+ end
276
+ end
277
+
278
+ # Do reload callbacks.
279
+ def _fire_on_load(name)
280
+ callbacks =
281
+ (@on_load['ANY'] || EMPTY_ARRAY) +
282
+ (@on_load[name] || EMPTY_ARRAY)
283
+ callbacks.uniq!
284
+ STDERR.puts "_fire_on_load(#{name.inspect}): callbacks = #{callbacks.inspect}" if @verbose && ! callbacks.empty?
285
+ callbacks.each do | cb |
286
+ cb.call()
287
+ end
288
+ end
289
+
290
+ def _check_config_changed(iname=nil)
291
+ iname=iname.nil? ? @cache_hash.keys.dup : [*iname]
292
+ ret=iname.map{ | name |
293
+ # STDERR.puts "ActiveConfig: config changed? #{name.inspect} reload_disabled = #{@reload_disabled}" if @verbose
294
+ if config_changed?(name) && ! @reload_disabled
295
+ STDERR.puts "ActiveConfig: config changed #{name.inspect}" if @verbose
296
+ if @cache_hash[name]
297
+ @cache_hash[name] = nil
298
+
299
+ # force on_load triggers.
300
+ name
301
+ end
302
+ end
303
+ }.compact
304
+ return nil if ret.empty?
305
+ ret
306
+ end
307
+
308
+ def with_file(name, *args)
309
+ # STDERR.puts "with_file(#{name.inspect}, #{args.inspect})"; result =
310
+ args.inject(get_config_file(name)) { | v, i |
311
+ # STDERR.puts "v = #{v.inspect}, i = #{i.inspect}"
312
+ case v
313
+ when Hash
314
+ v[i.to_s]
315
+ when Array
316
+ i.is_a?(Integer) ? v[i] : nil
317
+ else
318
+ nil
319
+ end
320
+ }
321
+ # STDERR.puts "with_file(#{name.inspect}, #{args.inspect}) => #{result.inspect}"; result
322
+ end
323
+
324
+ #If you are using this in production code, you fail.
325
+ def reload(force = false)
326
+ if force || ! @reload_disabled
327
+ _flush_cache
328
+ end
329
+ nil
330
+ end
331
+
332
+ ##
333
+ # Disables any reloading of config,
334
+ # executes &block,
335
+ # calls check_config_changed,
336
+ # returns result of block
337
+ #
338
+ def disable_reload(&block)
339
+ # This should increment @reload_disabled on entry, decrement on exit.
340
+ # -- kurt 2007/06/12
341
+ result = nil
342
+ reload_disabled_save = @reload_disabled
343
+ begin
344
+ @reload_disabled = true
345
+ result = yield
346
+ ensure
347
+ @reload_disabled = reload_disabled_save
348
+ _check_config_changed unless @reload_disabled
349
+ end
350
+ result
351
+ end
352
+
353
+ ##
354
+ # Gets a value from the global config file
355
+ #
356
+ def [](key, file=@root_file)
357
+ get_config_file(file)[key]
358
+ end
359
+
360
+ ##
361
+ # Short-hand access to config file by its name.
362
+ #
363
+ # Example:
364
+ #
365
+ # ActiveConfig.global(:foo) => ActiveConfig.with_file(:global).foo
366
+ # ActiveConfig.global.foo => ActiveConfig.with_file(:global).foo
367
+ #
368
+ def method_missing(method, *args)
369
+ return self[method.to_sym] if @opts[:one_file]
370
+ if method.to_s=~/^_(.*)/
371
+ _flush_cache
372
+ return @suffixes.send($1, *args)
373
+ else
374
+ if @root_file && rf=get_config_file(@root_file)
375
+ if rf.has_key?(method.to_sym) || rf.has_key?(method.to_s)
376
+ return with_file(@root_file).send(method,*args)
377
+ end
378
+ end
379
+ value = with_file(method, *args)
380
+ value
381
+ end
382
+ end
383
+ end
384
+