cnuconfig 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +19 -0
- data/README.txt +3 -0
- data/Rakefile +94 -0
- data/lib/cnuconfig.rb +498 -0
- data/lib/cnuconfig/version.rb +9 -0
- data/lib/cnuhash.rb +169 -0
- data/lib/indifferent_access.rb +88 -0
- data/scripts/txt2html +67 -0
- data/setup.rb +1585 -0
- data/test/test_cnuconfig.rb +246 -0
- data/test/test_cnuconfig/global.yml +787 -0
- data/test/test_cnuconfig/test.yml +14 -0
- data/test/test_cnuconfig/test_local.yml +9 -0
- data/website/index.html +11 -0
- data/website/index.txt +30 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +129 -0
- data/website/template.rhtml +48 -0
- metadata +68 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.txt
|
4
|
+
Rakefile
|
5
|
+
lib/cnuconfig.rb
|
6
|
+
lib/cnuhash.rb
|
7
|
+
lib/indifferent_access.rb
|
8
|
+
lib/cnuconfig/version.rb
|
9
|
+
scripts/txt2html
|
10
|
+
setup.rb
|
11
|
+
test/test_cnuconfig.rb
|
12
|
+
test/test_cnuconfig/global.yml
|
13
|
+
test/test_cnuconfig/test.yml
|
14
|
+
test/test_cnuconfig/test_local.yml
|
15
|
+
website/index.html
|
16
|
+
website/index.txt
|
17
|
+
website/javascripts/rounded_corners_lite.inc.js
|
18
|
+
website/stylesheets/screen.css
|
19
|
+
website/template.rhtml
|
data/README.txt
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'hoe'
|
11
|
+
include FileUtils
|
12
|
+
require File.join(File.dirname(__FILE__), 'lib', 'cnuconfig', 'version')
|
13
|
+
|
14
|
+
AUTHOR = 'Alex Skryl'
|
15
|
+
EMAIL = "askryl@cashnetusa.com"
|
16
|
+
DESCRIPTION = "Allows for intelligent overlay YAML configuration files as well as an intuitive mechanism for accessing YAML config data through dottable notation"
|
17
|
+
GEM_NAME = 'cnuconfig'
|
18
|
+
RUBYFORGE_PROJECT = 'cnuconfig'
|
19
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
|
+
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
21
|
+
|
22
|
+
NAME = "cnuconfig"
|
23
|
+
REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
24
|
+
VERS = CnuConfig::VERSION::STRING + (REV ? ".#{REV}" : "")
|
25
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
|
26
|
+
RDOC_OPTS = ['--quiet', '--title', 'cnuconfig documentation',
|
27
|
+
"--opname", "index.html",
|
28
|
+
"--line-numbers",
|
29
|
+
"--main", "README",
|
30
|
+
"--inline-source"]
|
31
|
+
|
32
|
+
class Hoe
|
33
|
+
def extra_deps
|
34
|
+
@extra_deps.reject { |x| Array(x).first == 'hoe' }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Generate all the Rake tasks
|
39
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
40
|
+
|
41
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
42
|
+
p.author = AUTHOR
|
43
|
+
p.description = DESCRIPTION
|
44
|
+
p.email = EMAIL
|
45
|
+
p.summary = DESCRIPTION
|
46
|
+
p.url = HOMEPATH
|
47
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
48
|
+
p.test_globs = ["test/**/test_*.rb"]
|
49
|
+
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
50
|
+
|
51
|
+
# == Optional
|
52
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
53
|
+
#p.extra_deps = [['activesupport']]
|
54
|
+
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
desc 'Generate website files'
|
59
|
+
task :website_generate do
|
60
|
+
Dir['website/**/*.txt'].each do |txt|
|
61
|
+
sh %{ ruby scripts/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
desc 'Upload website files to rubyforge'
|
66
|
+
task :website_upload do
|
67
|
+
config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
|
68
|
+
host = "#{config["username"]}@rubyforge.org"
|
69
|
+
remote_dir = "/var/www/gforge-projects/#{RUBYFORGE_PROJECT}/"
|
70
|
+
# remote_dir = "/var/www/gforge-projects/#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
|
71
|
+
local_dir = 'website'
|
72
|
+
sh %{rsync -av #{local_dir}/ #{host}:#{remote_dir}}
|
73
|
+
end
|
74
|
+
|
75
|
+
desc 'Generate and upload website files'
|
76
|
+
task :website => [:website_generate, :website_upload]
|
77
|
+
|
78
|
+
desc 'Release the website and new gem version'
|
79
|
+
task :deploy => [:check_version, :website, :release]
|
80
|
+
|
81
|
+
desc 'Runs tasks website_generate and install_gem as a local deployment of the gem'
|
82
|
+
task :local_deploy => [:website_generate, :install_gem]
|
83
|
+
|
84
|
+
task :check_version do
|
85
|
+
unless ENV['VERSION']
|
86
|
+
puts 'Must pass a VERSION=x.y.z release version'
|
87
|
+
exit
|
88
|
+
end
|
89
|
+
unless ENV['VERSION'] == VERS
|
90
|
+
puts "Please update your version.rb to match the release version, currently #{VERS}"
|
91
|
+
exit
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
data/lib/cnuconfig.rb
ADDED
@@ -0,0 +1,498 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'yaml'
|
3
|
+
require 'cnuhash'
|
4
|
+
require 'indifferent_access' #from active_support
|
5
|
+
#
|
6
|
+
#See LICENSE.txt for details
|
7
|
+
#
|
8
|
+
#=CnuConfig
|
9
|
+
#
|
10
|
+
# * Provides dottable, hash, array, and argument access to YAML configuration files
|
11
|
+
# * Implements multilevel caching to reduce disk accesses
|
12
|
+
# * Overlays multiple configuration files in an intelligent manner
|
13
|
+
#
|
14
|
+
#Config file access example:
|
15
|
+
# Given a configuration file named test.yaml and test_local.yaml
|
16
|
+
# test.yaml:
|
17
|
+
# ...
|
18
|
+
# hash_1:
|
19
|
+
# foo: "foo"
|
20
|
+
# bar: "bar"
|
21
|
+
# bok: "bok"
|
22
|
+
# ...
|
23
|
+
# test_local.yaml:
|
24
|
+
# ...
|
25
|
+
# hash_1:
|
26
|
+
# foo: "foo"
|
27
|
+
# bar: "baz"
|
28
|
+
# zzz: "zzz"
|
29
|
+
# ...
|
30
|
+
#
|
31
|
+
# irb(main):012:0> CnuConfig.test
|
32
|
+
# => {"array_1"=>["a", "b", "c", "d"], "perform_caching"=>true,
|
33
|
+
# "default"=>"yo!", "lazy"=>true, "hash_1"=>{"zzz"=>"zzz", "foo"=>"foo",
|
34
|
+
# "bok"=>"bok", "bar"=>"baz"}, "secure_login"=>true, "test_mode"=>true}
|
35
|
+
#
|
36
|
+
# --Notice that the hash produced is the result of merging the above
|
37
|
+
# config files in a particular order
|
38
|
+
#
|
39
|
+
# The overlay order of the config files is defined by SUFFIXES:
|
40
|
+
# * nil
|
41
|
+
# * _local
|
42
|
+
# * _config
|
43
|
+
# * _local_config
|
44
|
+
# ...
|
45
|
+
#
|
46
|
+
# ----------------------------------------------------------------------------
|
47
|
+
# irb(main):021:0> CnuConfig.test_local
|
48
|
+
# => {"hash_1"=>{"zzz"=>"zzz", "foo"=>"foo", "bar"=>"baz"}, "test_mode"=>true}
|
49
|
+
#
|
50
|
+
class CnuConfig
|
51
|
+
# include DogTag # Not needed?
|
52
|
+
|
53
|
+
unless defined? SUFFIXES
|
54
|
+
SUFFIXES = [nil, :local, :config, :local_config]
|
55
|
+
if defined? RAILS_ENV
|
56
|
+
SUFFIXES << RAILS_ENV
|
57
|
+
SUFFIXES << [RAILS_ENV, :local]
|
58
|
+
end
|
59
|
+
SUFFIXES << Socket.gethostname
|
60
|
+
SUFFIXES << [Socket.gethostname, :config_local]
|
61
|
+
end
|
62
|
+
|
63
|
+
@@cache = {}
|
64
|
+
@@cache_files = {}
|
65
|
+
@@cache_hash = { }
|
66
|
+
@@cache_config_files = { } # Keep around incase reload_disabled.
|
67
|
+
@@last_auto_check = { }
|
68
|
+
|
69
|
+
@@reload_disabled = false
|
70
|
+
def self._reload_disabled=(x); @@reload_disabled = x.nil? ? false : x; end
|
71
|
+
|
72
|
+
@@reload_delay = 300
|
73
|
+
# The number of seconds between reloading of config files
|
74
|
+
# and automatic reload checks.
|
75
|
+
def self._reload_delay=(x); @@reload_delay = x || 300; end
|
76
|
+
|
77
|
+
@@verbose = false
|
78
|
+
def self._verbose=(x); @@verbose = x; end
|
79
|
+
|
80
|
+
@@config_file_loaded = nil
|
81
|
+
def self._config_file_loaded=(x); @@config_file_loaded = x; end
|
82
|
+
def self._config_file_loaded; @@config_file_loaded; end
|
83
|
+
|
84
|
+
# Get each config file's yaml hash for the given config name,
|
85
|
+
# to be merged later.
|
86
|
+
def self.load_config_files(name, force=false)
|
87
|
+
name = name.to_s # if name.is_a?(Symbol)
|
88
|
+
|
89
|
+
# Return last config file hash list loaded,
|
90
|
+
# if reload is disabled and files have already been loaded.
|
91
|
+
return @@cache_config_files[name] if
|
92
|
+
@@reload_disabled &&
|
93
|
+
@@cache_config_files[name]
|
94
|
+
|
95
|
+
now = Time.now
|
96
|
+
config_files = self._get_config_files(name)
|
97
|
+
|
98
|
+
# STDERR.puts "load_config_files(#{name.inspect})"
|
99
|
+
|
100
|
+
hashes = config_files.collect do |f|
|
101
|
+
name, name_x, filename, mtime = *f
|
102
|
+
|
103
|
+
val, last_mtime, last_loaded = @@cache[filename]
|
104
|
+
|
105
|
+
# Reload the file if its been more than 5 minutes
|
106
|
+
# since last load attempt.
|
107
|
+
if val == nil || now - last_loaded > @@reload_delay
|
108
|
+
if force || val == nil || mtime != last_mtime
|
109
|
+
# mtime is nil if file does not exist.
|
110
|
+
if mtime
|
111
|
+
File.open( filename ) do | yf |
|
112
|
+
STDERR.puts "\nCnuConfig: loading #{filename.inspect}" if @@verbose
|
113
|
+
val = YAML::load(yf).freeze
|
114
|
+
# STDERR.puts "CnuConfig: loaded #{filename.inspect} => #{val.inspect}"
|
115
|
+
(@@config_file_loaded ||= { })[name] = config_files
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Save cached config file contents, and mtime.
|
120
|
+
@@cache[filename] = [ val, mtime, now ]
|
121
|
+
|
122
|
+
# Flush merged hash cache.
|
123
|
+
@@cache_hash[name] = nil
|
124
|
+
|
125
|
+
# Config files changed or disappeared.
|
126
|
+
@@cache_files[name] = config_files
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
val
|
132
|
+
end
|
133
|
+
hashes.compact!
|
134
|
+
|
135
|
+
# STDERR.puts "load_config_files(#{name.inspect}) => #{hashes.inspect}"
|
136
|
+
|
137
|
+
# Keep last loaded config files around in case @@reload_dsabled.
|
138
|
+
@@cache_config_files[name] = hashes
|
139
|
+
|
140
|
+
hashes
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# Returns a list of all relavant config files as specified
|
145
|
+
# by SUFFIXES list.
|
146
|
+
# Each element is an Array, containing:
|
147
|
+
# [ "the-top-level-config-name",
|
148
|
+
# "the-suffixed-config-name",
|
149
|
+
# "/the/absolute/path/to/yaml.yml",
|
150
|
+
# # The mtime of the yml file or nil,
|
151
|
+
# ]
|
152
|
+
def self._get_config_files(name)
|
153
|
+
# alexg: splatting *suffix allows us to deal with multipart suffixes
|
154
|
+
SUFFIXES.map{|suffix| [name, *suffix].compact.join('_')}.collect do |name_x|
|
155
|
+
filename = File.expand_path(filename_for_name(name_x))
|
156
|
+
[ name,
|
157
|
+
name_x,
|
158
|
+
filename,
|
159
|
+
File.exist?(filename) ? File.stat(filename).mtime : nil,
|
160
|
+
]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
def self._config_files(name)
|
166
|
+
@@cache_files[name] ||= _get_config_files(name)
|
167
|
+
end
|
168
|
+
|
169
|
+
#Checks whether a config file has been changed and reloads
|
170
|
+
#it in case it has
|
171
|
+
def self.config_changed?(name)
|
172
|
+
# STDERR.puts "config_changed?(#{name.inspect})"
|
173
|
+
name = name.to_s # if name.is_a?(Symbol)
|
174
|
+
! (@@cache_files[name] === _get_config_files(name))
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
|
179
|
+
# Get the merged config hash for the named file.
|
180
|
+
def self.config_hash(name)
|
181
|
+
name = name.to_s # if name.is_a?(Symbol)
|
182
|
+
_config_hash(name)
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
# Returns a cached indifferent access faker hash merged
|
187
|
+
# from all config files for a name.
|
188
|
+
def self._config_hash(name)
|
189
|
+
# STDERR.puts "_config_hash(#{name.inspect})"; result =
|
190
|
+
@@cache_hash[name] ||=
|
191
|
+
_make_indifferent(
|
192
|
+
_merge_hashes(
|
193
|
+
load_config_files(name)))
|
194
|
+
# STDERR.puts "_config_hash(#{name.inspect}) => #{result.inspect}"; result
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
# If config files have changed, caches are flushed.
|
199
|
+
def self.check_config_changed(name = nil)
|
200
|
+
# STDERR.puts "check_config_changed(#{name.inspect})"
|
201
|
+
if name == nil
|
202
|
+
@@cache_hash.keys.each do | name |
|
203
|
+
check_config_changed(name)
|
204
|
+
end
|
205
|
+
else
|
206
|
+
name = name.to_s # if name.is_a?(Symbol)
|
207
|
+
# STDERR.puts "CnuConfig: config changed? #{name.inspect} reload_disabled = #{@@reload_disabled}" if @@verbose
|
208
|
+
if config_changed?(name) && ! @@reload_disabled
|
209
|
+
STDERR.puts "CnuConfig: config changed #{name.inspect}, @@reload_disabled = #{@@reload_disabled}" if @@verbose
|
210
|
+
@@cache_hash[name] = nil
|
211
|
+
end
|
212
|
+
end
|
213
|
+
# STDERR.puts "check_config_changed(#{name.inspect}) =>"
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
# Returns a merge of hashes.
|
218
|
+
def self._merge_hashes(hashes)
|
219
|
+
hashes.inject({ }) { | n, h | n.weave(h, false) }
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
# Recursively makes hashes into frozen IndifferentAccess ConfigFakerHash
|
224
|
+
# Arrays are also traversed and frozen.
|
225
|
+
def self._make_indifferent(x)
|
226
|
+
case x
|
227
|
+
when Hash
|
228
|
+
unless x.frozen?
|
229
|
+
x.each_pair do | k, v |
|
230
|
+
x[k] = _make_indifferent(v)
|
231
|
+
end
|
232
|
+
x = HashConfig.new.merge!(x).freeze
|
233
|
+
end
|
234
|
+
# STDERR.puts "x = #{x.inspect}:#{x.class}"
|
235
|
+
when Array
|
236
|
+
unless x.frozen?
|
237
|
+
x.collect! do | v |
|
238
|
+
_make_indifferent(v)
|
239
|
+
end
|
240
|
+
x.freeze
|
241
|
+
end
|
242
|
+
end
|
243
|
+
x
|
244
|
+
end
|
245
|
+
|
246
|
+
#Calling CnuConfig without an argument gets values from the 'global' configuration file
|
247
|
+
#if one exists:
|
248
|
+
# irb(main):031:0> CnuConfig[:geocoder_check]
|
249
|
+
# => {"url"=>["http://geocoder.us/service/soap", "http://rpc.geocoder.us/Geo/Coder/US"]}
|
250
|
+
#
|
251
|
+
def self.[](key, file=:global)
|
252
|
+
self.get_config_file(file)[key]
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
def self.with_file(name, *args)
|
257
|
+
# STDERR.puts "with_file(#{name.inspect}, #{args.inspect})"; result =
|
258
|
+
args.inject(get_config_file(name)) { | v, i |
|
259
|
+
# STDERR.puts "v = #{v.inspect}, i = #{i.inspect}"
|
260
|
+
case v
|
261
|
+
when Hash
|
262
|
+
v[i.to_s]
|
263
|
+
when Array
|
264
|
+
i.is_a?(Integer) ? v[i] : nil
|
265
|
+
else
|
266
|
+
nil
|
267
|
+
end
|
268
|
+
}
|
269
|
+
# STDERR.puts "with_file(#{name.inspect}, #{args.inspect}) => #{result.inspect}"; result
|
270
|
+
end
|
271
|
+
|
272
|
+
# Get the merged config hash.
|
273
|
+
# Will auto check every 5 minutes, for longer running apps.
|
274
|
+
def self.get_config_file(name)
|
275
|
+
# STDERR.puts "get_config_file(#{name.inspect})"
|
276
|
+
name = name.to_s # if name.is_a?(Symbol)
|
277
|
+
now = Time.now
|
278
|
+
if (! @@last_auto_check[name]) || (now - @@last_auto_check[name]) > @@reload_delay
|
279
|
+
@@last_auto_check[name] = now
|
280
|
+
check_config_changed(name)
|
281
|
+
end
|
282
|
+
# result =
|
283
|
+
_config_hash(name)
|
284
|
+
# STDERR.puts "get_config_file(#{name.inspect}) => #{result.inspect}"; result
|
285
|
+
end
|
286
|
+
|
287
|
+
|
288
|
+
def self.with_file_sym(file, *args)
|
289
|
+
with_file(file, *args)
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
def self.global_sym(*args)
|
294
|
+
with_file('global', *args)
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
# Unused?
|
299
|
+
def self.can_write_file?(name)
|
300
|
+
filename = filename_for_name(name)
|
301
|
+
!File.exists?(filename) || File.writable?(filename)
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
# Unused?
|
306
|
+
def self.write_file(name, obj=nil)
|
307
|
+
raise exception([:writing_empty_file, name]) unless obj
|
308
|
+
File.open(filename_for_name(name), 'w') do |out|
|
309
|
+
YAML.dump(obj, out)
|
310
|
+
end
|
311
|
+
load_config_files(name, true)
|
312
|
+
end
|
313
|
+
|
314
|
+
|
315
|
+
# Disables any reloading of config,
|
316
|
+
# executes &block,
|
317
|
+
# calls check_config_changed,
|
318
|
+
# returns result of block
|
319
|
+
def self.disable_reload(&block)
|
320
|
+
result = nil
|
321
|
+
reload_disabled_save = @@reload_disabled
|
322
|
+
begin
|
323
|
+
@@reload_disabled = true
|
324
|
+
result = yield
|
325
|
+
ensure
|
326
|
+
@@reload_disabled = reload_disabled_save
|
327
|
+
check_config_changed unless @@reload_disabled
|
328
|
+
end
|
329
|
+
result
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
# Unused?
|
334
|
+
def self.rec_merge(hasha, hashb)
|
335
|
+
raise(:deprecated)
|
336
|
+
hasha = {}.merge(hasha || {})
|
337
|
+
hashb = {}.merge(hashb || {})
|
338
|
+
return hashb if hasha.empty?
|
339
|
+
return hasha if hashb.empty?
|
340
|
+
hasha.each_pair{|k,v|
|
341
|
+
if v.is_a?(Hash)
|
342
|
+
hasha[k]=rec_merge(v,hashb[k])
|
343
|
+
else
|
344
|
+
hasha[k]=hashb[k] || v
|
345
|
+
end
|
346
|
+
}
|
347
|
+
hasha
|
348
|
+
end
|
349
|
+
|
350
|
+
# Unused?
|
351
|
+
def self.recursive_hash_merge(hashes, *path)
|
352
|
+
raise(:deprecated)
|
353
|
+
hashes = hashes.compact
|
354
|
+
case
|
355
|
+
when path.empty?
|
356
|
+
#this will merge in hashes recursively (its pretty expensive)
|
357
|
+
hashes.inject({}){|retval, hash| retval.weave(hash, false) }#rec_merge(retval,hash)}
|
358
|
+
when hashes.empty?
|
359
|
+
nil
|
360
|
+
else
|
361
|
+
a = recursive_hash_merge(hashes.last(hashes.size-1), *path)
|
362
|
+
b = path.inject(hashes.first) do |cfg, key|
|
363
|
+
case cfg
|
364
|
+
when Hash then cfg[key.to_s]
|
365
|
+
when Array then key.is_a?(Integer) ? cfg[key] : nil
|
366
|
+
else nil
|
367
|
+
end
|
368
|
+
end
|
369
|
+
a.is_a?(Hash) && b.is_a?(Hash) ? a.weave(b, false) : (a || b) #rec_merge(a,b) : (a || b)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
|
374
|
+
# Creates a dottable hash for all Hash objects, recursively.
|
375
|
+
def self.create_dottable_hash(value)
|
376
|
+
_make_indifferent(value)
|
377
|
+
end
|
378
|
+
|
379
|
+
#This allows us to create faker methods out of the structures inside the
|
380
|
+
#config files to give us dottable access
|
381
|
+
def self.method_missing(method, *args)
|
382
|
+
# STDERR.puts "#{self}.method_missing(#{method.inspect}, #{args.inspect})"
|
383
|
+
value = with_file(method, *args)
|
384
|
+
# STDERR.puts "#{self}.method_missing(#{method.inspect}, #{args.inspect}) => #{value.inspect}"
|
385
|
+
value
|
386
|
+
end
|
387
|
+
|
388
|
+
#Forces the reloading of all caches (only for testing)
|
389
|
+
def self.reload(force = false)
|
390
|
+
if force || ! @@reload_disabled
|
391
|
+
return unless ['development', 'integration'].include?(RAILS_ENV)
|
392
|
+
@@cache = { }
|
393
|
+
@@cache_files = { }
|
394
|
+
@@cache_hash = { }
|
395
|
+
@@last_auto_check = { }
|
396
|
+
end
|
397
|
+
nil
|
398
|
+
end
|
399
|
+
|
400
|
+
protected
|
401
|
+
|
402
|
+
def self.filename_for_name(name, suffix='')
|
403
|
+
File.join(CONFIG_ROOT, name.to_s + suffix.to_s + '.yml')
|
404
|
+
end
|
405
|
+
|
406
|
+
|
407
|
+
class HashConfig < HashWithIndifferentAccess
|
408
|
+
# dotted notation can now be used with arguments (useful for creating mock objects)
|
409
|
+
# in the YAML file the method name is a key (just like usual), argument(s)
|
410
|
+
# form a nested key, and the value will get returned.
|
411
|
+
#
|
412
|
+
# For example loading to variable foo a yaml file that looks like:
|
413
|
+
# customer:
|
414
|
+
# id: 12345678
|
415
|
+
# verified:
|
416
|
+
# phone: verified
|
417
|
+
# address: info_not_available
|
418
|
+
# ? [name, employer]
|
419
|
+
# : not_verified
|
420
|
+
#
|
421
|
+
# Allows the following calls:
|
422
|
+
# foo.customer.id --> 12345678
|
423
|
+
# foo.customer.verified.phone --> verified
|
424
|
+
# foo.customer.verified("phone") --> verified
|
425
|
+
# foo.customer.verified("name", "employer") --> not_verified
|
426
|
+
# foo.customer.verified(:address) --> info_not_available
|
427
|
+
#
|
428
|
+
# Note that :address is specified as a symbol, where phone is just a string.
|
429
|
+
# Depending on what kind of parameter the method being mocked out is going
|
430
|
+
# to be called with, define in the YAML file either a string or a symbol.
|
431
|
+
# This also works inside the composite array keys.
|
432
|
+
def method_missing(method, *args)
|
433
|
+
method = method.to_s
|
434
|
+
# STDERR.puts "CHF#method_missing(#{method.inspect}, #{args.inspect}) on #{self.inspect}:#{self.class}" if method == 'dup'
|
435
|
+
value = self[method]
|
436
|
+
case args.size
|
437
|
+
when 0:
|
438
|
+
;
|
439
|
+
when 1:
|
440
|
+
# value = CnuConfig.create_dottable_hash(self[method]).send(args[0])
|
441
|
+
value = value.send(args[0])
|
442
|
+
else
|
443
|
+
value = value[args]
|
444
|
+
end
|
445
|
+
# value = convert_value(value)
|
446
|
+
value
|
447
|
+
end
|
448
|
+
|
449
|
+
|
450
|
+
def [](key)
|
451
|
+
key = key.to_s if key.kind_of?(Symbol)
|
452
|
+
super(key)
|
453
|
+
end
|
454
|
+
|
455
|
+
|
456
|
+
# HashWithIndifferentAccess#default is broken!
|
457
|
+
define_method(:default_Hash, Hash.instance_method(:default))
|
458
|
+
|
459
|
+
# Allow hash.default => hash['default']
|
460
|
+
# without breaking Hash's usage of default(key)
|
461
|
+
@@no_key = [ :no_key ] # magically unique value.
|
462
|
+
def default(key = @@no_key)
|
463
|
+
key = key.to_s if key.is_a?(Symbol)
|
464
|
+
key == @@no_key ? self['default'] : default_Hash(key == @@no_key ? nil : key)
|
465
|
+
end
|
466
|
+
|
467
|
+
|
468
|
+
# HashWithIndifferentAccess#update is broken!
|
469
|
+
# This took way too long to figure this out:
|
470
|
+
#
|
471
|
+
# Hash#update returns self,
|
472
|
+
# BUT,
|
473
|
+
# HashWithIndifferentAccess#update does not!
|
474
|
+
#
|
475
|
+
# { :a => 1 }.update({ :b => 2, :c => 3 })
|
476
|
+
# => { :a => 1, :b => 2, :c => 3 }
|
477
|
+
#
|
478
|
+
# HashWithIndifferentAccess.new({ :a => 1 }).update({ :b => 2, :c => 3 })
|
479
|
+
# => { :b => 2, :c => 3 } # WTF?
|
480
|
+
#
|
481
|
+
# Subclasses should *never* override methods and break their protocols!!!!
|
482
|
+
#
|
483
|
+
def update(hash)
|
484
|
+
super(hash)
|
485
|
+
self
|
486
|
+
end
|
487
|
+
|
488
|
+
|
489
|
+
# Override WithIndifferentAccess#convert_value
|
490
|
+
# return instances of this class for Hash values.
|
491
|
+
def convert_value(value)
|
492
|
+
# STDERR.puts "convert_value(#{value.inspect}:#{value.class})"
|
493
|
+
value.class == Hash ? self.class.new(value).freeze : value
|
494
|
+
end
|
495
|
+
|
496
|
+
end
|
497
|
+
|
498
|
+
end
|