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

load(profile, files: CONFIGURATION_FILES, dir: nil) click to toggle source

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
read(profile, *streams) click to toggle source

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