jlawler-activeconfig 0.1.4 → 0.2.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/Rakefile +54 -0
- data/VERSION.yml +4 -0
- data/bin/active_config +46 -0
- data/lib/active_config/hash_config.rb +110 -0
- data/lib/active_config/hash_weave.rb +72 -0
- data/lib/active_config/suffixes.rb +85 -0
- data/lib/active_config.rb +389 -0
- data/lib/cnu_config.rb +55 -0
- data/test/active_config_test/global.yml +1 -0
- data/test/active_config_test/test.yml +14 -0
- data/test/active_config_test/test_GB.yml +13 -0
- data/test/active_config_test/test_US.yml +15 -0
- data/test/active_config_test/test_config.yml +0 -0
- data/test/active_config_test/test_local.yml +9 -0
- data/test/active_config_test/test_production.yml +1 -0
- data/test/active_config_test.rb +426 -0
- data/test/active_config_test_multi/patha/test.yml +14 -0
- data/test/active_config_test_multi/pathb/test_local.yml +2 -0
- data/test/active_config_test_multi.rb +56 -0
- data/test/cnu_config_test/global.yml +1 -0
- data/test/cnu_config_test/test.yml +14 -0
- data/test/cnu_config_test/test_GB.yml +13 -0
- data/test/cnu_config_test/test_US.yml +15 -0
- data/test/cnu_config_test/test_local.yml +9 -0
- data/test/cnu_config_test.rb +405 -0
- data/test/env_test.rb +76 -0
- metadata +40 -32
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
Gem::manage_gems
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |s|
|
7
|
+
s.name = 'activeconfig'
|
8
|
+
s.author = 'Jeremy Lawler'
|
9
|
+
s.email = 'jlawler@cashnetusa.com'
|
10
|
+
s.homepage = 'http://jlawler.github.com/activeconfig/'
|
11
|
+
s.summary = 'An extremely flexible configuration system'
|
12
|
+
s.authors = ["Jeremy Lawler"]
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
16
|
+
end
|
17
|
+
|
18
|
+
task :default => :test
|
19
|
+
active_config_multi_paths=[File.expand_path(File.dirname(__FILE__) + "/test/active_config_test_multi/patha"),':',File.expand_path(File.dirname(__FILE__) + "/test/active_config_test_multi/pathb")]
|
20
|
+
|
21
|
+
task :rdoc do
|
22
|
+
sh "rm -rf #{File.dirname(__FILE__)}/doc"
|
23
|
+
sh "cd lib && rdoc -o ../doc "
|
24
|
+
end
|
25
|
+
task :test do
|
26
|
+
sh "ruby -I lib test/active_config_test.rb"
|
27
|
+
puts "\n\n"
|
28
|
+
exception1,exception2,exception3=nil,nil,nil
|
29
|
+
begin
|
30
|
+
ENV['ACTIVE_CONFIG_PATH'] = active_config_multi_paths.join('')
|
31
|
+
sh "ruby -I lib test/active_config_test_multi.rb"
|
32
|
+
rescue Object => exception1
|
33
|
+
end
|
34
|
+
puts "\n\n"
|
35
|
+
begin
|
36
|
+
ENV['ACTIVE_CONFIG_PATH'] = active_config_multi_paths.reverse.join('')
|
37
|
+
sh "ruby -I lib test/active_config_test_multi.rb"
|
38
|
+
sh "ruby -I lib test/env_test.rb"
|
39
|
+
rescue Object => exception2
|
40
|
+
end
|
41
|
+
x=exception1 ||exception2
|
42
|
+
raise x if x
|
43
|
+
end
|
44
|
+
task :cnu_config_test do
|
45
|
+
sh "ruby -I lib test/cnu_config_test.rb"
|
46
|
+
puts "\n\n"
|
47
|
+
begin
|
48
|
+
ENV['CNU_CONFIG_PATH'] = cnu_config_multi_paths.join('')
|
49
|
+
sh "ruby -I lib test/cnu_config_test.rb"
|
50
|
+
rescue Object => exception3
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
data/VERSION.yml
ADDED
data/bin/active_config
ADDED
@@ -0,0 +1,46 @@
|
|
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
|
+
# Need to remove active_config's dependency on activesupport.
|
27
|
+
require 'rubygems'
|
28
|
+
gem 'activesupport'
|
29
|
+
|
30
|
+
# Yuck!
|
31
|
+
RAILS_ENV = ENV['RAILS_ENV'] || 'development'
|
32
|
+
|
33
|
+
require 'active_config'
|
34
|
+
|
35
|
+
name = ''
|
36
|
+
|
37
|
+
ARGV.each do | x |
|
38
|
+
if x =~ /^--(.+)/
|
39
|
+
name = "#{$1}="
|
40
|
+
else
|
41
|
+
puts(name + (eval(".#{x}").to_s))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
exit 0
|
46
|
+
|
@@ -0,0 +1,110 @@
|
|
1
|
+
class ActiveConfig
|
2
|
+
class HashConfig < HashWithIndifferentAccess
|
3
|
+
# HashWithIndifferentAccess#dup always returns HashWithIndifferentAccess!
|
4
|
+
# -- kurt 2007/10/18
|
5
|
+
def dup
|
6
|
+
self.class.new(self)
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
# dotted notation can now be used with arguments (useful for creating mock objects)
|
11
|
+
# in the YAML file the method name is a key (just like usual), argument(s)
|
12
|
+
# form a nested key, and the value will get returned.
|
13
|
+
#
|
14
|
+
# For example loading to variable foo a yaml file that looks like:
|
15
|
+
# customer:
|
16
|
+
# id: 12345678
|
17
|
+
# verified:
|
18
|
+
# phone: verified
|
19
|
+
# :address: info_not_available
|
20
|
+
# ? [name, employer]
|
21
|
+
# : not_verified
|
22
|
+
#
|
23
|
+
# Allows the following calls:
|
24
|
+
# foo.customer.id --> 12345678
|
25
|
+
# foo.customer.verified.phone --> verified
|
26
|
+
# foo.customer.verified("phone") --> verified
|
27
|
+
# foo.customer.verified("name", "employer") --> not_verified
|
28
|
+
# foo.customer.verified(:address) --> info_not_available
|
29
|
+
#
|
30
|
+
# Note that :address is specified as a symbol, where phone is just a string.
|
31
|
+
# Depending on what kind of parameter the method being mocked out is going
|
32
|
+
# to be called with, define in the YAML file either a string or a symbol.
|
33
|
+
# This also works inside the composite array keys.
|
34
|
+
def method_missing(method, *args)
|
35
|
+
method = method.to_s
|
36
|
+
# STDERR.puts "CHF#method_missing(#{method.inspect}, #{args.inspect}) on #{self.inspect}:#{self.class}" if method == 'dup'
|
37
|
+
value = self[method]
|
38
|
+
case args.size
|
39
|
+
when 0:
|
40
|
+
# e.g.: ActiveConfig.global.method
|
41
|
+
;
|
42
|
+
when 1:
|
43
|
+
# e.g.: ActiveConfig.global.method(one_arg)
|
44
|
+
value = value.send(args[0])
|
45
|
+
else
|
46
|
+
# e.g.: ActiveConfig.global.method(arg_one, args_two, ...)
|
47
|
+
value = value[args]
|
48
|
+
end
|
49
|
+
# value = convert_value(value)
|
50
|
+
value
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Why the &*#^@*^&$ isn't HashWithIndifferentAccess actually doing this?
|
55
|
+
#
|
56
|
+
def [](key)
|
57
|
+
key = key.to_s if key.kind_of?(Symbol)
|
58
|
+
super(key)
|
59
|
+
end
|
60
|
+
|
61
|
+
# HashWithIndifferentAccess#default is broken!
|
62
|
+
define_method(:default_Hash, Hash.instance_method(:default))
|
63
|
+
|
64
|
+
##
|
65
|
+
# Allow hash.default => hash['default']
|
66
|
+
# without breaking Hash's usage of default(key)
|
67
|
+
#
|
68
|
+
@@no_key = [ :no_key ] # magically unique value.
|
69
|
+
def default(key = @@no_key)
|
70
|
+
key = key.to_s if key.is_a?(Symbol)
|
71
|
+
key == @@no_key ? self['default'] : default_Hash(key == @@no_key ? nil : key)
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# HashWithIndifferentAccess#update is broken!
|
76
|
+
# This took way too long to figure this out:
|
77
|
+
#
|
78
|
+
# Hash#update returns self,
|
79
|
+
# BUT,
|
80
|
+
# HashWithIndifferentAccess#update does not!
|
81
|
+
#
|
82
|
+
# { :a => 1 }.update({ :b => 2, :c => 3 })
|
83
|
+
# => { :a => 1, :b => 2, :c => 3 }
|
84
|
+
#
|
85
|
+
# HashWithIndifferentAccess.new({ :a => 1 }).update({ :b => 2, :c => 3 })
|
86
|
+
# => { :b => 2, :c => 3 } # WTF?
|
87
|
+
#
|
88
|
+
# Subclasses should *never* override methods and break their protocols!!!!
|
89
|
+
# -- kurt 2007/03/28
|
90
|
+
#
|
91
|
+
def update(hash)
|
92
|
+
super(hash)
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Override WithIndifferentAccess#convert_value
|
98
|
+
# return instances of this class for Hash values.
|
99
|
+
#
|
100
|
+
def convert_value(value)
|
101
|
+
# STDERR.puts "convert_value(#{value.inspect}:#{value.class})"
|
102
|
+
if value.class == Hash
|
103
|
+
value = self.class.new(value)
|
104
|
+
value.freeze if self.frozen?
|
105
|
+
end
|
106
|
+
value
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
unless Hash.instance_methods.include? 'weave'
|
2
|
+
class Hash
|
3
|
+
# source: http://rubyforge.org/projects/facets/
|
4
|
+
# version: 1.7.46
|
5
|
+
# license: Ruby License
|
6
|
+
# NOTE: remove this method if the Facets gem is installed.
|
7
|
+
# BUG: weave is destructive to values in the source hash that are arrays!
|
8
|
+
# (this is acceptable for our use as the basis for weave!)
|
9
|
+
# -------------------------------------------------------------
|
10
|
+
# Weaves two hashes producing a new hash. The two hashes need
|
11
|
+
# to be compatible according to the following rules for each node:
|
12
|
+
#
|
13
|
+
# <tt>
|
14
|
+
# hash, hash => hash (recursive +)
|
15
|
+
# hash, array => error
|
16
|
+
# hash, value => error
|
17
|
+
# array, hash => error
|
18
|
+
# array, array => array + array
|
19
|
+
# array, value => array << value
|
20
|
+
# value, hash => error
|
21
|
+
# value, array => array.unshift(valueB)
|
22
|
+
# valueA, valueB => valueB
|
23
|
+
# </tt>
|
24
|
+
#
|
25
|
+
# Example:
|
26
|
+
#
|
27
|
+
# # to do
|
28
|
+
#
|
29
|
+
def weave(h, dont_clobber=true)
|
30
|
+
return self unless h
|
31
|
+
raise ArgumentError, "Hash expected" unless h.kind_of?(Hash)
|
32
|
+
s = self.dup # self.clone does not remove freeze!
|
33
|
+
h.each { |k,node|
|
34
|
+
node_is_hash = node.kind_of?(Hash)
|
35
|
+
node_is_array = node.kind_of?(Array)
|
36
|
+
if s.has_key?(k)
|
37
|
+
self_node_is_hash = s[k].kind_of?(Hash)
|
38
|
+
self_node_is_array = s[k].kind_of?(Array)
|
39
|
+
if self_node_is_hash
|
40
|
+
if node_is_hash
|
41
|
+
s[k] = s[k].weave(node, dont_clobber)
|
42
|
+
elsif node_is_array
|
43
|
+
dont_clobber ? raise(ArgumentError, "{} <= [] is a tad meaningless") : s[k] = node
|
44
|
+
else
|
45
|
+
s[k] = node
|
46
|
+
end
|
47
|
+
elsif self_node_is_array
|
48
|
+
if node_is_hash
|
49
|
+
dont_clobber ? s[k] = s[k] << node : s[k] = node
|
50
|
+
elsif node_is_array
|
51
|
+
dont_clobber ? s[k] += node : s[k] = node
|
52
|
+
else
|
53
|
+
dont_clobber ? s[k] = s[k] << node : s[k] = node
|
54
|
+
end
|
55
|
+
else
|
56
|
+
if node_is_hash
|
57
|
+
s[k] = node
|
58
|
+
elsif node_is_array
|
59
|
+
dont_clobber ? s[k].unshift( node ) : s[k] = node
|
60
|
+
else
|
61
|
+
s[k] = node
|
62
|
+
end
|
63
|
+
end
|
64
|
+
else
|
65
|
+
s[k] = node
|
66
|
+
end
|
67
|
+
}
|
68
|
+
s
|
69
|
+
end
|
70
|
+
def weave!(h, dont_clobber = true) self.merge! self.weave(h, dont_clobber) end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class ActiveConfig
|
2
|
+
class HashWithHooks < HashWithIndifferentAccess
|
3
|
+
attr_accessor :write_hooks
|
4
|
+
alias_method :regular_writer_hwh, :regular_writer unless method_defined?(:regular_writer_hwh)
|
5
|
+
def write_hooks
|
6
|
+
@write_hooks||=[]
|
7
|
+
end
|
8
|
+
def regular_writer *args
|
9
|
+
write_hooks.each{|p|p.call}
|
10
|
+
regular_writer_hwh(*args)
|
11
|
+
end
|
12
|
+
def add_write_hook func=nil,&block
|
13
|
+
self.write_hooks||=[]
|
14
|
+
self.write_hooks << func if Proc===func
|
15
|
+
self.write_hooks << block if Kernel.block_given?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
class Suffixes
|
19
|
+
attr_writer :priority
|
20
|
+
attr_accessor :ac_instance
|
21
|
+
attr :symbols
|
22
|
+
|
23
|
+
def overlay= new_overlay
|
24
|
+
ac_instance._flush_cache
|
25
|
+
@symbols[:overlay]=(new_overlay.respond_to?(:upcase) ? new_overlay.upcase : new_overlay)
|
26
|
+
end
|
27
|
+
def initialize(*args)
|
28
|
+
ac_instance=args.shift
|
29
|
+
@symbols=HashWithHooks.new
|
30
|
+
@symbols[:hostname]=proc {|sym_table| ENV['ACTIVE_CONFIG_HOSTNAME'] ||
|
31
|
+
Socket.gethostname
|
32
|
+
}
|
33
|
+
@symbols[:hostname_short]=proc {|sym_table| sym_table[:hostname].call(sym_table).sub(/\..*$/, '').freeze}
|
34
|
+
@symbols[:rails_env]=proc { |sym_table|return (RAILS_ENV if defined?(RAILS_ENV))||ENV['RAILS_ENV']}
|
35
|
+
@symbols[:overlay]=proc { |sym_table| ENV['ACTIVE_CONFIG_OVERLAY']}
|
36
|
+
@symbols.add_write_hook do
|
37
|
+
ac_instance.flush_cache
|
38
|
+
end
|
39
|
+
@priority=[
|
40
|
+
nil,
|
41
|
+
:rails_env,
|
42
|
+
[:rails_env,:local],
|
43
|
+
:overlay,
|
44
|
+
[:overlay,:local],
|
45
|
+
[:hostname_short, :local],
|
46
|
+
:hostname,
|
47
|
+
[:hostname, :local],
|
48
|
+
:local,
|
49
|
+
]
|
50
|
+
end
|
51
|
+
def method_missing method, val=nil
|
52
|
+
super if method.to_s=~/^_/
|
53
|
+
if method.to_s=~/^(.*)=$/
|
54
|
+
ac_instance._flush_cache
|
55
|
+
return @symbols[$1]=val
|
56
|
+
end
|
57
|
+
ret=@symbols[method]
|
58
|
+
if ret
|
59
|
+
return ret.call(@symbols) if ret.respond_to?(:call)
|
60
|
+
return ret
|
61
|
+
end
|
62
|
+
super
|
63
|
+
end
|
64
|
+
def for file
|
65
|
+
suffixes.map { |this_suffix| [file,*this_suffix].compact.join('_')}.compact.uniq
|
66
|
+
end
|
67
|
+
def suffixes ary=@priority
|
68
|
+
ary.map{|e|
|
69
|
+
if Array===e
|
70
|
+
t=self.suffixes(e).compact
|
71
|
+
t.size > 0 ? t.join('_') : nil
|
72
|
+
elsif @symbols[e]
|
73
|
+
method_missing(e)
|
74
|
+
else
|
75
|
+
e && e.to_s
|
76
|
+
end
|
77
|
+
}
|
78
|
+
end
|
79
|
+
def file fname
|
80
|
+
suffixes.map{|m|m="#{m}" if m
|
81
|
+
"#{fname}#{m}.yml"
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|