rconfig 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/rconfig.rb +39 -0
- data/lib/rconfig/config_hash.rb +105 -0
- data/lib/rconfig/core_ext.rb +6 -0
- data/lib/rconfig/core_ext/hash.rb +104 -0
- data/lib/rconfig/exceptions.rb +13 -0
- data/lib/rconfig/properties_file_parser.rb +80 -0
- data/lib/rconfig/rconfig.rb +871 -0
- data/test/rconfig_test.rb +381 -0
- metadata +76 -0
data/lib/rconfig.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009 Rahmal Conda <rahmal@gmail.com>
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
$:.unshift File.dirname(__FILE__)
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'active_support'
|
28
|
+
require 'active_support/core_ext'
|
29
|
+
require 'active_support/core_ext/hash/conversions'
|
30
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
31
|
+
|
32
|
+
require 'rconfig/core_ext'
|
33
|
+
require 'rconfig/config_hash'
|
34
|
+
require 'rconfig/properties_file_parser'
|
35
|
+
require 'rconfig/rconfig'
|
36
|
+
|
37
|
+
# Create global reference to RConfig instance
|
38
|
+
$config = RConfig.instance
|
39
|
+
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#++
|
2
|
+
# Copyright (c) 2009 Rahmal Conda <rahmal@gmail.com>
|
3
|
+
#
|
4
|
+
# ConfigHash is a special class, derived from HashWithIndifferentAccess.
|
5
|
+
# It was specifically created for handling config data or creating mock
|
6
|
+
# objects from yaml files. It provides a dotted notation for accessing
|
7
|
+
# embedded hash values, similar to the way one might traverse a object tree.
|
8
|
+
#--
|
9
|
+
class ConfigHash < HashWithIndifferentAccess
|
10
|
+
|
11
|
+
# HashWithIndifferentAccess#dup always returns HashWithIndifferentAccess!
|
12
|
+
def dup
|
13
|
+
self.class.new(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Dotted notation can be used with arguments (useful for creating mock objects)
|
18
|
+
# in the YAML file the method name is a key, argument(s) form a nested key,
|
19
|
+
# so that the correct value is retrieved and returned.
|
20
|
+
#
|
21
|
+
# For example loading to variable foo a yaml file that looks like:
|
22
|
+
# customer:
|
23
|
+
# id: 12345678
|
24
|
+
# verified:
|
25
|
+
# phone: verified
|
26
|
+
# :address: info_not_available
|
27
|
+
# ? [name, employer]
|
28
|
+
# : not_verified
|
29
|
+
#
|
30
|
+
# Allows the following calls:
|
31
|
+
# foo.customer.id => 12345678
|
32
|
+
# foo.customer.verified.phone => verified
|
33
|
+
# foo.customer.verified("phone") => verified
|
34
|
+
# foo.customer.verified(:address) => info_not_available
|
35
|
+
# foo.customer.verified("name", "employer") => not_verified
|
36
|
+
#
|
37
|
+
# Note that :address is specified as a symbol, where phone is just a string.
|
38
|
+
# Depending on what kind of parameter the method being mocked out is going
|
39
|
+
# to be called with, define in the YAML file either a string or a symbol.
|
40
|
+
# This also works inside the composite array keys.
|
41
|
+
def method_missing(method, *args)
|
42
|
+
method = method.to_s
|
43
|
+
# STDERR.puts "CHF#method_missing(#{method.inspect}, #{args.inspect}) on #{self.inspect}:#{self.class}" if method == 'dup'
|
44
|
+
value = self[method]
|
45
|
+
case args.size
|
46
|
+
when 0:
|
47
|
+
# e.g.: RConfig.application.method
|
48
|
+
;
|
49
|
+
when 1:
|
50
|
+
# e.g.: RConfig.application.method(one_arg)
|
51
|
+
value = value.send(args[0])
|
52
|
+
else
|
53
|
+
# e.g.: RConfig.application.method(arg_one, args_two, ...)
|
54
|
+
value = value[args]
|
55
|
+
end
|
56
|
+
# value = convert_value(value)
|
57
|
+
value
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Why the &*#^@*^&$ isn't HashWithIndifferentAccess actually doing this?
|
62
|
+
def [](key)
|
63
|
+
key = key.to_s if key.kind_of?(Symbol)
|
64
|
+
super(key)
|
65
|
+
end
|
66
|
+
|
67
|
+
# HashWithIndifferentAccess#default is broken!
|
68
|
+
define_method(:default_Hash, Hash.instance_method(:default))
|
69
|
+
|
70
|
+
##
|
71
|
+
# Allow hash.default => hash['default']
|
72
|
+
# without breaking Hash's usage of default(key)
|
73
|
+
@@no_key = [ :no_key ] # magically unique value.
|
74
|
+
def default(key = @@no_key)
|
75
|
+
key = key.to_s if key.is_a?(Symbol)
|
76
|
+
key == @@no_key ? self['default'] : default_Hash(key == @@no_key ? nil : key)
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# HashWithIndifferentAccess#update is broken!
|
81
|
+
# Hash#update returns self,
|
82
|
+
# BUT,
|
83
|
+
# HashWithIndifferentAccess#update does not!
|
84
|
+
#
|
85
|
+
# { :a => 1 }.update({ :b => 2, :c => 3 })
|
86
|
+
# => { :a => 1, :b => 2, :c => 3 }
|
87
|
+
#
|
88
|
+
# HashWithIndifferentAccess.new({ :a => 1 }).update({ :b => 2, :c => 3 })
|
89
|
+
# => { :b => 2, :c => 3 } # WTF?
|
90
|
+
#
|
91
|
+
# Subclasses should *never* override methods and break their protocols!!!!
|
92
|
+
def update(hash)
|
93
|
+
super(hash)
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Override WithIndifferentAccess#convert_value
|
99
|
+
# return instances of this class for Hash values.
|
100
|
+
def convert_value(value)
|
101
|
+
# STDERR.puts "convert_value(#{value.inspect}:#{value.class})"
|
102
|
+
value.class == Hash ? self.class.new(value).freeze : value
|
103
|
+
end
|
104
|
+
|
105
|
+
end # class ConfigHash
|
@@ -0,0 +1,104 @@
|
|
1
|
+
|
2
|
+
#++
|
3
|
+
# source: http://rubyforge.org/projects/facets/
|
4
|
+
# version: 1.7.46
|
5
|
+
# license: Ruby License
|
6
|
+
# NOTE: remove this method if the Facets gem is installed.
|
7
|
+
# BUG: weave is destructive to values in the source hash that are arrays!
|
8
|
+
# (this is acceptable for RConfig's use as the basis for weave!)
|
9
|
+
#
|
10
|
+
#--
|
11
|
+
class Hash
|
12
|
+
|
13
|
+
##
|
14
|
+
# Weaves the contents of two hashes producing a new hash.
|
15
|
+
def weave(other_hash, dont_clobber = true)
|
16
|
+
return self unless other_hash
|
17
|
+
unless other_hash.kind_of?(Hash)
|
18
|
+
raise ArgumentError, "RConfig: (Hash#weave) expected <Hash>, but was <#{other_hash.class}>"
|
19
|
+
end
|
20
|
+
|
21
|
+
self_dup = self.dup # self.clone does not remove freeze!
|
22
|
+
|
23
|
+
other_hash.each { |key, other_node|
|
24
|
+
|
25
|
+
self_dup[key] =
|
26
|
+
|
27
|
+
if self_node = self_dup[key]
|
28
|
+
|
29
|
+
case self_node
|
30
|
+
when Hash
|
31
|
+
|
32
|
+
# hash1, hash2 => hash3 (recursive +)
|
33
|
+
if other_node.is_a?(Hash)
|
34
|
+
|
35
|
+
self_node.weave(other_node, dont_clobber)
|
36
|
+
|
37
|
+
# hash, array => error (Can't weave'em, must clobber.)
|
38
|
+
elsif other_node.is_a?(Array) && dont_clobber
|
39
|
+
|
40
|
+
raise(ArgumentError, "RConfig: (Hash#weave) Can't weave Hash and Array")
|
41
|
+
|
42
|
+
# hash, array => hash[key] = array
|
43
|
+
# hash, value => hash[key] = value
|
44
|
+
else
|
45
|
+
other_node
|
46
|
+
end
|
47
|
+
|
48
|
+
when Array
|
49
|
+
|
50
|
+
# array, hash => array << hash
|
51
|
+
# array1, array2 => array1 + array2
|
52
|
+
# array, value => array << value
|
53
|
+
if dont_clobber
|
54
|
+
case other_node
|
55
|
+
when Hash
|
56
|
+
self_node << other_node
|
57
|
+
when Array
|
58
|
+
self_node + other_node
|
59
|
+
else
|
60
|
+
self_node << other_node
|
61
|
+
end
|
62
|
+
|
63
|
+
# array, hash => hash
|
64
|
+
# array1, array2 => array2
|
65
|
+
# array, value => value
|
66
|
+
else
|
67
|
+
other_node
|
68
|
+
end
|
69
|
+
|
70
|
+
else
|
71
|
+
|
72
|
+
# value, array => array.unshift(value)
|
73
|
+
if other_node.is_a?(Array) && dont_clobber
|
74
|
+
other_node.unshift(self_node)
|
75
|
+
|
76
|
+
# value1, value2 => value2
|
77
|
+
else
|
78
|
+
other_node
|
79
|
+
end
|
80
|
+
|
81
|
+
end # case self_node
|
82
|
+
|
83
|
+
# Target hash didn't have a node matching the key,
|
84
|
+
# so just add it from the source hash.
|
85
|
+
# !self_dup.has_key?(key) => self_dup.add(key, other_node)
|
86
|
+
else
|
87
|
+
other_node
|
88
|
+
end
|
89
|
+
|
90
|
+
} # other_hash.each
|
91
|
+
|
92
|
+
self_dup # return new weaved hash
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Same as self.weave(other_hash, dont_clobber) except that it weaves other hash
|
97
|
+
# to itself, rather than create a new hash.
|
98
|
+
def weave!(other_hash, dont_clobber = true)
|
99
|
+
weaved_hash = self.weave(other_hash, dont_clobber)
|
100
|
+
self.merge!(weaved_hash)
|
101
|
+
end
|
102
|
+
|
103
|
+
end # class Hash
|
104
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
#--
|
3
|
+
# Copyright (c) 2009 Rahmal Conda <rahmal@gmail.com>
|
4
|
+
#
|
5
|
+
# RConfig Exceptions
|
6
|
+
#++
|
7
|
+
|
8
|
+
# General error in config initialization or operation.
|
9
|
+
class ConfigError < StandardError; end
|
10
|
+
|
11
|
+
# Config path(s) are not set, don't exist, or Invalid in some manner
|
12
|
+
class InvalidConfigPathError < ConfigError; end
|
13
|
+
|
@@ -0,0 +1,80 @@
|
|
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
|
@@ -0,0 +1,871 @@
|
|
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
|