Class: Collapsium::Config::Configuration
- Inherits:
-
UberHash
- Object
- UberHash
- Collapsium::Config::Configuration
- Extended by:
- Support::Values
- Includes:
- Support::Values, EnvironmentOverride
- Defined in:
- lib/collapsium-config/configuration.rb
Overview
The Config class extends UberHash by two main pieces of functionality:
-
it loads configuration files and turns them into pathed hashes, and
-
it treats environment variables as overriding anything contained in the configuration file.
For configuration file loading, a named configuration file will be laoaded if present. A file with the same name but ‘-local` appended before the extension will be loaded as well, overriding any values in the original configuration file.
For environment variable support, any environment variable named like a path into the configuration hash, but with separators transformed to underscore and all letters capitalized will override values from the configuration files under that path, i.e. ‘FOO_BAR` will override `’foo.bar’‘.
Environment variables can contain JSON only; if the value can be parsed as JSON, it becomes a Hash in the configuration tree. If it cannot be parsed as JSON, it remains a string.
Note: if your configuration file’s top-level structure is an array, it will be returned as a hash with a ‘config’ key that maps to your file’s contents. That means that if you are trying to merge a hash with an array config, the result may be unexpected.
Class Method Summary collapse
-
.load_config(path, options = {}) ⇒ Object
Loads a configuration file with the given file name.
Instance Method Summary collapse
- #fetch_base_values(root, parent, value, options) ⇒ Object
-
#initialize(*args) ⇒ Configuration
constructor
A new instance of Configuration.
- #merge_base(root, path, value, options) ⇒ Object
- #merge_base_values(root, value, bases, options) ⇒ Object
- #recursive_resolve(root, prefix = "", options = {}) ⇒ Object
-
#resolve_extensions!(options) ⇒ Object
Resolve extensions in configuration hashes.
Methods included from Support::Values
Constructor Details
#initialize(*args) ⇒ Configuration
Returns a new instance of Configuration.
76 77 78 |
# File 'lib/collapsium-config/configuration.rb', line 76 def initialize(*args) super(*args) end |
Class Method Details
.load_config(path, options = {}) ⇒ Object
Loads a configuration file with the given file name. The format is detected based on one of the extensions in FILE_TO_PARSER.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/collapsium-config/configuration.rb', line 112 def load_config(path, = {}) # Option defaults = .dup if [:resolve_extensions].nil? [:resolve_extensions] = true end if not [true, false].include?([:resolve_extensions]) raise "The :resolve_extensions option must be a boolean value!" end if [:nonexistent_base].nil? [:nonexistent_base] = :ignore end if not %i[extend ignore].include?([:nonexistent_base]) raise "The :nonexistent_base option must be one of :ignore or :extend!" end [:data] ||= {} if not [:data].is_a? Hash raise "The :data option must be a Hash!" end # Load base and local configuration files base, config = load_base_config(path, [:data]) _, local_config = load_local_config(base, [:data]) # Merge local configuration config.recursive_merge!(local_config) # Resolve includes config = resolve_includes(base, config, ) # Create config from the result cfg = Configuration.new(config) # Now resolve config hashes that extend other hashes. if [:resolve_extensions] cfg.resolve_extensions!() end return cfg end |
Instance Method Details
#fetch_base_values(root, parent, value, options) ⇒ Object
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
# File 'lib/collapsium-config/configuration.rb', line 327 def fetch_base_values(root, parent, value, ) base_paths = array_value(value["extends"]) bases = [] base_count = 0 base_paths.each do |base_path| if not base_path.start_with?(separator) base_path = "#{parent}#{separator}#{base_path}" end base_path = normalize_path(base_path) # Fetch the base value from the root. This makes full use of # PathedAccess. # We default to nil. Only Hash base values can be processed. base_value = root.fetch(base_path, nil) if not base_value.is_a? Hash bases << [base_path, nil] next end bases << [base_path, base_value] base_count += 1 end # Only delete the "extends" keyword if we found all base. if [:nonexistent_base] == :extend or \ base_count == base_paths.length value.delete("extends") end return bases end |
#merge_base(root, path, value, options) ⇒ Object
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/collapsium-config/configuration.rb', line 305 def merge_base(root, path, value, ) # If the value is not a Hash, we can't do anything here. if not value.is_a? Hash return end # If the value contains an "extends" keyword, we can find the value's # base. Otherwise there's nothing to do. if not value.include? "extends" return end # Now to resolve the path to the base and remove the "extends" keyword. bases = fetch_base_values(root, parent_path(path), value, ) # Merge the bases merge_base_values(root, value, bases, ) # And we're done, set the value to what was being merged. root[path] = value end |
#merge_base_values(root, value, bases, options) ⇒ Object
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/collapsium-config/configuration.rb', line 359 def merge_base_values(root, value, bases, ) # We need to recursively resolve the base values before merging them into # value. To preserve the override order, we need to overwrite values when # merging bases... merged_base = Configuration.new bases.each do |base_path, base_value| if not base_value.nil? base_value.recursive_resolve(root, base_path) merged_base.recursive_merge!(base_value, true) end # Modify bases for this path: we go depth first into the hierarchy if [:nonexistent_base] == :ignore and base_value.nil? next end base_val = merged_base.fetch("base", []).dup base_val << base_path base_val.uniq! merged_base["base"] = base_val end # ... but value needs to stay authoritative. value.recursive_merge!(merged_base, false) end |
#recursive_resolve(root, prefix = "", options = {}) ⇒ Object
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/collapsium-config/configuration.rb', line 283 def recursive_resolve(root, prefix = "", = {}) # The self object is a Hash or an Array. Let's iterate over its children # one by one. Defaulting to a Hash here is just convenience, it could # equally be an Array. children = root.fetch(prefix, {}) merge_base(root, prefix, children, ) if children.is_a? Hash children.each do |key, _| full_key = normalize_path("#{prefix}#{separator}#{key}") recursive_resolve(root, full_key, ) end elsif children.is_a? Array children.each_with_index do |_, idx| key = idx.to_s full_key = normalize_path("#{prefix}#{separator}#{key}") recursive_resolve(root, full_key, ) end end end |
#resolve_extensions!(options) ⇒ Object
Resolve extensions in configuration hashes. If your hash contains e.g.:
“‘yaml
foo:
bar:
some: value
baz:
extends: bar
“‘
Then ‘’foo.baz.some’‘ will equal `’value’‘ after resolving extensions. Note that `:load_config` calls this function, so normally you don’t need to call it yourself. You can switch this behaviour off in ‘:load_config`.
Note that this process has some intended side-effects:
-
If a hash can’t be extended because the base cannot be found, an error is raised.
-
If a hash got successfully extended, the ‘extends` keyword itself is removed from the hash.
-
In a successfully extended hash, an ‘base` keyword, which contains the name of the base. In case of multiple recursive extensions, the final base is stored here.
Also note that all of this means that :extends and :base are reserved keywords that cannot be used in configuration files other than for this purpose!
277 278 279 280 281 |
# File 'lib/collapsium-config/configuration.rb', line 277 def resolve_extensions!() # The root object is always a Hash, so has keys, which can be processed # recursively. recursive_resolve(self, "", ) end |