Project

jac

0.0
No commit activity in last 3 years
No release in over 3 years
Profile based configuration lib
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

Jac - just another configuration lib

Gem Coverage Status Code Climate Build Status

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) # => 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

Features

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: 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

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

# .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)

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 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.

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-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 as Hash.

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 present

Using 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.