vizor-games/jac

View on GitHub
Readme.md

Summary

Maintainability
Test Coverage
# Jac - just another configuration lib

[![Gem](https://img.shields.io/gem/v/jac.svg)](https://rubygems.org/gems/jac)
[![Coverage Status](https://img.shields.io/codeclimate/coverage/vizor-games/jac.svg)](https://codeclimate.com/github/vizor-games/jac)
[![Code Climate](https://codeclimate.com/github/vizor-games/jac/badges/gpa.svg)](https://codeclimate.com/github/vizor-games/jac)
[![Build Status](https://travis-ci.org/vizor-games/jac.svg?branch=master)](https://travis-ci.org/vizor-games/jac)

## Installation and usage

To start using jac you need to add 

```ruby
gem 'jac', '~> 0.0.5'
```

to your `Gemfile` and load configuration from default paths (`jac.yml`, `jac.user.yml`) relative to working dir:

```ruby
require 'jac'
profile = %w[any profile combination]
Jac::Configuration.load(profile) # => OpenStruct
```

or to load custom set of files:

```ruby
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

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

_local_ values can be referenced through self, e.g.:

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

```yml
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 `nil`s and any instance of `Enumerable`.
Merge result is always new `Set` instance.

Example: 
```yml
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 `/.../`):

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

```yaml
^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](LICENSE) for more information.