rconfig 0.3.3 → 0.4.0

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