rconfig 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,276 @@
|
|
1
|
+
##
|
2
|
+
# Copyright (c) 2009 Rahmal Conda <rahmal@gmail.com>
|
3
|
+
#
|
4
|
+
module RConfig
|
5
|
+
module CoreMethods
|
6
|
+
include Constants
|
7
|
+
|
8
|
+
##
|
9
|
+
# Get each config file's yaml hash for the given config name,
|
10
|
+
# to be merged later. Files will only be loaded if they have
|
11
|
+
# not been loaded before or the files have changed within the
|
12
|
+
# last five minutes, or force is explicitly set to true.
|
13
|
+
#
|
14
|
+
def load_config_files(name, force=false)
|
15
|
+
name = name.to_s
|
16
|
+
|
17
|
+
# Return last config file hash list loaded,
|
18
|
+
# if reload is disabled and files have already been loaded.
|
19
|
+
return self.cache_config_files[name] if self.reload_disabled? && self.cache_config_files[name]
|
20
|
+
|
21
|
+
logger.info "Loading config files for: #{name}"
|
22
|
+
logger.debug "load_config_files(#{name.inspect})"
|
23
|
+
|
24
|
+
|
25
|
+
now = Time.now
|
26
|
+
|
27
|
+
# Get array of all the existing files file the config name.
|
28
|
+
config_files = self.get_config_files(name)
|
29
|
+
|
30
|
+
# Get all the data from all yaml files into as configs
|
31
|
+
configs = config_files.collect do |f|
|
32
|
+
name, name_with_suffix, filename, ext, modified_time = * f
|
33
|
+
|
34
|
+
# Get the cached file info the specific file, if
|
35
|
+
# it's been loaded before.
|
36
|
+
config_data, last_modified, last_loaded = self.cache[filename]
|
37
|
+
|
38
|
+
logger.debug "f = #{f.inspect}\n" +
|
39
|
+
"cache #{name_with_suffix} filename = #{filename.inspect}\n" +
|
40
|
+
"cache #{name_with_suffix} config_data = #{config_data.inspect}\n" +
|
41
|
+
"cache #{name_with_suffix} last_modified = #{last_modified.inspect}\n" +
|
42
|
+
"cache #{name_with_suffix} last_loaded = #{last_loaded.inspect}\n"
|
43
|
+
|
44
|
+
# Load the file if its never been loaded or its been more than
|
45
|
+
# so many minutes since last load attempt. (default: 5 minutes)
|
46
|
+
if config_data.blank? || (now - last_loaded > self.reload_interval)
|
47
|
+
if force || config_data.blank? || modified_time != last_modified
|
48
|
+
|
49
|
+
logger.debug "modified_time #{name.inspect} #{filename.inspect} " +
|
50
|
+
"changed #{modified_time != last_modified} : #{modified_time.inspect} #{last_modified.inspect}"
|
51
|
+
|
52
|
+
logger.debug "RConfig: loading #{filename.inspect}"
|
53
|
+
|
54
|
+
config_data = read(filename, name, ext) # Get contents from config file
|
55
|
+
|
56
|
+
logger.debug "RConfig: loaded #{filename.inspect} => #{config_data.inspect}"
|
57
|
+
|
58
|
+
(self.config_loaded ||= {})[name] = config_files # add files to the loaded files cache
|
59
|
+
|
60
|
+
self.cache[filename] = [config_data, modified_time, now] # Save cached config file contents, and modified_time.
|
61
|
+
|
62
|
+
logger.debug "cache[#{filename.inspect}] = #{self.cache[filename].inspect}"
|
63
|
+
|
64
|
+
self.cache_hash[name] = nil # Flush merged hash cache.
|
65
|
+
|
66
|
+
self.cache_files[name] = config_files # Config files changed or disappeared.
|
67
|
+
|
68
|
+
end # if config_data == nil || (now - last_loaded > self.reload_interval)
|
69
|
+
end # if force || config_data == nil || modified_time != last_modified
|
70
|
+
|
71
|
+
config_data
|
72
|
+
end # config_files.collect
|
73
|
+
configs.compact!
|
74
|
+
|
75
|
+
logger.debug "load_config_files(#{name.inspect}) => #{configs.inspect}"
|
76
|
+
|
77
|
+
# Keep last loaded config files around in case self.reload_dsabled.
|
78
|
+
self.cache_config_files[name] = configs #unless configs.empty?
|
79
|
+
|
80
|
+
configs
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
##
|
85
|
+
# Returns a list of all relevant config files as specified by suffixes list.
|
86
|
+
# Each element is an Array, containing:
|
87
|
+
#
|
88
|
+
# [
|
89
|
+
# "server", # The base name of the
|
90
|
+
# "server_production", # The suffixed name
|
91
|
+
# "/path/to/server.yml", # The absolute path to the file
|
92
|
+
# <Wed Apr 09 08:53:14> # The last modified time of the file or nil, if it doesn't exist.
|
93
|
+
# ]
|
94
|
+
#
|
95
|
+
def get_config_files(name)
|
96
|
+
files = []
|
97
|
+
|
98
|
+
self.load_paths.reverse.each do |directory|
|
99
|
+
# splatting *suffix allows us to deal with multipart suffixes
|
100
|
+
name_no_overlay, suffixes = suffixes_for(name)
|
101
|
+
suffixes.map { |suffix| [name_no_overlay, *suffix].compact.join('_') }.each do |name_with_suffix|
|
102
|
+
self.file_types.each do |ext|
|
103
|
+
filename = filename_for_name(name_with_suffix, directory, ext)
|
104
|
+
if File.exists?(filename)
|
105
|
+
modified_time = File.stat(filename).mtime
|
106
|
+
files << [name, name_with_suffix, filename, ext, modified_time]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
logger.debug "get_config_files(#{name}) => #{files.select { |x| x[3] }.inspect}"
|
113
|
+
|
114
|
+
files
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# Return the config file information for the given config name.
|
119
|
+
#
|
120
|
+
def config_files(name)
|
121
|
+
self.cache_files[name] ||= get_config_files(name)
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
##
|
126
|
+
# Returns whether or not the config for the given config name has changed
|
127
|
+
# since it was last loaded.
|
128
|
+
#
|
129
|
+
# Returns true if any files for config have changes since
|
130
|
+
# last load.
|
131
|
+
def config_changed?(name)
|
132
|
+
logger.debug "config_changed?(#{name.inspect})"
|
133
|
+
name = name.to_s
|
134
|
+
!(self.cache_files[name] === get_config_files(name))
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
##
|
139
|
+
# Get the merged config hash for the named file.
|
140
|
+
# Returns a cached indifferent access faker hash merged
|
141
|
+
# from all config files for a name.
|
142
|
+
#
|
143
|
+
def get_config_data(name)
|
144
|
+
logger.debug "get_config_data(#{name.inspect})"
|
145
|
+
|
146
|
+
name = name.to_s
|
147
|
+
unless result = self.cache_hash[name]
|
148
|
+
result = self.cache_hash[name] =
|
149
|
+
make_indifferent(
|
150
|
+
merge_hashes(
|
151
|
+
load_config_files(name)
|
152
|
+
)
|
153
|
+
)
|
154
|
+
logger.debug "get_config_data(#{name.inspect}): reloaded"
|
155
|
+
end
|
156
|
+
|
157
|
+
result
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# If name is specified, checks that file for changes and
|
162
|
+
# reloads it if there are. Otherwise, checks all files
|
163
|
+
# in the cache, reloading the changed files.
|
164
|
+
def check_for_changes(name=nil)
|
165
|
+
changed = []
|
166
|
+
if name == nil
|
167
|
+
self.cache_hash.keys.dup.each do |name|
|
168
|
+
if reload_on_change(name)
|
169
|
+
changed << name
|
170
|
+
end
|
171
|
+
end
|
172
|
+
else
|
173
|
+
name = name.to_s
|
174
|
+
if reload_on_change(name)
|
175
|
+
changed << name
|
176
|
+
end
|
177
|
+
end
|
178
|
+
logger.debug "check_for_changes(#{name.inspect}) => #{changed.inspect}"
|
179
|
+
changed
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
# If config files have changed, caches are flushed, on_load triggers are run.
|
184
|
+
def reload_on_change(name)
|
185
|
+
logger.debug "reload_on_change(#{name.inspect}), reload_disabled=#{self.reload_disabled?}"
|
186
|
+
if changed = config_changed?(name) && reload?
|
187
|
+
if self.cache_hash[name]
|
188
|
+
flush_cache(name) # flush cached config values.
|
189
|
+
fire_on_load(name) # force on_load triggers.
|
190
|
+
end
|
191
|
+
end
|
192
|
+
changed
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
##
|
197
|
+
# This method provides shorthand to retrieve configuration data that
|
198
|
+
# is global in scope, and used on an application or environment-wide
|
199
|
+
# level. The default location that it checks is the application file.
|
200
|
+
# The application config file is a special config file that should be
|
201
|
+
# used for config data that is broad in scope and used throughout the
|
202
|
+
# application. Since RConfig gives special regard to the application
|
203
|
+
# config file, thought should be given to whatever config information
|
204
|
+
# is placed there.
|
205
|
+
#
|
206
|
+
# Most config data will be specific to particular part of the
|
207
|
+
# application (i.e. database, web service), and should therefore
|
208
|
+
# be placed in its own specific config file, such as database.yml,
|
209
|
+
# or services.xml
|
210
|
+
#
|
211
|
+
# This method also acts as a wrapper for ENV. If no value is
|
212
|
+
# returned from the application config, it will also check
|
213
|
+
# ENV for a value matching the specified key.
|
214
|
+
#
|
215
|
+
# Ex.1 RConfig[:test_mode] =>
|
216
|
+
# RConfig.application[:test_mode] ||
|
217
|
+
# RConfig.application.test_mode
|
218
|
+
#
|
219
|
+
# Ex.2 RConfig[:web_app_root] => ENV['WEB_APP_ROOT']
|
220
|
+
#
|
221
|
+
# NOTE: The application config file can be in any of
|
222
|
+
# the supported formats (yml, xml, conf, etc.)
|
223
|
+
#
|
224
|
+
def [](key, file=:application)
|
225
|
+
self.config_for(file)[key] || ENV[key.to_s.upcase]
|
226
|
+
end
|
227
|
+
|
228
|
+
##
|
229
|
+
# Get the value specified by the args, in the file specified by th name
|
230
|
+
#
|
231
|
+
def with_file(name, *args)
|
232
|
+
logger.debug "with_file(#{name.inspect}, #{args.inspect})"
|
233
|
+
result = args.inject(config_for(name)) { |v, i|
|
234
|
+
logger.debug "v = #{v.inspect}, i = #{i.inspect}"
|
235
|
+
case v
|
236
|
+
when Hash
|
237
|
+
v[i.to_s]
|
238
|
+
when Array
|
239
|
+
i.is_a?(Integer) ? v[i] : nil
|
240
|
+
else
|
241
|
+
nil
|
242
|
+
end
|
243
|
+
}
|
244
|
+
logger.debug "with_file(#{name.inspect}, #{args.inspect}) => #{result.inspect}"
|
245
|
+
result
|
246
|
+
end
|
247
|
+
|
248
|
+
##
|
249
|
+
# Get a hash of merged config data.
|
250
|
+
# Will auto check every 5 minutes, for longer running apps, unless reload is disabled.
|
251
|
+
#
|
252
|
+
def config_for(name)
|
253
|
+
name = name.to_s
|
254
|
+
check_for_changes(name) if auto_check?(name)
|
255
|
+
data = get_config_data(name)
|
256
|
+
logger.debug "config_for(#{name.inspect}) => #{data.inspect}"
|
257
|
+
data
|
258
|
+
end
|
259
|
+
|
260
|
+
##
|
261
|
+
# Short-hand access to config file by its name.
|
262
|
+
#
|
263
|
+
# Example:
|
264
|
+
#
|
265
|
+
# RConfig.provider(:foo) => RConfig.with_file(:provider).foo
|
266
|
+
# RConfig.provider.foo => RConfig.with_file(:provider).foo
|
267
|
+
#
|
268
|
+
def method_missing(method, * args)
|
269
|
+
value = with_file(method, * args)
|
270
|
+
logger.debug "#{self}.method_missing(#{method.inspect}, #{args.inspect}) => #{value.inspect}"
|
271
|
+
value
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
data/lib/rconfig/exceptions.rb
CHANGED
@@ -1,13 +1,26 @@
|
|
1
|
-
|
2
|
-
#--
|
1
|
+
##
|
3
2
|
# Copyright (c) 2009 Rahmal Conda <rahmal@gmail.com>
|
4
3
|
#
|
5
4
|
# RConfig Exceptions
|
6
|
-
|
5
|
+
#
|
6
|
+
module RConfig
|
7
|
+
# General error in config initialization or operation.
|
8
|
+
class ConfigError < StandardError; end
|
9
|
+
|
10
|
+
# Load path(s) are not set, don't exist, or Invalid in some manner
|
11
|
+
class InvalidLoadPathError < ConfigError; end
|
12
|
+
|
13
|
+
module Exceptions
|
14
|
+
|
15
|
+
# Raised when no valid load paths are available.
|
16
|
+
def raise_load_path_error
|
17
|
+
raise InvalidLoadPathError, "No load paths were provided, and none of the default paths were found."
|
18
|
+
end
|
7
19
|
|
8
|
-
#
|
9
|
-
|
20
|
+
# Raised if logging is enabled, but no logger is specified}.
|
21
|
+
def raise_logger_error
|
22
|
+
raise ConfigError, "No logger was specified, and a defualt logger was not found. Set logger to `false` to disable logging."
|
23
|
+
end
|
10
24
|
|
11
|
-
|
12
|
-
|
13
|
-
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module RConfig
|
2
|
+
module LoadPaths
|
3
|
+
include Constants
|
4
|
+
|
5
|
+
##
|
6
|
+
# Sets the list of directories to search for
|
7
|
+
# configuration files.
|
8
|
+
# The argument must be an array of strings representing
|
9
|
+
# the paths to the directories, or a string representing
|
10
|
+
# either a single path or a list of paths separated by
|
11
|
+
# either a colon (:) or a semi-colon (;).
|
12
|
+
def set_load_paths(paths)
|
13
|
+
self.load_paths = parse_load_paths(paths)
|
14
|
+
reload(true) # Load Paths have changed so force a reload
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Adds the specified path to the list of directories to search for
|
19
|
+
# configuration files.
|
20
|
+
# It only allows one path to be entered at a time.
|
21
|
+
def add_load_path(path)
|
22
|
+
if path = parse_load_paths(path).first # only accept first one.
|
23
|
+
self.load_paths << path
|
24
|
+
return reload(true) # Load Paths have changed so force a reload
|
25
|
+
end
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# If the paths are made up of a delimited string, then parse out the
|
31
|
+
# individual paths. Verify that each path is valid.
|
32
|
+
def parse_load_paths(paths)
|
33
|
+
return [] unless paths
|
34
|
+
if paths.is_a? String
|
35
|
+
path_sep = (paths =~ /;/) ? ';' : ':'
|
36
|
+
paths = paths.split(/#{path_sep}+/)
|
37
|
+
end
|
38
|
+
raise ArgumentError, "Path(s) must be a String or an Array [#{paths.inspect}]" unless paths.is_a? Array
|
39
|
+
raise ArgumentError, "Must provide at least one load path: [#{paths.inspect}]" if paths.empty?
|
40
|
+
paths.each do |dir|
|
41
|
+
dir = CONFIG_ROOT if dir == 'CONFIG_ROOT'
|
42
|
+
raise RConfig::InvalidConfigPathError, "This directory is invalid: [#{dir.inspect}]" unless Dir.exists?(dir)
|
43
|
+
end
|
44
|
+
paths
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Indicates whether or not config_paths have been set.
|
49
|
+
# Returns true if self.load_paths has at least one directory.
|
50
|
+
def load_paths_set?
|
51
|
+
not load_paths.blank?
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module RConfig
|
2
|
+
class Logger #:nodoc:
|
3
|
+
attr_accessor :level, :log_format, :date_format
|
4
|
+
attr_reader :output
|
5
|
+
|
6
|
+
FATAL = 4
|
7
|
+
ERROR = 3
|
8
|
+
WARN = 2
|
9
|
+
INFO = 1
|
10
|
+
DEBUG = 0
|
11
|
+
|
12
|
+
MAX_LEVEL = 4
|
13
|
+
|
14
|
+
def initialize(options={})
|
15
|
+
# Use provided output
|
16
|
+
if output = options[:output] && output.respond_to?(:puts)
|
17
|
+
@output = output
|
18
|
+
@needs_close = false
|
19
|
+
end
|
20
|
+
|
21
|
+
# Use log file
|
22
|
+
if output.nil? && options[:file] && File.exists?(optios[:file])
|
23
|
+
@output = File.open(options[:file].to_s, 'a')
|
24
|
+
@needs_close = true
|
25
|
+
end
|
26
|
+
|
27
|
+
# Default to stderr, if no outputter or file provider
|
28
|
+
@output ||= STDERR
|
29
|
+
|
30
|
+
# Use provided level or default to warn
|
31
|
+
@level = options[:level] ||
|
32
|
+
((defined?(Rails) && %w[test development].include?(Rails.env)) ? DEBUG : WARN)
|
33
|
+
|
34
|
+
# Date format
|
35
|
+
@date_format = options[:date_format] || '%Y-%m-%d %H:%M:%S'
|
36
|
+
#@log_format = options[:log_format] || "[%l] %d :: %M :: %t"
|
37
|
+
end
|
38
|
+
|
39
|
+
def close
|
40
|
+
output.close if @needs_close
|
41
|
+
end
|
42
|
+
|
43
|
+
def log(level, message)
|
44
|
+
if self.level <= level
|
45
|
+
indent = "%*s" % [MAX_LEVEL, "*" * (MAX_LEVEL - level)]
|
46
|
+
message.lines.each do |line|
|
47
|
+
log_str = "[#{indent}] #{Time.now.strftime(self.date_format)} :: #{line.strip}\n"
|
48
|
+
output.puts log_str
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def fatal(message)
|
54
|
+
log(FATAL, message)
|
55
|
+
end
|
56
|
+
|
57
|
+
def error(message)
|
58
|
+
log(ERROR, message)
|
59
|
+
end
|
60
|
+
|
61
|
+
def warn(message)
|
62
|
+
log(WARN, message)
|
63
|
+
end
|
64
|
+
|
65
|
+
def info(message)
|
66
|
+
log(INFO, message)
|
67
|
+
end
|
68
|
+
|
69
|
+
def debug(message)
|
70
|
+
log(DEBUG, message)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
class DisabledLogger
|
76
|
+
def log(level, message) end
|
77
|
+
def dont_log(message) end
|
78
|
+
|
79
|
+
alias_method :fatal, :dont_log
|
80
|
+
alias_method :error, :dont_log
|
81
|
+
alias_method :warn, :dont_log
|
82
|
+
alias_method :info, :dont_log
|
83
|
+
alias_method :debug, :dont_log
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|