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,138 @@
1
+ ##
2
+ # Copyright (c) 2009 Rahmal Conda <rahmal@gmail.com>
3
+ #
4
+ # This class parses key/value based properties files used for configuration.
5
+ # It is used by rconfig to import configuration files of the aforementioned
6
+ # format. Unlike yaml, and xml files it can only support three levels. First,
7
+ # it can have root level properties:
8
+ #
9
+ # server_url=host.domain.com
10
+ # server_port=8080
11
+ #
12
+ # Secondly, it can have properties grouped into catagories. The group names
13
+ # must be specified within brackets like [ ... ]
14
+ #
15
+ # [server]
16
+ # url=host.domain.com
17
+ # port=8080
18
+ #
19
+ # Finally, groups can also be qualified with namespaces, similar to git
20
+ # config files. Group names are same as before, but with namespace in
21
+ # within the brackets like [ <group> "<name>" ]
22
+ #
23
+ # [host "dev"]
24
+ # domain=dev.server.com
25
+ #
26
+ # [host "prod"]
27
+ # domain=www.server.com
28
+ #
29
+ # These can be retrieved using dot-notation or variable to do it dynamically.
30
+ #
31
+ # RConfig.props.host.dev.domain
32
+ # - or -
33
+ # RConfig.props.host[env].domain (where env is 'dev' or 'prod')
34
+ #
35
+ module RConfig
36
+ class PropertiesFile
37
+ include Singleton # Don't instantiate this class
38
+
39
+ COMMENT = /^\#/
40
+ KEYVAL = /\s*=\s*/
41
+ QUOTES = /^['"](.*)['"]$/
42
+ GROUP = /^\[(.+)\]$/
43
+ NAMEGRP = /^\[(.+) \"(.+)\"\]$/
44
+ KEY_REF = /(.+)?\%\{(.+)\}(.+)?/
45
+
46
+ ##
47
+ # Parse config file and import data into hash to be stored in config.
48
+ #
49
+ def self.parse(config_file)
50
+ raise ArgumentError, 'Invalid config file name.' unless config_file
51
+
52
+ config = {}
53
+
54
+ # The config is top down.. anything after a [group] gets added as part
55
+ # of that group until a new [group] is found.
56
+ group, topgrp = nil
57
+ config_file.split("\n").each do |line| # for each line in the config file
58
+ line.strip!
59
+ unless COMMENT.match(line) # skip comments (lines that state with '#')
60
+ if KEYVAL.match(line) # if this line is a config property
61
+ key, val = line.split(KEYVAL, 2) # parse key and value from line
62
+ key = key.chomp.strip
63
+ val = val.chomp.strip
64
+ if val
65
+ if val =~ QUOTES # if the value is in quotes
66
+ value = $1 # strip out value from quotes
67
+ else
68
+ value = val # otherwise, leave as-is
69
+ end
70
+ else
71
+ value = '' # if value was nil, set it to empty string
72
+ end
73
+
74
+ if topgrp # If there was a top-level named group, then there must be a group.
75
+ config[topgrp][group][key] = value # so add the prop to the named group
76
+ elsif group # if this property is part of a group
77
+ config[group][key] = value # then add it to the group
78
+ else # otherwise...
79
+ config[key] = value # add the prop to top-level config
80
+ end
81
+
82
+ elsif match = NAMEGRP.match(line) # This line is a named group (i.e. [env "test"], [env "qa"], [env "production"])
83
+ topgrp, group = match.to_a[1..-1] # There can be multiple groups within a single top-level group
84
+ config[topgrp] ||= {} # add group to top-level group
85
+ config[topgrp][group] ||= {} # add name of group as subgroup (properties are added to subgroup)
86
+
87
+ elsif match = GROUP.match(line) # if this line is a config group
88
+ group = match.to_a[1] # parse the group name from line
89
+ topgrp = nil # we got a new group with no namespace, so reset topgrp
90
+ config[group] ||= {} # add group to top-level config
91
+ end
92
+ end
93
+ end
94
+
95
+ # Pre-populate the values that refer to other properties
96
+ # Example:
97
+ # root_path = /path/to/root
98
+ # image_path = %{root_path}/images
99
+ config = parse_references(config)
100
+
101
+ config # return config hash
102
+ end # def parse
103
+
104
+ ##
105
+ # Recursively checks all the values in the hash for references to other properties,
106
+ # and replaces the references with the actual values. The syntax for referring to
107
+ # another property in the config file is the ley of the property wrapped in curly
108
+ # braces, preceded by a percent sign. If the property is in a group, then the full
109
+ # namespace must be used.
110
+ #
111
+ # Example:
112
+ # root_path = /path/to/root # In this property file snippet
113
+ # image_path = %{root_path}/images # image path refers to root path
114
+ #
115
+ #
116
+ def self.parse_references hash, config=nil
117
+ config ||= hash.dup
118
+
119
+ hash.each do |key, val|
120
+ case val
121
+ when Hash
122
+ hash[key] = parse_references(val, config)
123
+ when String
124
+ pre, ref, post = KEY_REF.match(val).to_a[1..-1]
125
+ hash[key] = if ref
126
+ ref = ref.split('.').inject(config){|c,i| c && c[i] }
127
+ [pre, ref, post].join
128
+ else
129
+ val
130
+ end
131
+ end
132
+ end
133
+
134
+ hash
135
+ end # def parse_references
136
+
137
+ end # class PropertiesFileParser
138
+ end # module RConfig
@@ -0,0 +1,75 @@
1
+ module RConfig
2
+ module Reload
3
+
4
+ ##
5
+ # Flag indicating whether or not reload should be executed.
6
+ def reload?
7
+ self.enable_reload
8
+ end
9
+
10
+ def reload_disabled?
11
+ not reload?
12
+ end
13
+
14
+ ##
15
+ # Sets the flag indicating whether or not reload should be executed.
16
+ def enable_reload=(reload)
17
+ raise ArgumentError, 'Argument must be true or false.' unless [true, false].include?(reload)
18
+ self.enable_reload = reload
19
+ end
20
+
21
+ ##
22
+ # Sets the number of seconds between reloading of config files
23
+ # and automatic reload checks. Defaults to 5 minutes. Setting
24
+ #
25
+ def reload_interval=(interval)
26
+ raise ArgumentError, 'Argument must be Integer.' unless interval.kind_of?(Integer)
27
+ self.enable_reload = false if interval == 0 # Sett
28
+ self.reload_interval = interval
29
+ end
30
+
31
+ ##
32
+ # Flushes cached config data, so that it can be reloaded from disk.
33
+ # It is recommended that this should be used with caution, and any
34
+ # need to reload in a production setting should minimized or
35
+ # completely avoided if possible.
36
+ def reload(force=false)
37
+ raise ArgumentError, 'Argument must be true or false.' unless [true, false].include?(force)
38
+ if force || reload?
39
+ flush_cache
40
+ return true
41
+ end
42
+ false
43
+ end
44
+
45
+ ##
46
+ # Executes given block, without reloading any config. Meant to
47
+ # run configuration-sensitive code that may otherwise trigger a
48
+ # reload of any/all config files. If reload is disabled then it
49
+ # makes no difference if this wrapper is used or not.
50
+ # Returns result of the block
51
+ def without_reload(&block)
52
+ return unless block_given?
53
+ result = nil
54
+ enable_reload_cache = self.enable_reload
55
+ begin
56
+ self.enable_reload
57
+ result = yield
58
+ ensure
59
+ self.enable_reload = enable_reload_cache
60
+ check_config_changed if reload?
61
+ end
62
+ result
63
+ end
64
+
65
+ def auto_check?(name)
66
+ now = Time.now
67
+ if (!self.last_auto_check[name]) || (now - self.last_auto_check[name]) > self.reload_interval
68
+ self.last_auto_check[name] = now
69
+ return true
70
+ end
71
+ return false
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,93 @@
1
+ module RConfig
2
+ module Settings
3
+ extend Utils
4
+ include Constants
5
+
6
+ ### Configuration Settings ====>
7
+
8
+ # Load paths for configuration files. Add folders to this load path
9
+ # to load up other resources for administration. External gems can
10
+ # include their paths in this load path to provide active_admin UIs
11
+ setting :load_paths, default_load_paths
12
+
13
+ # Custom cascade used when cascading configurations to override default
14
+ # config files. Can be used to add locale or branch specific configs.
15
+ setting :overlay, false
16
+
17
+ # The type of configuration files to load Supported file types are yaml,
18
+ # xml, and property files (.property).
19
+ setting :file_types, CONFIG_FILE_TYPES
20
+
21
+ # The logger rconfig will log to.
22
+ setting :logger, RConfig::Logger.new
23
+
24
+ # Indicates whether or not periodic reloads should be performed.
25
+ setting :enable_reload, false
26
+
27
+ # How often periodic reloads should be performed in seconds.
28
+ # The number of seconds between reloading of config files
29
+ # and automatic reload checks. Defaults to 5 minutes.
30
+ setting :reload_interval, 300 # 5 min
31
+
32
+ ### Initialize Configuration Cache ====>
33
+
34
+ # Primary configuration cache for RConfig.
35
+ # Hash of yaml file names and their respective contents,
36
+ # last modified time, and the last time it was loaded.
37
+ # self.cache[filename] = [yaml_contents, mod_time, time_loaded]
38
+ setting :cache, {}
39
+
40
+ # File cache used to store loaded configuration files.
41
+ # Hash of config file base names and their existing filenames
42
+ # including the suffixes.
43
+ # self.cache_files['ldap'] = ['ldap.yml', 'ldap_local.yml', 'ldap_<hostname>.yml']
44
+ setting :cache_files, {}
45
+
46
+ # Cache key-value lookup
47
+ # Hash of config base name and the contents of all its respective
48
+ # files merged into hashes. This hash holds the data that is
49
+ # accessed when RConfig is called. This gets re-created each time
50
+ # the config files are loaded.
51
+ # self.cache_hash['ldap'] = config_hash
52
+ setting :cache_hash, {}
53
+
54
+ # This hash holds the same info as self.cache_hash, but it is only
55
+ # loaded once. If reload is disabled, data will from this hash
56
+ # will always be passed back when RConfig is called.
57
+ setting :cache_config_files, {}
58
+
59
+ # Hash of suffixes for a given cascading configuration name.
60
+ # The suffixes are used to load cascading configurations for
61
+ # a specified name.
62
+ # Example:
63
+ # self.suffixes[:name] #=> %w[ name_development name_staging name_production name_GB ]
64
+ setting :suffixes, {}
65
+
66
+ # Hash of callbacks, mapped to a given config file. Each collection of procs are
67
+ # executed when the config file they are mapped to has been reloaded.
68
+ setting :callbacks, {}
69
+
70
+ # Hash of config base name and the last time it was checked for
71
+ # update.
72
+ # self.last_auto_check['ldap'] = Time.now
73
+ setting :last_auto_check, {}
74
+
75
+ # Helper variable for white-box testing and debugging.
76
+ # A hash of each file that has been loaded.
77
+ setting :config_loaded
78
+
79
+ # Magically unique value. Used to provide a key to retrieve a default value
80
+ # specified in a config file. The symbol is wrapped in an array so that it will
81
+ # not be treated like a normal key and changed to a string.
82
+ #
83
+ # Example:
84
+ # currency:
85
+ # us: dollar
86
+ # gb: pound
87
+ # default: dollar
88
+ #
89
+ # RConfig.currency.ca => 'dollar'
90
+ setting :default_key, [:default_key].freeze
91
+
92
+ end
93
+ end
@@ -0,0 +1,175 @@
1
+ module RConfig
2
+ module Utils
3
+ include Constants
4
+
5
+ # Used to customize configuration of RConfig. Run 'rails generate rconfig:install' to create
6
+ # a fresh initializer with all configuration values.
7
+ def setup
8
+ yield self
9
+ raise_load_path_error if load_paths.empty?
10
+ self.logger ||= DisabledLogger.new
11
+ end
12
+
13
+ # Creates a class variable a sets it to the default value specified.
14
+ def setting(name, default=nil)
15
+ RConfig.class_eval <<-EOC, __FILE__, __LINE__ + 1
16
+ def self.#{name}
17
+ @@#{name}
18
+ end
19
+ def self.#{name}=(val)
20
+ @@#{name} = val
21
+ end
22
+ EOC
23
+
24
+ RConfig.send(:"#{name}=", default)
25
+ end
26
+
27
+ # Checks environment for default configuration load paths. Adds them to load paths if found.
28
+ def default_load_paths
29
+ paths = []
30
+
31
+ # Check for Rails config path
32
+ paths << "#{::Rails.root}/config" if rails?
33
+
34
+ # Check for defined constants
35
+ paths << CONFIG_ROOT if defined?(CONFIG_ROOT) && Dir.exists?(CONFIG_ROOT)
36
+ paths << CONFIG_PATH if defined?(CONFIG_PATH) && Dir.exists?(CONFIG_PATH)
37
+
38
+ # Check for config directory in app root
39
+ config_dir = File.join(app_root, 'config')
40
+ paths << config_dir if Dir.exists?(config_dir)
41
+
42
+ paths
43
+ end
44
+
45
+ ##
46
+ # Returns true if the current application is a rails app.
47
+ def rails?
48
+ !!defined?(::Rails)
49
+ end
50
+
51
+ ##
52
+ #
53
+ def app_root
54
+ File.expand_path(File.dirname(__FILE__))
55
+ end
56
+
57
+ ##
58
+ # Helper method for white-box testing and debugging.
59
+ # Sets the flag indicating whether or not to log
60
+ # errors and application run-time information.
61
+ def log_level=(level)
62
+ return unless logger
63
+ logger.level = level unless level.nil?
64
+ end
65
+
66
+ def log_level
67
+ logger.try(:level)
68
+ end
69
+
70
+ ##
71
+ # Creates a dottable hash for all Hash objects, recursively.
72
+ def create_dottable_hash(hash)
73
+ make_indifferent(hash)
74
+ end
75
+
76
+ ##
77
+ # Reads and parses the config data from the specified file.
78
+ def read(file, name, ext)
79
+ contents = File.read(file) # Read the contents from the file.
80
+ contents = ERB.new(contents).result # Evaluate any ruby code using ERB.
81
+ parse(contents, name, ext) # Parse the contents based on the file type
82
+ end
83
+
84
+ ##
85
+ # Parses contents of the config file based on file type.
86
+ # XML files expect the root element to be the same as the
87
+ # file name.
88
+ #
89
+ def parse(contents, name, ext)
90
+ hash = case ext
91
+ when *YML_FILE_TYPES
92
+ YAML::load(contents)
93
+ when *XML_FILE_TYPES
94
+ parse_xml(contents, name)
95
+ when *CNF_FILE_TYPES
96
+ RConfig::PropertiesFile.parse(contents)
97
+ else
98
+ raise ConfigError, "Unknown File type: #{ext}"
99
+ end
100
+ hash.freeze
101
+ end
102
+
103
+ ##
104
+ # Parses xml file and processes any references in the property values.
105
+ def parse_xml(contents, name)
106
+ hash = Hash.from_xml(contents)
107
+ hash = hash[name] if hash.size == 1 && hash.key?(name) # xml document could have root tag matching the file name.
108
+ RConfig::PropertiesFile.parse_references(hash)
109
+ end
110
+
111
+ ##
112
+ # Returns a merge of hashes.
113
+ #
114
+ def merge_hashes(hashes)
115
+ hashes.inject({}) { |n, h| n.weave(h, true) }
116
+ end
117
+
118
+ ##
119
+ # Recursively makes hashes into frozen IndifferentAccess Config Hash
120
+ # Arrays are also traversed and frozen.
121
+ #
122
+ def make_indifferent(hash)
123
+ case hash
124
+ when Hash
125
+ unless hash.frozen?
126
+ hash.each do |k, v|
127
+ hash[k] = make_indifferent(v)
128
+ end
129
+ hash = RConfig::Config.new.merge!(hash).freeze
130
+ end
131
+ logger.debug "make_indefferent: x = #{hash.inspect}:#{hash.class}"
132
+ when Array
133
+ unless hash.frozen?
134
+ hash.collect! do |v|
135
+ make_indifferent(v)
136
+ end
137
+ hash.freeze
138
+ end
139
+ # Freeze Strings.
140
+ when String
141
+ hash.freeze
142
+ end
143
+ hash
144
+ end
145
+
146
+ ##
147
+ # If a config file name is specified, flushes cached config values
148
+ # for specified config file. Otherwise, flushes all cached config data.
149
+ # The latter should be avoided in production environments, if possible.
150
+ def flush_cache(name=nil)
151
+ if name
152
+ name = name.to_s
153
+ self.cache_hash[name] &&= nil
154
+ else
155
+ logger.warn "RConfig: Flushing config data cache."
156
+ self.suffixes = {}
157
+ self.cache = {}
158
+ self.cache_files = {}
159
+ self.cache_hash = {}
160
+ self.last_auto_check = {}
161
+ self
162
+ end
163
+ end
164
+
165
+ ##
166
+ # Get complete file name, including file path for the given config name
167
+ # and directory.
168
+ def filename_for_name(name, directory=self.load_paths.first, ext=:yml)
169
+ File.join(directory, "#{name}.#{ext}")
170
+ end
171
+
172
+
173
+
174
+ end
175
+ end