README.md

## UCL: BEAM-friendly bindings for `libUCL`

[LibUCL] is a human-friendly, automation-oriented parser and generator
for `Universal Configuration Language` or `UCL`. UCL's main benefits
are:

- human readable and editable with comments
- machine parsable
- conversion to/from YAML, JSON
- macro and include-file support
- lightning-fast parser

`UCL` is a NIF-based binding to the reference implementation [LibUCL],
and currently requires that library to be pre-installed on your system.

It does not use dirty schedulers, nor does it time-slice NIF calls, as
this would make the code significantly more complex for a function that
is unlikely to be run frequently within the BEAM.

UCL comes with friendly functions for *both* Elixir and Erlang, we are
all friends here.

## Installation & Compilation

### Dependencies

`libucl` is obviously a dependency, and the usual UNIX build chain is
required.

You will need Erlang/OTP 25 or higher, because the build tool `enc`
is a pre-compiled escript.

- FreeBSD:  `textproc/libucl devel/rebar3`
- OSX:      `brew install libucl`
- Debian:   `build-essential` from base
            `erlang-dev erlang-nox` from erlang-solutions
            `rebar3` from https://rebar3.org/
- OpenBSD:  `erlang-25 elixir` from base
            `rebar3` from https://rebar3.org/

On some systems, LibUCL is missing and you'll need to build it.
On Debian, there is a conflicting compression library of the same name.

## Building LibUCL from source

### OpenBSD

```
$ doas pkg_add pkgconf autogen autoconf-archive autoconf-2.69 \
    automake-1.11 libtool
$ tar xzf libucl-*.gz
$ cd libucl*
$ ./autogen.sh
$ ./configure
$ make
$ doas make install
```

### Debian

```
$ sudo apt install autoconf libcurl4-openssl-dev automake libtool \
    autoconf-archive pkg-config
$ tar xzf libucl-*.gz
$ cd libucl*
$ ./autogen.sh
$ ./configure --prefix=/usr
$ make
$ sudo make install
```

### Installation

Via git clone:

```
$ export PATH=$PATH:/usr/local/lib/erlang25/bin:$HOME/.mix
$ mix local.rebar
$ rebar3 do clean, compile, ct
```

Via [hex] in the usual fashion:

- Elixir: add `{:ucl, "~> 1.0"}` to `mix.exs`
- Erlang: add `{ucl, "1.1.0"}` to `rebar.config`

Compilation should work on all UNIX-like OS out of the box. There is
o Windows support planned, but if you can compile `libucl` on Windows,
get in touch.

Tests are available as usual via `rebar3 ct`, and long-running property
tests via `rebar3 proper`. The property tests are effectively fuzzing
the NIF by injecting random `binary()` data and expecting it to return
a classic `{ok | error, any()}` tuple, and take a considerable amount of
time (several hours):

```
$ time rebar3 proper
... 3 hours later ...
00% {60000,66000}
10% {54000,60000}
09% {48000,54000}
10% {42000,48000}
10% {36000,42000}
09% {30000,36000}
09% {24000,30000}
09% {18000,24000}
09% {12000,18000}
10% {6000,12000}
9% {0,6000}
===> 1/1 properties passed
OK: Passed 500000 test(s).
11005.99 real     11039.08 user        86.36 sys
```

Although `UCL` is written in Erlang, and uses `rebar3` ,it should
compile cleanly as a dependency on any BEAM language.

## Usage

```elixir
iex> UCL.to_json(":foo")
     {:error, :ucl_invalid}
iex> "foo: true" |> UCL.to_json! |> Jason.decode!
     %{"foo" => true}
```

```erlang
1> ucl:to_json(<<"foo: true">>).
    {ok,<<"{\n    \"foo\": true\n}">>}
```

The implementation is intended for functional transformation of config
data held in memory, and thus a large portion of the LibUCL API has been
summarily ignored. If you need something please let us know.

## Universal Configuration Language Syntax

UCL is heavily infused by `nginx` configuration as the example of a
convenient configuration system. However, UCL is fully compatible with
`JSON` format and is able to parse json files. For example, you can
write the same configuration in the following ways:

* in nginx like:

```nginx
param = value;
section {
    param = value;
    param1 = value1;
    flag = true;
    number = 10k;
    time = 0.2s;
    string = "something";
    subsection {
        host = {
            host = "hostname";
            port = 900;
        }
        host = {
            host = "hostname";
            port = 901;
        }
    }
}
```

* or in JSON:

```json
{
    "param": "value",
    "param1": "value1",
    "flag": true,
    "subsection": {
        "host": [
        {
            "host": "hostname",
            "port": 900
        },
        {
            "host": "hostname",
            "port": 901
        }
        ]
    }
}
```

## Improvements to the json notation.

There are various things that make ucl configuration more convenient for
editing than strict json:

### General syntax sugar

* Braces are not necessary to enclose a top object: it is automatically
  treated as an object:

```json
"key": "value"
```
is equal to:
```json
{"key": "value"}
```

* There is no requirement of quotes for strings and keys, moreover, `:`
  may be replaced `=` or even be skipped for objects:

```nginx
key = value;
section {
    key = value;
}
```
is equal to:
```json
{
    "key": "value",
    "section": {
        "key": "value"
    }
}
```

* No commas mess: you can safely place a comma or semicolon for the last
  element in an array or an object:

```json
{
    "key1": "value",
    "key2": "value",
}
```
### Automatic arrays creation

* Non-unique keys in an object are allowed and are automatically
  converted to the arrays internally:

```json
{
    "key": "value1",
    "key": "value2"
}
```
is converted to:
```json
{
    "key": ["value1", "value2"]
}
```

### Named keys hierarchy

UCL accepts named keys and organize them into objects hierarchy
internally. Here is an example of this process:

```nginx
section "blah" {
	key = value;
}
section foo {
	key = value;
}
```

is converted to the following object:

```nginx
section {
	blah {
		key = value;
	}
	foo {
		key = value;
	}
}
```

Plain definitions may be more complex and contain more than a single
level of nested objects:

```nginx
section "blah" "foo" {
	key = value;
}
```

is presented as:

```nginx
section {
	blah {
		foo {
			key = value;
		}
	}
}
```

### Convenient numbers and booleans

* Numbers can have suffixes to specify standard multipliers:
    + `[kKmMgG]` - standard 10 base multipliers (so `1k` is translated to 1000)
    + `[kKmMgG]b` - 2 power multipliers (so `1kb` is translated to 1024)
    + `[s|min|d|w|y]` - time multipliers, all time values are translated to float number of seconds, for example `10min` is translated to 600.0 and `10ms` is translated to 0.01
* Hexadecimal integers can be used by `0x` prefix, for example `key = 0xff`. However, floating point values can use decimal base only.
* Booleans can be specified as `true` or `yes` or `on` and `false` or `no` or `off`.
* It is still possible to treat numbers and booleans as strings by enclosing them in double quotes.

## General improvements

### Comments

UCL supports different style of comments:

* single line: `#`
* multiline: `/* ... */`

Multiline comments may be nested:
```c
# Sample single line comment
/*
 some comment
 /* nested comment */
 end of comment
*/
```

## Thanks

- Paul Davis, whose well documented [NIF examples] made writing this almost cake

[NIF examples]: https://github.com/davisp/nif-examples
[hex]: https://hex.pm/packages/ucl
[LibUCL]: https://github.com/vstakhov/libucl