activeconfig 0.5.5

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/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
+