rconfig 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +1 -0
  4. data/ChangeLog +50 -0
  5. data/Credits +13 -0
  6. data/Gemfile +9 -0
  7. data/Gemfile.lock +30 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.rdoc +0 -0
  10. data/Rakefile +11 -0
  11. data/demo/application.conf +3 -0
  12. data/demo/demo.conf +13 -0
  13. data/demo/demo.rb +14 -0
  14. data/demo/demo.xml +13 -0
  15. data/demo/demo.yml +12 -0
  16. data/doc/classes/ClassVariables.html +111 -0
  17. data/doc/classes/ConfigError.html +120 -0
  18. data/doc/classes/ConfigHash.html +354 -0
  19. data/doc/classes/Constants.html +226 -0
  20. data/doc/classes/Hash.html +269 -0
  21. data/doc/classes/InvalidConfigPathError.html +119 -0
  22. data/doc/classes/Object.html +220 -0
  23. data/doc/classes/PropertiesFileParser.html +282 -0
  24. data/doc/classes/RConfig.html +1745 -0
  25. data/doc/created.rid +1 -0
  26. data/doc/files/README_rdoc.html +271 -0
  27. data/doc/files/lib/rconfig/class_variables_rb.html +107 -0
  28. data/doc/files/lib/rconfig/config_hash_rb.html +114 -0
  29. data/doc/files/lib/rconfig/constants_rb.html +101 -0
  30. data/doc/files/lib/rconfig/core_ext/hash_rb.html +114 -0
  31. data/doc/files/lib/rconfig/core_ext/object_rb.html +101 -0
  32. data/doc/files/lib/rconfig/core_ext_rb.html +114 -0
  33. data/doc/files/lib/rconfig/exceptions_rb.html +110 -0
  34. data/doc/files/lib/rconfig/properties_file_parser_rb.html +146 -0
  35. data/doc/files/lib/rconfig/rconfig_rb.html +186 -0
  36. data/doc/files/lib/rconfig_rb.html +117 -0
  37. data/doc/fr_class_index.html +35 -0
  38. data/doc/fr_file_index.html +37 -0
  39. data/doc/fr_method_index.html +75 -0
  40. data/doc/index.html +24 -0
  41. data/doc/rdoc-style.css +208 -0
  42. data/lib/generators/rconfig/install_generator.rb +13 -0
  43. data/lib/generators/rconfig/templates/rconfig.rb +82 -0
  44. data/lib/rconfig.rb +98 -32
  45. data/lib/rconfig/callbacks.rb +46 -0
  46. data/lib/rconfig/cascade.rb +56 -0
  47. data/lib/rconfig/config.rb +78 -0
  48. data/lib/rconfig/constants.rb +58 -0
  49. data/lib/rconfig/core_ext/array.rb +3 -0
  50. data/lib/rconfig/core_ext/hash.rb +56 -57
  51. data/lib/rconfig/core_ext/nil.rb +5 -0
  52. data/lib/rconfig/core_ext/string.rb +3 -0
  53. data/lib/rconfig/core_methods.rb +276 -0
  54. data/lib/rconfig/exceptions.rb +21 -8
  55. data/lib/rconfig/load_paths.rb +55 -0
  56. data/lib/rconfig/logger.rb +86 -0
  57. data/lib/rconfig/properties_file.rb +138 -0
  58. data/lib/rconfig/reload.rb +75 -0
  59. data/lib/rconfig/settings.rb +93 -0
  60. data/lib/rconfig/utils.rb +175 -0
  61. data/lib/tasks/gem.rake +14 -0
  62. data/lib/tasks/rdoc.rake +11 -0
  63. data/lib/tasks/spec.rake +25 -0
  64. data/rconfig.gemspec +33 -0
  65. data/spec/core_ext/object_spec.rb +44 -0
  66. data/spec/rconfig_spec.rb +7 -0
  67. data/spec/spec.opts +4 -0
  68. data/spec/spec_helper.rb +16 -0
  69. metadata +128 -32
  70. data/lib/rconfig/config_hash.rb +0 -105
  71. data/lib/rconfig/core_ext.rb +0 -6
  72. data/lib/rconfig/properties_file_parser.rb +0 -80
  73. data/lib/rconfig/rconfig.rb +0 -871
  74. data/test/rconfig_test.rb +0 -381
@@ -1,6 +0,0 @@
1
- #++
2
- # Copyright (c) 2009 Rahmal Conda <rahmal@gmail.com>
3
- #
4
- #
5
- #--
6
- require 'rconfig/core_ext/hash'
@@ -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
@@ -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