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 +7 -0
- data/Rakefile +46 -0
- data/VERSION.yml +5 -0
- data/activeconfig.gemspec +73 -0
- data/bin/active_config +44 -0
- data/lib/active_config.rb +384 -0
- data/lib/active_config/hash_config.rb +213 -0
- data/lib/active_config/hash_weave.rb +72 -0
- data/lib/active_config/suffixes.rb +86 -0
- data/pkg/activeconfig-0.1.4.gem +0 -0
- data/pkg/activeconfig-0.2.0.gem +0 -0
- data/pkg/activeconfig-0.3.0.gem +0 -0
- data/pkg/activeconfig-0.4.0.gem +0 -0
- data/pkg/activeconfig-0.4.1.gem +0 -0
- data/pkg/activeconfig-0.5.0.gem +0 -0
- data/pkg/activeconfig-0.5.1.gem +0 -0
- data/test/active_config_collision_test.rb +40 -0
- data/test/active_config_multi_test.rb +60 -0
- data/test/active_config_test.rb +435 -0
- data/test/active_config_test/global.yml +2 -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_collision/patha/test.yml +14 -0
- data/test/active_config_test_collision/pathb/test.yml +14 -0
- data/test/active_config_test_collision/pathb/test_local.yml +2 -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/env_test.rb +75 -0
- metadata +118 -0
@@ -0,0 +1,213 @@
|
|
1
|
+
|
2
|
+
class ActiveConfig
|
3
|
+
class HashConfig < Hash
|
4
|
+
def initialize(constructor = {})
|
5
|
+
super()
|
6
|
+
update(constructor)
|
7
|
+
end
|
8
|
+
def default(key = nil)
|
9
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
10
|
+
self[key]
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
17
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
18
|
+
|
19
|
+
# Assigns a new value to the hash:
|
20
|
+
#
|
21
|
+
# hash = HashWithIndifferentAccess.new
|
22
|
+
# hash[:key] = "value"
|
23
|
+
#
|
24
|
+
def []=(key, value)
|
25
|
+
regular_writer(convert_key(key), convert_value(value))
|
26
|
+
end
|
27
|
+
|
28
|
+
# Updates the instantized hash with values from the second:
|
29
|
+
#
|
30
|
+
# hash_1 = HashWithIndifferentAccess.new
|
31
|
+
# hash_1[:key] = "value"
|
32
|
+
#
|
33
|
+
# hash_2 = HashWithIndifferentAccess.new
|
34
|
+
# hash_2[:key] = "New Value!"
|
35
|
+
#
|
36
|
+
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
|
37
|
+
#
|
38
|
+
def update(other_hash)
|
39
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method :merge!, :update
|
44
|
+
|
45
|
+
# Checks the hash for a key matching the argument passed in:
|
46
|
+
#
|
47
|
+
# hash = HashWithIndifferentAccess.new
|
48
|
+
# hash["key"] = "value"
|
49
|
+
# hash.key? :key # => true
|
50
|
+
# hash.key? "key" # => true
|
51
|
+
#
|
52
|
+
def key?(key)
|
53
|
+
super(convert_key(key))
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :include?, :key?
|
57
|
+
alias_method :has_key?, :key?
|
58
|
+
alias_method :member?, :key?
|
59
|
+
|
60
|
+
# Fetches the value for the specified key, same as doing hash[key]
|
61
|
+
def fetch(key, *extras)
|
62
|
+
super(convert_key(key), *extras)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns an array of the values at the specified indices:
|
66
|
+
#
|
67
|
+
# hash = HashWithIndifferentAccess.new
|
68
|
+
# hash[:a] = "x"
|
69
|
+
# hash[:b] = "y"
|
70
|
+
# hash.values_at("a", "b") # => ["x", "y"]
|
71
|
+
#
|
72
|
+
def values_at(*indices)
|
73
|
+
indices.collect {|key| self[convert_key(key)]}
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns an exact copy of the hash.
|
77
|
+
def dup
|
78
|
+
self.class.new(self)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
|
82
|
+
# Does not overwrite the existing hash.
|
83
|
+
def merge(hash)
|
84
|
+
self.dup.update(hash)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Removes a specified key from the hash.
|
88
|
+
def delete(key)
|
89
|
+
super(convert_key(key))
|
90
|
+
end
|
91
|
+
|
92
|
+
# Convert to a Hash with String keys.
|
93
|
+
def to_hash
|
94
|
+
Hash.new(default).merge(self)
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# HashWithIndifferentAccess#dup always returns HashWithIndifferentAccess!
|
99
|
+
# -- kurt 2007/10/18
|
100
|
+
def dup
|
101
|
+
self.class.new(self)
|
102
|
+
end
|
103
|
+
|
104
|
+
def self._make_indifferent_and_freeze(x)
|
105
|
+
_make_indifferent(x,:freeze => true)
|
106
|
+
end
|
107
|
+
def freeze!
|
108
|
+
return false if self.frozen?
|
109
|
+
self.each_pair do | k, v |
|
110
|
+
self[self.class.recursive_freeze(k)] = self.class.recursive_freeze(v)
|
111
|
+
end
|
112
|
+
self.freeze
|
113
|
+
self
|
114
|
+
end
|
115
|
+
def self.recursive_freeze x
|
116
|
+
return x if x.frozen?
|
117
|
+
case x
|
118
|
+
when HashConfig,Hash
|
119
|
+
x.each_pair do | k, v |
|
120
|
+
x[recursive_freeze(k)] = recursive_freeze(v)
|
121
|
+
end
|
122
|
+
when Array
|
123
|
+
x.collect! {|v|freeze(v)}
|
124
|
+
end
|
125
|
+
x.freeze
|
126
|
+
end
|
127
|
+
def self._make_indifferent(x,opts={})
|
128
|
+
return x if opts[:freeze] and x.frozen?
|
129
|
+
case x
|
130
|
+
when HashConfig
|
131
|
+
x.each_pair do | k, v |
|
132
|
+
x[k.freeze] = _make_indifferent(v,opts)
|
133
|
+
end
|
134
|
+
when Hash
|
135
|
+
x = HashConfig.new.merge(x)
|
136
|
+
x.each_pair do | k, v |
|
137
|
+
x[k.freeze] = _make_indifferent(v,opts)
|
138
|
+
end
|
139
|
+
# STDERR.puts "x = #{x.inspect}:#{x.class}"
|
140
|
+
when Array
|
141
|
+
x.collect! do | v |
|
142
|
+
_make_indifferent(v,opts)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
x.freeze if opts[:freeze]
|
147
|
+
x
|
148
|
+
end
|
149
|
+
|
150
|
+
# dotted notation can now be used with arguments (useful for creating mock objects)
|
151
|
+
# in the YAML file the method name is a key (just like usual), argument(s)
|
152
|
+
# form a nested key, and the value will get returned.
|
153
|
+
#
|
154
|
+
# For example loading to variable foo a yaml file that looks like:
|
155
|
+
# customer:
|
156
|
+
# id: 12345678
|
157
|
+
# verified:
|
158
|
+
# phone: verified
|
159
|
+
# :address: info_not_available
|
160
|
+
# ? [name, employer]
|
161
|
+
# : not_verified
|
162
|
+
#
|
163
|
+
# Allows the following calls:
|
164
|
+
# foo.customer.id --> 12345678
|
165
|
+
# foo.customer.verified.phone --> verified
|
166
|
+
# foo.customer.verified("phone") --> verified
|
167
|
+
# foo.customer.verified("name", "employer") --> not_verified
|
168
|
+
# foo.customer.verified(:address) --> info_not_available
|
169
|
+
#
|
170
|
+
# Note that :address is specified as a symbol, where phone is just a string.
|
171
|
+
# Depending on what kind of parameter the method being mocked out is going
|
172
|
+
# to be called with, define in the YAML file either a string or a symbol.
|
173
|
+
# This also works inside the composite array keys.
|
174
|
+
def method_missing(method, *args)
|
175
|
+
method = method.to_s
|
176
|
+
if args.size==1 and method.to_s=~/=$/
|
177
|
+
return self[method.to_s.sub(/=$/,'')]=args[0]
|
178
|
+
end
|
179
|
+
args.inject(self[method]){|s,e|s and s[e]}
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
# Why the &*#^@*^&$ isn't HashWithIndifferentAccess actually doing this?
|
184
|
+
#
|
185
|
+
def [](key)
|
186
|
+
key = key.to_s if key.kind_of?(Symbol)
|
187
|
+
super(key)
|
188
|
+
end
|
189
|
+
|
190
|
+
# HashWithIndifferentAccess#default is broken!
|
191
|
+
define_method(:default_Hash, Hash.instance_method(:default))
|
192
|
+
|
193
|
+
##
|
194
|
+
# Allow hash.default => hash['default']
|
195
|
+
# without breaking Hash's usage of default(key)
|
196
|
+
#
|
197
|
+
@@no_key = [ :no_key ] # magically unique value.
|
198
|
+
def default(key = @@no_key)
|
199
|
+
key = key.to_s if key.is_a?(Symbol)
|
200
|
+
key == @@no_key ? self['default'] : default_Hash(key == @@no_key ? nil : key)
|
201
|
+
end
|
202
|
+
|
203
|
+
protected
|
204
|
+
def convert_key(key)
|
205
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
206
|
+
end
|
207
|
+
|
208
|
+
def convert_value(value)
|
209
|
+
return value
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
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,86 @@
|
|
1
|
+
class ActiveConfig
|
2
|
+
class HashWithHooks < HashConfig
|
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| (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],
|
46
|
+
[:hostname_short, :local],
|
47
|
+
:hostname,
|
48
|
+
[:hostname, :local],
|
49
|
+
:local,
|
50
|
+
]
|
51
|
+
end
|
52
|
+
def method_missing method, val=nil
|
53
|
+
super if method.to_s=~/^_/
|
54
|
+
if method.to_s=~/^(.*)=$/
|
55
|
+
ac_instance._flush_cache
|
56
|
+
return @symbols[$1]=val
|
57
|
+
end
|
58
|
+
ret=@symbols[method]
|
59
|
+
if ret
|
60
|
+
return ret.call(@symbols) if ret.respond_to?(:call)
|
61
|
+
return ret
|
62
|
+
end
|
63
|
+
super
|
64
|
+
end
|
65
|
+
def for file
|
66
|
+
suffixes.map { |this_suffix| [file,*this_suffix].compact.join('_')}.compact.uniq
|
67
|
+
end
|
68
|
+
def suffixes ary=@priority
|
69
|
+
ary.map{|e|
|
70
|
+
if Array===e
|
71
|
+
t=self.suffixes(e).compact
|
72
|
+
t.size > 0 ? t.join('_') : nil
|
73
|
+
elsif @symbols[e]
|
74
|
+
method_missing(e)
|
75
|
+
else
|
76
|
+
e && e.to_s
|
77
|
+
end
|
78
|
+
}
|
79
|
+
end
|
80
|
+
def file fname
|
81
|
+
suffixes.map{|m|m="#{m}" if m
|
82
|
+
"#{fname}#{m}.yml"
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift File.expand_path("../../lib",__FILE__)
|
3
|
+
ENV['ACTIVE_CONFIG_PATH']=[File.expand_path("../active_config_test_collision/patha",__FILE__),File.expand_path("../active_config_test_collision/pathb",__FILE__)].join(':')
|
4
|
+
|
5
|
+
|
6
|
+
# even if a gem is installed, load cnu_config and active_config locally
|
7
|
+
dir = File.dirname __FILE__
|
8
|
+
$LOAD_PATH.unshift File.join(dir, "..", "lib")
|
9
|
+
|
10
|
+
# Configure ActiveConfig to use our test config files.
|
11
|
+
RAILS_ENV = 'development'
|
12
|
+
ENV.delete('ACTIVE_CONFIG_OVERLAY') # Avoid gb magic.
|
13
|
+
|
14
|
+
# Test environment.
|
15
|
+
require 'rubygems'
|
16
|
+
|
17
|
+
# Test target
|
18
|
+
require 'active_config'
|
19
|
+
|
20
|
+
# Test dependencies
|
21
|
+
require 'test/unit'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
class ActiveConfig::TestMulti < Test::Unit::TestCase
|
28
|
+
|
29
|
+
|
30
|
+
def teardown
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_collision
|
35
|
+
assert_raise ActiveConfig::DuplicateConfig do
|
36
|
+
ActiveConfig.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift File.expand_path("../../lib",__FILE__)
|
3
|
+
ENV['ACTIVE_CONFIG_PATH']=[File.expand_path("../active_config_test_multi/patha",__FILE__),File.expand_path("../active_config_test_multi/pathb",__FILE__)].join(':')
|
4
|
+
|
5
|
+
# TEST_CONFIG_BEGIN
|
6
|
+
# enabled: true
|
7
|
+
# TEST_CONFIG_END
|
8
|
+
|
9
|
+
# Test target dependencies
|
10
|
+
|
11
|
+
# even if a gem is installed, load cnu_config and active_config locally
|
12
|
+
dir = File.dirname __FILE__
|
13
|
+
$LOAD_PATH.unshift File.join(dir, "..", "lib")
|
14
|
+
|
15
|
+
# Configure ActiveConfig to use our test config files.
|
16
|
+
RAILS_ENV = 'development'
|
17
|
+
ENV.delete('ACTIVE_CONFIG_OVERLAY') # Avoid gb magic.
|
18
|
+
|
19
|
+
# Test environment.
|
20
|
+
require 'rubygems'
|
21
|
+
|
22
|
+
# Test target
|
23
|
+
require 'active_config'
|
24
|
+
|
25
|
+
# Test dependencies
|
26
|
+
require 'test/unit'
|
27
|
+
require 'fileutils' # FileUtils.touch
|
28
|
+
require 'benchmark'
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
class ActiveConfig::TestMulti < Test::Unit::TestCase
|
35
|
+
def active_config
|
36
|
+
@active_config||=ActiveConfig.new
|
37
|
+
end
|
38
|
+
def setup
|
39
|
+
super
|
40
|
+
begin
|
41
|
+
active_config._flush_cache
|
42
|
+
active_config._verbose = nil # default
|
43
|
+
active_config.reload(true)
|
44
|
+
active_config._reload_disabled = nil # default
|
45
|
+
active_config._reload_delay = nil # default
|
46
|
+
rescue => err
|
47
|
+
# NOTHING
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def teardown
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_multi
|
57
|
+
assert_equal "WIN", active_config.test.default
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|