rconfig 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|