rconfig 0.3.3 → 0.4.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/.gitignore +17 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/ChangeLog +50 -0
- data/Credits +13 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +30 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +0 -0
- data/Rakefile +11 -0
- data/demo/application.conf +3 -0
- data/demo/demo.conf +13 -0
- data/demo/demo.rb +14 -0
- data/demo/demo.xml +13 -0
- data/demo/demo.yml +12 -0
- data/doc/classes/ClassVariables.html +111 -0
- data/doc/classes/ConfigError.html +120 -0
- data/doc/classes/ConfigHash.html +354 -0
- data/doc/classes/Constants.html +226 -0
- data/doc/classes/Hash.html +269 -0
- data/doc/classes/InvalidConfigPathError.html +119 -0
- data/doc/classes/Object.html +220 -0
- data/doc/classes/PropertiesFileParser.html +282 -0
- data/doc/classes/RConfig.html +1745 -0
- data/doc/created.rid +1 -0
- data/doc/files/README_rdoc.html +271 -0
- data/doc/files/lib/rconfig/class_variables_rb.html +107 -0
- data/doc/files/lib/rconfig/config_hash_rb.html +114 -0
- data/doc/files/lib/rconfig/constants_rb.html +101 -0
- data/doc/files/lib/rconfig/core_ext/hash_rb.html +114 -0
- data/doc/files/lib/rconfig/core_ext/object_rb.html +101 -0
- data/doc/files/lib/rconfig/core_ext_rb.html +114 -0
- data/doc/files/lib/rconfig/exceptions_rb.html +110 -0
- data/doc/files/lib/rconfig/properties_file_parser_rb.html +146 -0
- data/doc/files/lib/rconfig/rconfig_rb.html +186 -0
- data/doc/files/lib/rconfig_rb.html +117 -0
- data/doc/fr_class_index.html +35 -0
- data/doc/fr_file_index.html +37 -0
- data/doc/fr_method_index.html +75 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/lib/generators/rconfig/install_generator.rb +13 -0
- data/lib/generators/rconfig/templates/rconfig.rb +82 -0
- data/lib/rconfig.rb +98 -32
- data/lib/rconfig/callbacks.rb +46 -0
- data/lib/rconfig/cascade.rb +56 -0
- data/lib/rconfig/config.rb +78 -0
- data/lib/rconfig/constants.rb +58 -0
- data/lib/rconfig/core_ext/array.rb +3 -0
- data/lib/rconfig/core_ext/hash.rb +56 -57
- data/lib/rconfig/core_ext/nil.rb +5 -0
- data/lib/rconfig/core_ext/string.rb +3 -0
- data/lib/rconfig/core_methods.rb +276 -0
- data/lib/rconfig/exceptions.rb +21 -8
- data/lib/rconfig/load_paths.rb +55 -0
- data/lib/rconfig/logger.rb +86 -0
- data/lib/rconfig/properties_file.rb +138 -0
- data/lib/rconfig/reload.rb +75 -0
- data/lib/rconfig/settings.rb +93 -0
- data/lib/rconfig/utils.rb +175 -0
- data/lib/tasks/gem.rake +14 -0
- data/lib/tasks/rdoc.rake +11 -0
- data/lib/tasks/spec.rake +25 -0
- data/rconfig.gemspec +33 -0
- data/spec/core_ext/object_spec.rb +44 -0
- data/spec/rconfig_spec.rb +7 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +16 -0
- metadata +128 -32
- data/lib/rconfig/config_hash.rb +0 -105
- data/lib/rconfig/core_ext.rb +0 -6
- data/lib/rconfig/properties_file_parser.rb +0 -80
- data/lib/rconfig/rconfig.rb +0 -871
- data/test/rconfig_test.rb +0 -381
data/lib/rconfig/core_ext.rb
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
|
2
|
-
#++
|
3
|
-
# Copyright (c) 2009 Rahmal Conda <rahmal@gmail.com>
|
4
|
-
#
|
5
|
-
# This class parses key/value based properties files used for configuration.
|
6
|
-
# It is used by rconfig to import configuration files of the aforementioned
|
7
|
-
# format. Unlike yaml, and xml files it can only support two levels. First,
|
8
|
-
# it can have root level properties.
|
9
|
-
#
|
10
|
-
# Example:
|
11
|
-
# server_url=host.domain.com
|
12
|
-
# server_port=8080
|
13
|
-
#
|
14
|
-
# Secondly, it can have properties grouped into catagories. The group names
|
15
|
-
# must be specified within brackets like [ ... ]
|
16
|
-
#
|
17
|
-
# Example:
|
18
|
-
# [server]
|
19
|
-
# url=host.domain.com
|
20
|
-
# port=8080
|
21
|
-
#
|
22
|
-
#--
|
23
|
-
class PropertiesFileParser
|
24
|
-
include Singleton # Don't instantiate this class
|
25
|
-
|
26
|
-
##
|
27
|
-
# Parse config file and import data into hash to be stored in config.
|
28
|
-
#
|
29
|
-
def self.parse config_file
|
30
|
-
validate(config_file)
|
31
|
-
|
32
|
-
config = {}
|
33
|
-
|
34
|
-
# The config is top down.. anything after a [group] gets added as part
|
35
|
-
# of that group until a new [group] is found.
|
36
|
-
group = nil
|
37
|
-
config_file.each do |line| # for each line in the config file
|
38
|
-
line.strip!
|
39
|
-
unless (/^\#/.match(line)) # skip comments (lines that state with '#')
|
40
|
-
if(/\s*=\s*/.match(line)) # if this line is a config property
|
41
|
-
key, val = line.split(/\s*=\s*/, 2) # parse key and value from line
|
42
|
-
key = key.chomp.strip
|
43
|
-
val = val.chomp.strip
|
44
|
-
if (val)
|
45
|
-
if val =~ /^['"](.*)['"]$/ # if the value is in quotes
|
46
|
-
value = $1 # strip out value from quotes
|
47
|
-
else
|
48
|
-
value = val # otherwise, leave as-is
|
49
|
-
end
|
50
|
-
else
|
51
|
-
value = '' # if value was nil, set it to empty string
|
52
|
-
end
|
53
|
-
|
54
|
-
if group # if this property is part of a group
|
55
|
-
config[group][key] = value # then add it to the group
|
56
|
-
else
|
57
|
-
config[key] = value # otherwise, add it to top-level config
|
58
|
-
end
|
59
|
-
|
60
|
-
# Group lines are parsed into arrays: %w('[', 'group', ']')
|
61
|
-
elsif(/^\[(.+)\]$/.match(line).to_a != []) # if this line is a config group
|
62
|
-
group = /^\[(.+)\]$/.match(line).to_a[1] # parse the group name from line
|
63
|
-
config[group] ||= {} # add group to top-level config
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
config # return config hash
|
68
|
-
end # def parse
|
69
|
-
|
70
|
-
##
|
71
|
-
# Validate the config file. Check that the file exists and that it's readable.
|
72
|
-
# TODO: Create real error.
|
73
|
-
#
|
74
|
-
def self.validate config_file
|
75
|
-
raise 'Invalid config file name.' unless config_file
|
76
|
-
raise Errno::ENOENT, "#{config_file} does not exist" unless File.exist?(config_file.path)
|
77
|
-
raise Errno::EACCES, "#{config_file} is not readable" unless File.readable?(config_file.path)
|
78
|
-
end
|
79
|
-
|
80
|
-
end # class PropertiesFileParser
|
data/lib/rconfig/rconfig.rb
DELETED
@@ -1,871 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'socket'
|
3
|
-
require 'yaml'
|
4
|
-
|
5
|
-
require 'rconfig/properties_file_parser'
|
6
|
-
require 'rconfig/config_hash'
|
7
|
-
require 'rconfig/core_ext'
|
8
|
-
require 'rconfig/exceptions'
|
9
|
-
|
10
|
-
##
|
11
|
-
#
|
12
|
-
# Copyright (c) 2009 Rahmal Conda <rahmal@gmail.com>
|
13
|
-
# -------------------------------------------------------------------
|
14
|
-
# The complete solution for Ruby Configuration Management. RConfig is a Ruby library that
|
15
|
-
# manages configuration within Ruby applications. It bridges the gap between yaml, xml, and
|
16
|
-
# key/value based properties files, by providing a centralized solution to handle application
|
17
|
-
# configuration from one location. It provides the simplicity of hash-based access, that
|
18
|
-
# Rubyists have come to know and love, supporting your configuration style of choice, while
|
19
|
-
# providing many new features, and an elegant API.
|
20
|
-
#
|
21
|
-
# -------------------------------------------------------------------
|
22
|
-
# * Simple, easy to install and use.
|
23
|
-
# * Supports yaml, xml, and properties files.
|
24
|
-
# * Yaml and xml files supprt infinite level of configuration grouping.
|
25
|
-
# * Intuitive dot-notation 'key chaining' argument access.
|
26
|
-
# * Simple well-known hash/array based argument access.
|
27
|
-
# * Implements multilevel caching to reduce disk access.
|
28
|
-
# * Short-hand access to 'global' application configuration, and shell environment.
|
29
|
-
# * Overlays multiple configuration files to support environment, host, and
|
30
|
-
# even locale-specific configuration.
|
31
|
-
#
|
32
|
-
# -------------------------------------------------------------------
|
33
|
-
# The overlay order of the config files is defined by SUFFIXES:
|
34
|
-
# * nil
|
35
|
-
# * _local
|
36
|
-
# * _config
|
37
|
-
# * _local_config
|
38
|
-
# * _{environment} (.i.e _development)
|
39
|
-
# * _{environment}_local (.i.e _development_local)
|
40
|
-
# * _{hostname} (.i.e _whiskey)
|
41
|
-
# * _{hostname}_config_local (.i.e _whiskey_config_local)
|
42
|
-
#
|
43
|
-
# -------------------------------------------------------------------
|
44
|
-
#
|
45
|
-
# Example:
|
46
|
-
#
|
47
|
-
# shell/console =>
|
48
|
-
# export LANG=en
|
49
|
-
#
|
50
|
-
# demo.yml =>
|
51
|
-
# server:
|
52
|
-
# address: host.domain.com
|
53
|
-
# port: 81
|
54
|
-
# ...
|
55
|
-
#
|
56
|
-
# application.properties =>
|
57
|
-
# debug_level = verbose
|
58
|
-
# ...
|
59
|
-
#
|
60
|
-
# demo.rb =>
|
61
|
-
# require 'rconfig'
|
62
|
-
# RConfig.config_paths = ['$HOME/config', '#{APP_ROOT}/config', '/demo/conf']
|
63
|
-
# RConfig.demo[:server][:port] => 81
|
64
|
-
# RConfig.demo.server.address => 'host.domain.com'
|
65
|
-
#
|
66
|
-
# RConfig[:debug_level] => 'verbose'
|
67
|
-
# RConfig[:lang] => 'en'
|
68
|
-
# ...
|
69
|
-
#
|
70
|
-
#--
|
71
|
-
class RConfig
|
72
|
-
include Singleton
|
73
|
-
|
74
|
-
|
75
|
-
# ENV TIER i.e. (development, integration, staging, or production)
|
76
|
-
# Defaults to RAILS_ENV if running in Rails, otherwise, it checks
|
77
|
-
# if ENV['TIER'] is present. If not, it assumes production.
|
78
|
-
unless defined? ENV_TIER
|
79
|
-
ENV_TIER = defined?(RAILS_ENV) ? RAILS_ENV : (ENV['TIER'] || 'production')
|
80
|
-
end
|
81
|
-
|
82
|
-
# The type of file used for config. Valid choices
|
83
|
-
# include (yml, yaml, xml, conf, config, properties)
|
84
|
-
# yml, yaml => yaml files, parsable by YAML library
|
85
|
-
# conf, properties => <key=value> based config files
|
86
|
-
# xml => self-explanatory
|
87
|
-
# Defaults to yml, if not specified.
|
88
|
-
YML_FILE_TYPES = [:yml, :yaml] unless defined? YML_FILE_TYPES
|
89
|
-
XML_FILE_TYPES = [:xml] unless defined? XML_FILE_TYPES
|
90
|
-
CNF_FILE_TYPES = [:cnf, :conf, :config, :properties] unless defined? CNF_FILE_TYPES
|
91
|
-
unless defined? CONFIG_FILE_TYPES
|
92
|
-
CONFIG_FILE_TYPES = YML_FILE_TYPES + XML_FILE_TYPES + CNF_FILE_TYPES
|
93
|
-
end
|
94
|
-
|
95
|
-
# Use CONFIG_HOSTNAME environment variable to
|
96
|
-
# test host-based configurations.
|
97
|
-
unless defined? HOSTNAME
|
98
|
-
HOSTNAME = ENV['CONFIG_HOSTNAME'] || Socket.gethostname
|
99
|
-
end
|
100
|
-
|
101
|
-
# Short hostname: remove all chars after first ".".
|
102
|
-
HOSTNAME_SHORT = HOSTNAME.sub(/\..*$/, '').freeze unless defined? HOSTNAME_SHORT
|
103
|
-
|
104
|
-
# This is an array of filename suffixes facilitates overriding
|
105
|
-
# configuration (i.e. 'services_local', 'services_development').
|
106
|
-
# These files get loaded in order of the array the last file
|
107
|
-
# loaded gets splatted on top of everything there.
|
108
|
-
# Ex. database_whiskey.yml overrides database_integration.yml
|
109
|
-
# overrides database.yml
|
110
|
-
SUFFIXES = [nil,
|
111
|
-
:local,
|
112
|
-
:config, :local_config,
|
113
|
-
ENV_TIER, [ENV_TIER, :local],
|
114
|
-
HOSTNAME_SHORT, [HOSTNAME_SHORT, :config_local],
|
115
|
-
HOSTNAME, [HOSTNAME, :config_local]
|
116
|
-
] unless defined? SUFFIXES
|
117
|
-
|
118
|
-
### Helper methods for white-box testing and debugging.
|
119
|
-
|
120
|
-
# Flag indicating whether or not to log errors and
|
121
|
-
# errors and application run-time information. It
|
122
|
-
# defaults to environment debug level setting, or
|
123
|
-
# false if the env variable is not set.
|
124
|
-
@@verbose = (ENV['DEBUG_LEVEL'] == 'verbose')
|
125
|
-
|
126
|
-
# Sets the flag indicating whether or not to log
|
127
|
-
# errors and application run-time information.
|
128
|
-
def self.verbose=(x)
|
129
|
-
@@verbose = x.nil? ? false : x;
|
130
|
-
end
|
131
|
-
|
132
|
-
# A hash of each file that has been loaded.
|
133
|
-
@@config_file_loaded = nil
|
134
|
-
def self.config_file_loaded=(x)
|
135
|
-
@@config_file_loaded = x
|
136
|
-
end
|
137
|
-
def self.config_file_loaded
|
138
|
-
@@config_file_loaded
|
139
|
-
end
|
140
|
-
### End Helper methods
|
141
|
-
|
142
|
-
# The list of directories to search for configuration files.
|
143
|
-
@@config_paths = []
|
144
|
-
|
145
|
-
# Hash of suffixes for a given config name.
|
146
|
-
# @@suffixes['name'] vs @@suffix['name_GB']
|
147
|
-
@@suffixes = {}
|
148
|
-
|
149
|
-
# Hash of yaml file names and their respective contents,
|
150
|
-
# last modified time, and the last time it was loaded.
|
151
|
-
# @@cache[filename] = [yaml_contents, mod_time, time_loaded]
|
152
|
-
@@cache = {}
|
153
|
-
|
154
|
-
# Hash of config file base names and their existing filenames
|
155
|
-
# including the suffixes.
|
156
|
-
# @@cache_files['ldap'] = ['ldap.yml', 'ldap_local.yml', 'ldap_<hostname>.yml']
|
157
|
-
@@cache_files = {}
|
158
|
-
|
159
|
-
# Hash of config base name and the contents of all its respective
|
160
|
-
# files merged into hashes. This hash holds the data that is
|
161
|
-
# accessed when RConfig is called. This gets re-created each time
|
162
|
-
# the config files are loaded.
|
163
|
-
# @@cache_hash['ldap'] = config_hash
|
164
|
-
@@cache_hash = {}
|
165
|
-
|
166
|
-
# The hash holds the same info as @@cache_hash, but it is only
|
167
|
-
# loaded once. If reload is disabled, data will from this hash
|
168
|
-
# will always be passed back when RConfig is called.
|
169
|
-
@@cache_config_files = {} # Keep around incase reload_disabled.
|
170
|
-
|
171
|
-
# Hash of config base name and the last time it was checked for
|
172
|
-
# update.
|
173
|
-
# @@last_auto_check['ldap'] = Time.now
|
174
|
-
@@last_auto_check = {}
|
175
|
-
|
176
|
-
# Hash of callbacks Procs for when a particular config file has changed.
|
177
|
-
@@on_load = {}
|
178
|
-
|
179
|
-
# Flag variable indicating whether or not reload should be executed.
|
180
|
-
# Defaults to false.
|
181
|
-
@@reload_disabled = false
|
182
|
-
|
183
|
-
# Sets the flag indicating whether or not reload should be executed.
|
184
|
-
def self.allow_reload=(reload)
|
185
|
-
raise
|
186
|
-
@@reload_disabled = (not reload)
|
187
|
-
end
|
188
|
-
|
189
|
-
# Flag indicating whether or not reload should be executed.
|
190
|
-
def self.reload?
|
191
|
-
!@@reload_disabled
|
192
|
-
end
|
193
|
-
|
194
|
-
# The number of seconds between reloading of config files
|
195
|
-
# and automatic reload checks. Defaults to 5 minutes.
|
196
|
-
@@reload_interval = 300
|
197
|
-
|
198
|
-
# Sets the number of seconds between reloading of config files
|
199
|
-
# and automatic reload checks. Defaults to 5 minutes.
|
200
|
-
def self.reload_interval=(x)
|
201
|
-
@@reload_interval = (x || 300)
|
202
|
-
end
|
203
|
-
|
204
|
-
##
|
205
|
-
# Flushes cached config data, so that it can be reloaded from disk.
|
206
|
-
# It is recommended that this should be used with caution, and any
|
207
|
-
# need to reload in a production setting should minimized or
|
208
|
-
# completely avoided if possible.
|
209
|
-
def self.reload(force = false)
|
210
|
-
if force || reload?
|
211
|
-
flush_cache
|
212
|
-
end
|
213
|
-
nil
|
214
|
-
end
|
215
|
-
|
216
|
-
|
217
|
-
##
|
218
|
-
# Convenience method to initialize necessary fields including,
|
219
|
-
# config paths, overlay, reload_disabled, and verbose, all at
|
220
|
-
# one time.
|
221
|
-
def self.initialize(*args)
|
222
|
-
case args[0]
|
223
|
-
when Hash
|
224
|
-
params = args[0].symbolize_keys
|
225
|
-
paths = params[:paths]
|
226
|
-
ovrlay = params[:overlay]
|
227
|
-
reload = params[:reload]
|
228
|
-
verbos = params[:verbose]
|
229
|
-
else
|
230
|
-
paths, ovrlay, reload, verbos = *args
|
231
|
-
end
|
232
|
-
self.config_paths = paths
|
233
|
-
self.overlay = ovrlay
|
234
|
-
self.allow_reload = reload
|
235
|
-
self.verbose = verbos
|
236
|
-
end
|
237
|
-
|
238
|
-
|
239
|
-
##
|
240
|
-
# Sets the list of directories to search for
|
241
|
-
# configuration files.
|
242
|
-
# The argument must be an array of strings representing
|
243
|
-
# the paths to the directories, or a string representing
|
244
|
-
# either a single path or a list of paths separated by
|
245
|
-
# either a colon (:) or a semi-colon (;).
|
246
|
-
# If reload is disabled, it can only be set once.
|
247
|
-
def self.config_paths=(paths)
|
248
|
-
return if @@reload_disabled && config_paths_set?
|
249
|
-
if paths.is_a? String
|
250
|
-
path_sep = (paths =~ /;/) ? ';' : ':'
|
251
|
-
paths = paths.split(/#{path_sep}+/)
|
252
|
-
end
|
253
|
-
unless paths.is_a? Array
|
254
|
-
raise ArgumentError,
|
255
|
-
"Path(s) must be a String or an Array [#{paths.inspect}]"
|
256
|
-
end
|
257
|
-
if paths.empty?
|
258
|
-
raise ArgumentError,
|
259
|
-
"Must provide at least one paths: [#{paths.inspect}]"
|
260
|
-
end
|
261
|
-
paths.all? do |dir|
|
262
|
-
dir = CONFIG_ROOT if dir == 'CONFIG_ROOT'
|
263
|
-
unless File.directory?(dir)
|
264
|
-
raise InvalidConfigPathError,
|
265
|
-
"This directory is invalid: [#{dir.inspect}]"
|
266
|
-
end
|
267
|
-
end
|
268
|
-
reload
|
269
|
-
@@config_paths = paths
|
270
|
-
end
|
271
|
-
class << self; alias_method :set_config_paths, :config_paths= end
|
272
|
-
|
273
|
-
##
|
274
|
-
# Adds the specified path to the list of directories to search for
|
275
|
-
# configuration files.
|
276
|
-
# It only allows one path to be entered at a time.
|
277
|
-
# If reload is disabled, it can onle be set once.
|
278
|
-
def self.set_config_path path
|
279
|
-
return if @@reload_disabled && config_paths_set?
|
280
|
-
return unless path.is_a?(String) # only allow string argument
|
281
|
-
path_sep = (path =~ /;/) ? ';' : ':' # if string contains multiple paths
|
282
|
-
path = path.split(/#{path_sep}+/)[0] # only accept first one.
|
283
|
-
|
284
|
-
if @@config_paths.blank?
|
285
|
-
set_config_paths(path)
|
286
|
-
else
|
287
|
-
config_paths << path if File.directory?(path)
|
288
|
-
reload
|
289
|
-
@@config_paths
|
290
|
-
end
|
291
|
-
end
|
292
|
-
class << self; alias_method :add_config_path, :set_config_path end
|
293
|
-
|
294
|
-
##
|
295
|
-
# Returns a list of directories to search for
|
296
|
-
# configuration files.
|
297
|
-
#
|
298
|
-
# Can be preset with config_paths=/set_config_path,
|
299
|
-
# controlled via ENV['CONFIG_PATH'],
|
300
|
-
# or defaulted to CONFIG_ROOT (assumming some sort of
|
301
|
-
# application initiation as in RAILS).
|
302
|
-
# Defaults to [ CONFIG_ROOT ].
|
303
|
-
#
|
304
|
-
# Examples:
|
305
|
-
# export CONFIG_PATH="$HOME/work/config:CONFIG_ROOT"
|
306
|
-
# CONFIG_ROOT = RAILS_ROOT + "/config" unless defined? CONFIG_ROOT
|
307
|
-
#
|
308
|
-
def self.config_paths
|
309
|
-
return @@config_paths unless @@config_paths.blank?
|
310
|
-
|
311
|
-
begin
|
312
|
-
config_paths = ENV['CONFIG_PATH']
|
313
|
-
rescue
|
314
|
-
verbose_log "Forget something? No config paths! ENV['CONFIG_PATH'] is not set.",
|
315
|
-
"Hint: Use config_paths= or set_config_path."
|
316
|
-
end
|
317
|
-
|
318
|
-
begin
|
319
|
-
config_paths = [CONFIG_ROOT]
|
320
|
-
rescue
|
321
|
-
verbose_log "Forget something? No config paths! CONFIG_ROOT is not set.",
|
322
|
-
"Hint: Use config_paths= or set_config_path."
|
323
|
-
end
|
324
|
-
|
325
|
-
if @@config_paths.blank?
|
326
|
-
raise InvalidConfigPathError,
|
327
|
-
"Forget something? No config paths!\n" +
|
328
|
-
"Niether ENV['CONFIG_PATH'] or CONFIG_ROOT is set.\n" +
|
329
|
-
"Hint: Use config_paths= or set_config_path."
|
330
|
-
end
|
331
|
-
|
332
|
-
@@config_paths
|
333
|
-
end
|
334
|
-
|
335
|
-
##
|
336
|
-
# Indicates whether or not config_paths have been set.
|
337
|
-
# Returns true if @@config_paths has at least one directory.
|
338
|
-
def self.config_paths_set?
|
339
|
-
not @@config_paths.blank?
|
340
|
-
end
|
341
|
-
|
342
|
-
|
343
|
-
# Specifies an additional overlay suffix.
|
344
|
-
#
|
345
|
-
# E.g. 'gb' for UK locale.
|
346
|
-
#
|
347
|
-
# Defaults from ENV['CONFIG_OVERLAY'].
|
348
|
-
def self.overlay
|
349
|
-
@@overlay ||= (x = ENV['CONFIG_OVERLAY']) && x.dup.freeze
|
350
|
-
end
|
351
|
-
|
352
|
-
def self.overlay=(x)
|
353
|
-
flush_cache if @@overlay != x
|
354
|
-
@@overlay = x && x.dup.freeze
|
355
|
-
end
|
356
|
-
|
357
|
-
|
358
|
-
# Returns a list of suffixes to try for a given config name.
|
359
|
-
#
|
360
|
-
# A config name with an explicit overlay (e.g.: 'name_GB')
|
361
|
-
# overrides any current _overlay.
|
362
|
-
#
|
363
|
-
# This allows code to specifically ask for config overlays
|
364
|
-
# for a particular locale.
|
365
|
-
#
|
366
|
-
def self.suffixes(name)
|
367
|
-
name = name.to_s
|
368
|
-
@@suffixes[name] ||=
|
369
|
-
begin
|
370
|
-
ol = overlay
|
371
|
-
name_x = name.dup
|
372
|
-
if name_x.sub!(/_([A-Z]+)$/, '')
|
373
|
-
ol = $1
|
374
|
-
end
|
375
|
-
name_x.freeze
|
376
|
-
result = if ol
|
377
|
-
ol_ = ol.upcase
|
378
|
-
ol = ol.downcase
|
379
|
-
x = [ ]
|
380
|
-
SUFFIXES.each do | suffix |
|
381
|
-
# Standard, no overlay:
|
382
|
-
# e.g.: database_<suffix>.yml
|
383
|
-
x << suffix
|
384
|
-
|
385
|
-
# Overlay:
|
386
|
-
# e.g.: database_(US|GB)_<suffix>.yml
|
387
|
-
x << [ ol_, suffix ]
|
388
|
-
end
|
389
|
-
[ name_x, x.freeze ]
|
390
|
-
else
|
391
|
-
[ name.dup.freeze, SUFFIXES.freeze ]
|
392
|
-
end
|
393
|
-
result.freeze
|
394
|
-
|
395
|
-
verbose_log "suffixes(#{name}) => #{result.inspect}"
|
396
|
-
|
397
|
-
result
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
|
-
|
402
|
-
##
|
403
|
-
# Get each config file's yaml hash for the given config name,
|
404
|
-
# to be merged later. Files will only be loaded if they have
|
405
|
-
# not been loaded before or the files have changed within the
|
406
|
-
# last five minutes, or force is explicitly set to true.
|
407
|
-
#
|
408
|
-
def self.load_config_files(name, force=false)
|
409
|
-
name = name.to_s # if name.is_a?(Symbol)
|
410
|
-
|
411
|
-
# Return last config file hash list loaded,
|
412
|
-
# if reload is disabled and files have already been loaded.
|
413
|
-
return @@cache_config_files[name] if
|
414
|
-
@@reload_disabled &&
|
415
|
-
@@cache_config_files[name]
|
416
|
-
|
417
|
-
now = Time.now
|
418
|
-
|
419
|
-
# Get array of all the existing files file the config name.
|
420
|
-
config_files = self.get_config_files(name)
|
421
|
-
|
422
|
-
verbose_log "load_config_files(#{name.inspect})"
|
423
|
-
|
424
|
-
# Get all the data from all yaml files into as hashes
|
425
|
-
hashes = config_files.collect do |f|
|
426
|
-
name, name_x, filename, ext, mtime = *f
|
427
|
-
|
428
|
-
# Get the cached file info the specific file, if
|
429
|
-
# it's been loaded before.
|
430
|
-
val, last_mtime, last_loaded = @@cache[filename]
|
431
|
-
|
432
|
-
verbose_log "f = #{f.inspect}",
|
433
|
-
"cache #{name_x} filename = #{filename.inspect}",
|
434
|
-
"cache #{name_x} val = #{val.inspect}",
|
435
|
-
"cache #{name_x} last_mtime = #{last_mtime.inspect}",
|
436
|
-
"cache #{name_x} last_loaded = #{last_loaded.inspect}"
|
437
|
-
|
438
|
-
# Load the file if its never been loaded or its been more than
|
439
|
-
# so many minutes since last load attempt. (default: 5 minutes)
|
440
|
-
if val.blank? || (now - last_loaded > @@reload_interval)
|
441
|
-
if force || val.blank? || mtime != last_mtime
|
442
|
-
|
443
|
-
verbose_log "mtime #{name.inspect} #{filename.inspect} " +
|
444
|
-
"changed #{mtime != last_mtime} : #{mtime.inspect} #{last_mtime.inspect}"
|
445
|
-
|
446
|
-
# Get contents from config file
|
447
|
-
File.open(filename) do |f|
|
448
|
-
verbose_log "RConfig: loading #{filename.inspect}"
|
449
|
-
val = parse_file(f, ext)
|
450
|
-
val = val[name] if ext == :xml # xml document must have root tag matching the file name.
|
451
|
-
verbose_log "RConfig: loaded #{filename.inspect} => #{val.inspect}"
|
452
|
-
(@@config_file_loaded ||= { })[name] = config_files
|
453
|
-
end
|
454
|
-
|
455
|
-
# Save cached config file contents, and mtime.
|
456
|
-
@@cache[filename] = [ val, mtime, now ]
|
457
|
-
verbose_log "cache[#{filename.inspect}] = #{@@cache[filename].inspect}"
|
458
|
-
|
459
|
-
# Flush merged hash cache.
|
460
|
-
@@cache_hash[name] = nil
|
461
|
-
|
462
|
-
# Config files changed or disappeared.
|
463
|
-
@@cache_files[name] = config_files
|
464
|
-
|
465
|
-
end # if val == nil || (now - last_loaded > @@reload_interval)
|
466
|
-
end # if force || val == nil || mtime != last_mtime
|
467
|
-
|
468
|
-
val
|
469
|
-
end
|
470
|
-
hashes.compact!
|
471
|
-
|
472
|
-
verbose_log "load_config_files(#{name.inspect}) => #{hashes.inspect}"
|
473
|
-
|
474
|
-
# Keep last loaded config files around in case @@reload_dsabled.
|
475
|
-
@@cache_config_files[name] = hashes #unless hashes.empty?
|
476
|
-
|
477
|
-
hashes
|
478
|
-
end
|
479
|
-
|
480
|
-
##
|
481
|
-
# Parses file based on file type.
|
482
|
-
#
|
483
|
-
def self.parse_file(conf_file, ext)
|
484
|
-
hash = case ext
|
485
|
-
when *YML_FILE_TYPES
|
486
|
-
YAML::load(conf_file)
|
487
|
-
when *XML_FILE_TYPES
|
488
|
-
Hash.from_xml(conf_file)
|
489
|
-
when *CNF_FILE_TYPES
|
490
|
-
PropertiesFileParser.parse(conf_file)
|
491
|
-
else
|
492
|
-
#TODO: Raise real error
|
493
|
-
raise ConfigError, "Unknown File type:#{ext}"
|
494
|
-
end
|
495
|
-
hash.freeze
|
496
|
-
end
|
497
|
-
|
498
|
-
##
|
499
|
-
# Returns a list of all relavant config files as specified
|
500
|
-
# by _suffixes list.
|
501
|
-
# Each element is an Array, containing:
|
502
|
-
# [ "the-top-level-config-name",
|
503
|
-
# "the-suffixed-config-name",
|
504
|
-
# "/the/absolute/path/to/yaml.yml",
|
505
|
-
# # The mtime of the yml file or nil, if it doesn't exist.
|
506
|
-
# ]
|
507
|
-
def self.get_config_files(name)
|
508
|
-
files = []
|
509
|
-
|
510
|
-
config_paths.reverse.each do | dir |
|
511
|
-
# splatting *suffix allows us to deal with multipart suffixes
|
512
|
-
name_no_overlay, suffixes = suffixes(name)
|
513
|
-
suffixes.map { | suffix | [ name_no_overlay, *suffix ].compact.join('_') }.each do | name_x |
|
514
|
-
CONFIG_FILE_TYPES.each do |ext|
|
515
|
-
filename = filename_for_name(name_x, dir, ext)
|
516
|
-
if File.exists?(filename)
|
517
|
-
files << [ name, name_x, filename, ext, File.stat(filename).mtime ]
|
518
|
-
end
|
519
|
-
end
|
520
|
-
end
|
521
|
-
end
|
522
|
-
|
523
|
-
verbose_log "get_config_files(#{name}) => #{files.select{|x| x[3]}.inspect}"
|
524
|
-
|
525
|
-
files
|
526
|
-
end
|
527
|
-
|
528
|
-
##
|
529
|
-
# Return the config file information for the given config name.
|
530
|
-
#
|
531
|
-
def self.config_files(name)
|
532
|
-
@@cache_files[name] ||= get_config_files(name)
|
533
|
-
end
|
534
|
-
|
535
|
-
##
|
536
|
-
# Returns whether or not the config for the given config name has changed
|
537
|
-
# since it was last loaded.
|
538
|
-
#
|
539
|
-
# Returns true if any files for config have changes since
|
540
|
-
# last load.
|
541
|
-
def self.config_changed?(name)
|
542
|
-
verbose_log "config_changed?(#{name.inspect})"
|
543
|
-
name = name.to_s # if name.is_a?(Symbol)
|
544
|
-
! (@@cache_files[name] === get_config_files(name))
|
545
|
-
end
|
546
|
-
|
547
|
-
##
|
548
|
-
# Get the merged config hash for the named file.
|
549
|
-
# Returns a cached indifferent access faker hash merged
|
550
|
-
# from all config files for a name.
|
551
|
-
#
|
552
|
-
def self.config_hash(name)
|
553
|
-
verbose_log "config_hash(#{name.inspect})"
|
554
|
-
|
555
|
-
name = name.to_s
|
556
|
-
unless result = @@cache_hash[name]
|
557
|
-
result = @@cache_hash[name] =
|
558
|
-
make_indifferent(
|
559
|
-
merge_hashes(
|
560
|
-
load_config_files(name)
|
561
|
-
)
|
562
|
-
)
|
563
|
-
verbose_log "config_hash(#{name.inspect}): reloaded"
|
564
|
-
end
|
565
|
-
|
566
|
-
result
|
567
|
-
end
|
568
|
-
|
569
|
-
|
570
|
-
##
|
571
|
-
# Register a callback when a config has been reloaded. If no config name
|
572
|
-
# is specified, the callback will be registered under the name :ANY. The
|
573
|
-
# name :ANY will register a callback for any config file change.
|
574
|
-
#
|
575
|
-
# Example:
|
576
|
-
#
|
577
|
-
# class MyClass
|
578
|
-
# @@my_config = { }
|
579
|
-
# RConfig.on_load(:cache) do
|
580
|
-
# @@my_config = { }
|
581
|
-
# end
|
582
|
-
# def my_config
|
583
|
-
# @@my_config ||= something_expensive_thing_on_config(RConfig.cache.memory_limit)
|
584
|
-
# end
|
585
|
-
# end
|
586
|
-
#
|
587
|
-
def self.on_load(*args, &blk)
|
588
|
-
args << :ANY if args.empty?
|
589
|
-
proc = blk.to_proc
|
590
|
-
|
591
|
-
# Call proc on registration.
|
592
|
-
proc.call()
|
593
|
-
|
594
|
-
# Register callback proc.
|
595
|
-
args.each do | name |
|
596
|
-
name = name.to_s
|
597
|
-
(@@on_load[name] ||= [ ]) << proc
|
598
|
-
end
|
599
|
-
end
|
600
|
-
|
601
|
-
|
602
|
-
EMPTY_ARRAY = [].freeze unless defined? EMPTY_ARRAY
|
603
|
-
|
604
|
-
# Executes all of the reload callbacks registered to the specified config name,
|
605
|
-
# and all of the callbacks registered to run on any config, as specified by the
|
606
|
-
# :ANY symbol.
|
607
|
-
def self.fire_on_load(name)
|
608
|
-
callbacks =
|
609
|
-
(@@on_load['ANY'] || EMPTY_ARRAY) +
|
610
|
-
(@@on_load[name] || EMPTY_ARRAY)
|
611
|
-
callbacks.uniq!
|
612
|
-
verbose_log "fire_on_load(#{name.inspect}): callbacks[#{callbacks.inspect}]" unless callbacks.empty?
|
613
|
-
callbacks.each{|cb| cb.call()}
|
614
|
-
end
|
615
|
-
|
616
|
-
|
617
|
-
# If config files have changed,
|
618
|
-
# Caches are flushed, on_load triggers are run.
|
619
|
-
def self.check_config_changed(name = nil)
|
620
|
-
changed = []
|
621
|
-
if name == nil
|
622
|
-
@@cache_hash.keys.dup.each do | name |
|
623
|
-
if config_has_changed?(name)
|
624
|
-
changed << name
|
625
|
-
end
|
626
|
-
end
|
627
|
-
else
|
628
|
-
name = name.to_s # if name.is_a?(Symbol)
|
629
|
-
if config_has_changed?(name)
|
630
|
-
changed << name
|
631
|
-
end
|
632
|
-
end
|
633
|
-
|
634
|
-
verbose_log "check_config_changed(#{name.inspect}) => #{changed.inspect}"
|
635
|
-
|
636
|
-
changed.empty? ? nil : changed
|
637
|
-
end
|
638
|
-
|
639
|
-
|
640
|
-
def self.config_has_changed?(name)
|
641
|
-
verbose_log "config_has_changed?(#{name.inspect}), reload_disabled=#{@@reload_disabled}"
|
642
|
-
|
643
|
-
changed = false
|
644
|
-
|
645
|
-
if config_changed?(name) && reload?
|
646
|
-
if @@cache_hash[name]
|
647
|
-
@@cache_hash[name] = nil
|
648
|
-
|
649
|
-
# force on_load triggers.
|
650
|
-
fire_on_load(name)
|
651
|
-
end
|
652
|
-
|
653
|
-
changed = true
|
654
|
-
end
|
655
|
-
|
656
|
-
changed
|
657
|
-
end
|
658
|
-
|
659
|
-
|
660
|
-
##
|
661
|
-
# Returns a merge of hashes.
|
662
|
-
#
|
663
|
-
def self.merge_hashes(hashes)
|
664
|
-
hashes.inject({ }) { | n, h | n.weave(h, false) }
|
665
|
-
end
|
666
|
-
|
667
|
-
|
668
|
-
##
|
669
|
-
# Recursively makes hashes into frozen IndifferentAccess ConfigFakerHash
|
670
|
-
# Arrays are also traversed and frozen.
|
671
|
-
#
|
672
|
-
def self.make_indifferent(x)
|
673
|
-
case x
|
674
|
-
when Hash
|
675
|
-
unless x.frozen?
|
676
|
-
x.each_pair do | k, v |
|
677
|
-
x[k] = make_indifferent(v)
|
678
|
-
end
|
679
|
-
x = ConfigHash.new.merge!(x).freeze
|
680
|
-
end
|
681
|
-
verbose_log "make_indefferent: x = #{x.inspect}:#{x.class}"
|
682
|
-
when Array
|
683
|
-
unless x.frozen?
|
684
|
-
x.collect! do | v |
|
685
|
-
make_indifferent(v)
|
686
|
-
end
|
687
|
-
x.freeze
|
688
|
-
end
|
689
|
-
# Freeze Strings.
|
690
|
-
when String
|
691
|
-
x.freeze
|
692
|
-
end
|
693
|
-
|
694
|
-
x
|
695
|
-
end
|
696
|
-
|
697
|
-
|
698
|
-
##
|
699
|
-
# This method provides shorthand to retrieve confiuration data that
|
700
|
-
# is global in scope, and used on an application or environment-wide
|
701
|
-
# level. The default location that it checks is the application file.
|
702
|
-
# The application config file is a special config file that should be
|
703
|
-
# used for config data that is broad in scope and used throughout the
|
704
|
-
# application. Since RConfig gives special regard to the application
|
705
|
-
# config file, thought should be given to whatever config information
|
706
|
-
# is placed there.
|
707
|
-
#
|
708
|
-
# Most config data will be specific to particular part of the
|
709
|
-
# application (i.e. database, web service), and should therefore
|
710
|
-
# be placed in its own specific config file, such as database.yml,
|
711
|
-
# or services.xml
|
712
|
-
#
|
713
|
-
# This method also acts as a wrapper for ENV. If no value is
|
714
|
-
# returned from the application config, it will also check
|
715
|
-
# ENV for a value matching the specified key.
|
716
|
-
#
|
717
|
-
# Ex.1 RConfig[:test_mode] =>
|
718
|
-
# RConfig.application[:test_mode] ||
|
719
|
-
# RConfig.application.test_mode
|
720
|
-
#
|
721
|
-
# Ex.2 RConfig[:web_app_root] => ENV['WEB_APP_ROOT']
|
722
|
-
#
|
723
|
-
# NOTE: The application config file can be in any of
|
724
|
-
# the supported formats (yml, xml, conf, etc.)
|
725
|
-
#
|
726
|
-
def self.[](key, file=:application)
|
727
|
-
self.get_config_file(file)[key] || ENV[key.to_s.upcase]
|
728
|
-
end
|
729
|
-
|
730
|
-
##
|
731
|
-
# Get the value specified by the args, in the file specified by th name
|
732
|
-
#
|
733
|
-
def self.with_file(name, *args)
|
734
|
-
# verbose_log "with_file(#{name.inspect}, #{args.inspect})"; result =
|
735
|
-
args.inject(get_config_file(name)) { | v, i |
|
736
|
-
# verbose_log "v = #{v.inspect}, i = #{i.inspect}"
|
737
|
-
case v
|
738
|
-
when Hash
|
739
|
-
v[i.to_s]
|
740
|
-
when Array
|
741
|
-
i.is_a?(Integer) ? v[i] : nil
|
742
|
-
else
|
743
|
-
nil
|
744
|
-
end
|
745
|
-
}
|
746
|
-
# verbose_log "with_file(#{name.inspect}, #{args.inspect}) => #{result.inspect}"; result
|
747
|
-
end
|
748
|
-
|
749
|
-
##
|
750
|
-
# Get the merged config hash.
|
751
|
-
# Will auto check every 5 minutes, for longer running apps.
|
752
|
-
#
|
753
|
-
def self.get_config_file(name)
|
754
|
-
name = name.to_s
|
755
|
-
now = Time.now
|
756
|
-
|
757
|
-
if (! @@last_auto_check[name]) || (now - @@last_auto_check[name]) > @@reload_interval
|
758
|
-
@@last_auto_check[name] = now
|
759
|
-
check_config_changed(name)
|
760
|
-
end
|
761
|
-
|
762
|
-
result = config_hash(name)
|
763
|
-
|
764
|
-
verbose_log "get_config_file(#{name.inspect}) => #{result.inspect}"
|
765
|
-
|
766
|
-
result
|
767
|
-
end
|
768
|
-
|
769
|
-
|
770
|
-
##
|
771
|
-
# Disables any reloading of config,
|
772
|
-
# executes &block,
|
773
|
-
# calls check_config_changed,
|
774
|
-
# returns result of block
|
775
|
-
#
|
776
|
-
def self.disable_reload(&block)
|
777
|
-
# This should increment @@reload_disabled on entry, decrement on exit.
|
778
|
-
result = nil
|
779
|
-
reload_disabled_save = @@reload_disabled
|
780
|
-
begin
|
781
|
-
@@reload_disabled = true
|
782
|
-
result = yield
|
783
|
-
ensure
|
784
|
-
@@reload_disabled = reload_disabled_save
|
785
|
-
check_config_changed unless @@reload_disabled
|
786
|
-
end
|
787
|
-
result
|
788
|
-
end
|
789
|
-
|
790
|
-
##
|
791
|
-
# Creates a dottable hash for all Hash objects, recursively.
|
792
|
-
#
|
793
|
-
def self.create_dottable_hash(value)
|
794
|
-
make_indifferent(value)
|
795
|
-
end
|
796
|
-
|
797
|
-
##
|
798
|
-
# Short-hand access to config file by its name.
|
799
|
-
#
|
800
|
-
# Example:
|
801
|
-
#
|
802
|
-
# RConfig.provider(:foo) => RConfig.with_file(:provider).foo
|
803
|
-
# RConfig.provider.foo => RConfig.with_file(:provider).foo
|
804
|
-
#
|
805
|
-
def self.method_missing(method, *args)
|
806
|
-
value = with_file(method, *args)
|
807
|
-
verbose_log "#{self}.method_missing(#{method.inspect}, #{args.inspect}) => #{value.inspect}"
|
808
|
-
value
|
809
|
-
end
|
810
|
-
|
811
|
-
##
|
812
|
-
# Creating an instance isn't required. But if you just have to a reference to RConfig
|
813
|
-
# you can get it using RConfig.instance. It's a singleton, so there's never more than
|
814
|
-
# one. The instance has no state, and no methods of it's own accept what it inherits
|
815
|
-
# from object. But this method delegates back to the class, so configuration data is
|
816
|
-
# still accessible.
|
817
|
-
|
818
|
-
# Example:
|
819
|
-
#
|
820
|
-
# config = RConfig.instance
|
821
|
-
# config.provider(:foo) => RConfig.provider(:foo)
|
822
|
-
# config.provider.foo => RConfig.provider.foo
|
823
|
-
#
|
824
|
-
def method_missing(method, *args)
|
825
|
-
self.class.method_missing(method, *args)
|
826
|
-
end
|
827
|
-
|
828
|
-
##
|
829
|
-
# Creating an instance isn't required. But if you just have to a reference to RConfig
|
830
|
-
# you can get it using RConfig.instance. It's a singleton, so there's never more than
|
831
|
-
# one. The instance has no state, and no methods of it's own accept what it inherits
|
832
|
-
# from object. But this method delegates back to the class, so configuration data is
|
833
|
-
# still accessible.
|
834
|
-
#
|
835
|
-
# Example:
|
836
|
-
#
|
837
|
-
# config = RConfig.instance
|
838
|
-
# config[:foo] => RConfig[:foo]
|
839
|
-
#
|
840
|
-
def [](key)
|
841
|
-
self.class[key]
|
842
|
-
end
|
843
|
-
|
844
|
-
protected
|
845
|
-
|
846
|
-
##
|
847
|
-
# Flushes cached config data. This should avoided in production
|
848
|
-
# environments, if possible.
|
849
|
-
def self.flush_cache
|
850
|
-
@@suffixes = { }
|
851
|
-
@@cache = { }
|
852
|
-
@@cache_files = { }
|
853
|
-
@@cache_hash = { }
|
854
|
-
@@last_auto_check = { }
|
855
|
-
self
|
856
|
-
end
|
857
|
-
|
858
|
-
##
|
859
|
-
# Get complete file name, including file path for the given config name
|
860
|
-
# and directory.
|
861
|
-
def self.filename_for_name(name, dir = config_paths[0], ext = :yml)
|
862
|
-
File.join(dir, "#{name}.#{ext}")
|
863
|
-
end
|
864
|
-
|
865
|
-
##
|
866
|
-
# Helper method for logging verbose messages.
|
867
|
-
def self.verbose_log *args
|
868
|
-
$stderr.puts(args.join("\n")) if @@verbose
|
869
|
-
end
|
870
|
-
|
871
|
-
end # class RConfig
|