rconfig 0.3.3 → 0.4.0

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