Class: Collapsium::Config::Configuration

Inherits:
UberHash
  • Object
show all
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

Instance Method Summary collapse

Methods included from Support::Values

array_value

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.

Parameters:

  • path (String)

    the path of the configuration file to load.

  • options (Hash) (defaults to: {})

    options hash with the following keys:

    • resolve_extensions [Boolean] flag whether to resolve configuration

      hash extensions. (see `#resolve_extensions`)
      
    • nonexistent_base [Symbol] either of :extend or :ignore; flag that determines how a nonexistent base should be treated:

      • :extend behaves as if it does exist, but refers to an empty Hash

      • :ignore does not modify the ‘.base` or `.extends` properties, and simply ignores the reference.

    • data [Hash] data Hash to pass on to the templating mechanism.



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, options = {})
  # Option defaults
  options = options.dup
  if options[:resolve_extensions].nil?
    options[:resolve_extensions] = true
  end
  if not [true, false].include?(options[:resolve_extensions])
    raise "The :resolve_extensions option must be a boolean value!"
  end
  if options[:nonexistent_base].nil?
    options[:nonexistent_base] = :ignore
  end
  if not %i[extend ignore].include?(options[:nonexistent_base])
    raise "The :nonexistent_base option must be one of :ignore or :extend!"
  end
  options[:data] ||= {}
  if not options[: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, options[:data])
  _, local_config = load_local_config(base, options[:data])

  # Merge local configuration
  config.recursive_merge!(local_config)

  # Resolve includes
  config = resolve_includes(base, config, options)

  # Create config from the result
  cfg = Configuration.new(config)

  # Now resolve config hashes that extend other hashes.
  if options[:resolve_extensions]
    cfg.resolve_extensions!(options)
  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, options)
  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 options[: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, options)
  # 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, options)

  # Merge the bases
  merge_base_values(root, value, bases, options)

  # 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, options)
  # 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 options[: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 = "", options = {})
  # 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, options)

  if children.is_a? Hash
    children.each do |key, _|
      full_key = normalize_path("#{prefix}#{separator}#{key}")
      recursive_resolve(root, full_key, options)
    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, options)
    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:

  1. If a hash can’t be extended because the base cannot be found, an error is raised.

  2. If a hash got successfully extended, the ‘extends` keyword itself is removed from the hash.

  3. 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!(options)
  # The root object is always a Hash, so has keys, which can be processed
  # recursively.
  recursive_resolve(self, "", options)
end