Jac - just another configuration lib
Installation and usage
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) # => OpenStructor 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]) # => OpenStructFeatures
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.
Extending profiles
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: 9292In 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 profileMerging 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
# .application.yml
base:
user: deployer
debug:
extends: base
# ... other values# .application.user.yml
base:
user: developerWe'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:
foo:
build_time: "#{Time.now}" # Will be evaluated at configuration resolving stepConfiguration 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-releaselocal 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.
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 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'
Merging sets
Sets allowed to be merged with nils 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
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-debugIf 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_capturesasHash.
Implicit profiles
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 presentUsing this example we can get default profile containing
java: 1.7
Xmx: 512M
License
jac is licensed under the MIT licence. Please see the LICENSE for more information.