To start using jac you need to add
gem 'jac', '~> 0.0.5'
to your Gemfile
and load configuration from default paths (jac.yml
, jac.user.yml
) relative to working dir:
require 'jac'
profile = %w[any profile combination]
Jac::Configuration.load(profile) # => OpenStruct
or to load custom set of files:
require 'jac'
profile = %w[any profile combination]
Jac::Configuration.load(profile, files: %w[example/config/base.yml example/config/custom.yml]) # => OpenStruct
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
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.
One profile can extend
another. Or any amount of other
profiles. To specify this one should use extends
field
like that
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:
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
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
# .application.yml
base:
user: deployer
debug:
extends: base
# ... other values
# .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
)
Configuration resolver comes with powerful yet dangerous feature: it allows evaluate strings as ruby expressions like this:
foo:
build_time: "#{Time.now}" # Will be evaluated at configuration resolving step
Configuration values are available to and can be referenced with c
:
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
local values can be referenced through self, e.g.:
default:
project:
network: fb
server_name: "#{self['network']}-srv" # => 'fb-srv'
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
.
By default if one value have multiple defenitions it will be overwritten by topmost value. Except several cases where Jac handles value resolution specialy
Hashes inside profile are recurseively merged automatically. This rule works for profile extensions and value redefenitions too.
Example:
base:
servers:
release: 'http://release.com'
debug:
extends: base
servers: # will contain {'debug' => 'https://debug.com', 'release' => 'https://release.com'}
debug: 'http://debug.com'
Sets allowed to be merged with nil
s and any instance of Enumerable
.
Merge result is always new Set
instance.
Example:
release:
extends:
- no_rtti
- no_debug
flags: !set {} # empty set
no_rtti:
flags:
- '-fno-rtti'
no_debug:
flags:
- '-DNDEBUG'
Resulting profile will have Set('-fno-rtti', '-DNDEBUG')
in release profile
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.
If running on Ruby 2.4+ you can use named captures in generic profiles. Named captures will be stored in
c.named_captures
asHash
.
jac
has two implicit profiles which automaticly aplied to any configuration
it produces: ^base
and ^top
(note ^
at the begining of profile name).
If for some reason you need to provide base values for any profile you use
you should define them in ^base
profile. And if you need always apply some
values onto any configuration you receive you should define them in ^top
profile.
^base:
java: 1.7 # Use java 1.7 by default
^top:
Xmx: 512M # Override any Xmx value if present
Using this example we can get default
profile containing
java: 1.7
Xmx: 512M
jac is licensed under the MIT licence. Please see the LICENSE for more information.