module Jac::Configuration
Configuration
is loaded from well formed YAML streams. Each document expected to be key-value mapping where keys a `profile` names and values is a profile content. Profile itself is key-value mapping too. Except reserved key names (i.e. `extends`) each key in profile is a configuration field. For example following yaml document
“`yml
foo: bar: 42 qoo: bar: 32
“`
represents description of two profiles, `foo` and `qoo`, where field `bar` is set to `42` and `32` respectively.
Profile can be constructed using combination of other profiles for example having `debug` and `release` profiles for testing and production. And having `remote` and `local` profiles for building on local or remote machine. We cant get `debug,local`, `debug,remote`, `release,local` and `release,remote` profiles. Each of such profiles is a result of merging values of listed profiles. When merging profile with another configuration resolver overwrites existing fields. For example if `debug` and `local` for some reason have same field. In profile `debug,local` value from `debug` profile will be overwritten with value from `local` profile.
## Extending profiles
One profile can `extend` another. Or any amount of other profiles. To specify this one should use `extends` field like that
“`yml
base:
application_name: my-awesome-app port: 80
version:
version_name: 0.0.0 version_code: 42
debug:
extends: [base, version] port: 9292
“`
In this example `debug` will receive following fields:
“`yml application_name: my-awesome-app # from base profile port: 9292 # from debug profile version_name: 0.0.0 # from version profile version_code: 42 # from version profile “`
## Merging multiple configuration files
Configuration
can be loaded from multiple YAML documents. Before resolve requested profile all described profiles are merged down together. Having sequence of files like `.application.yml`, `.application.user.yml` with following content
“`yml # .application.yml base:
user: deployer
debug:
extends: base # ... other values
“`
“`yml # .application.user.yml base:
user: developer
“`
We'll get `user` field overwritten with value from `.application.user.yml`. And only after that construction of resulting profile will begin (for example `debug`)
## String evaluation
Configuration
resolver comes with powerful yet dangerous feature: it allows evaluate strings as ruby expressions like this:
“`yml foo:
build_time: "#{Time.now}" # Will be evaluated at configuration resolving step
“`
Configuration
values are available to and can be referenced with `c`:
“`yml base:
application_name: my-awesome-app
debug:
extends: base server_name: "#{c.application_name}-debug" # yields to my-awesome-app-debug
release:
extends: base server_name: "#{c.application_name}-release" # yields to my-awesome-app-release
“`
All strings evaluated after profile is constructed thus you don't need to have declared values in current profile but be ready to get `nil`.
## Merging values
By default if one value have multiple defenitions it will be overwritten by topmost value. Except several cases where Jac
handles value resolution specialy
### Merging hashes
Hashes inside profile are recurseively merged automaticly. This rule works for profile extensions and value redefenitions too.
Example:
“`yml base:
servers: release: http://release.com
debug:
extends: base debug: http://debug.com
“`
### Merging sets
Sets allowed to be merged with `nil`s and any instance of `Enumerable`. Merge result is always new `Set` instance. “`yml release:
extends: - no_rtti - no_debug flags: !set {} # empty set
no_rtti:
flags: - -fno-rtti
no_debug:
flags: - -DNDEBUG
“`
Resulting profile will have `-fno-rtti, -DNDEBUG` in `release profile` ## Generic profiles
Same result as shown above can be achieved with generic profiles. Generic profile is a profile which name is regex (i.e. contained in `/…/`):
“` base:
application_name: my-awesome-app
/(release|debug)/: # Profile name is a regex, with single capture (1)
extends: base server_name: "#{c.application_name}-#{c.captures[1]}" # yields my-awesome-app-release or my-awesome-app-debug
“`
If profile name matches multiple generic profiles it not defined which profile will be used.
Constants
- BASE_PROFILE_NAME
Name of base level profile. ^base profile is implicit resulting configuration automaticly merged on top of it.
- BASIC_PROFILES
List of default profiles. Having this list we can easily create default configuration.
- CONFIGURATION_FILES
List of files where configuration can be placed
-
`jac.yml` - expected to be main configuration file.
Usually it placed under version control.
-
`jac.user.yml` - user defined overrides for main
configuration, sensitive data which can't be placed under version control.
-
`jac.override.yml` - final overrides.
-
- DEFAULT_PROFILE_NAME
Any configuration set always contains `default` profile which is loaded when no profile requested.
- EXTENDS_KEY
Key where all inherited profiles listed
- TOP_PROFILE_NAME
Name of top level profile. ^top profile is implicit it automaticly merged on top of resulting configuration
Public Class Methods
Read configuration from configuration files. @praram [String or Array] profile which should be loaded @param [Array] files filenames to load @param [String] dir base directory path for provided files may be nil @return [OpenStruct] resolved profile values
# File lib/jac/configuration.rb, line 492 def load(profile, files: CONFIGURATION_FILES, dir: nil) # Read all known files streams = files .map { |f| [dir ? File.join(dir, f) : f, f] } .select { |path, _name| File.exist?(path) } .map { |path, name| [IO.read(path), name] } read(profile, *streams) end
Generates configuration object for given profile and list of streams with YAML document @param profile [Array] list of profile names to merge @param streams [Array] list of YAML documents and their names to read @return [OpenStruct] instance which contains all resolved profile fields
# File lib/jac/configuration.rb, line 482 def read(profile, *streams) profile = [Configuration::DEFAULT_PROFILE_NAME] if profile.empty? ConfigurationReader.new(streams).read(*profile) end