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,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