NACL is a configuration data language intended to be both human and machine friendly. Although it's a JSON superset which means that JSON can be used as valid input to the NACL parser, the primary motivation behind NACL is representation and interpretation of configuration data, by opposition to traditional data representation languages like JSON or YAML, that define themselves as data object representation and data serialization respectively, which would belong to the general data representation languages domain, and thus quickly show weaknesses within the application configuration domain.
Thanks to Vsevolod Stakhov who created UCL after having felt that XML, as a configuration language, wasn't up to the task. NACL is heavily inspired by Vsevolod Stakhov's UCL (Universal Configuration Language).
This project contains both the NACL specification, and it's implementation as a PHP library. A detailed NACL grammar reference is also available in EBNF.
- Nuglif Application Configuration Language (NACL)
- The PHP Library
- Authors
- License
application {
debug off;
buffer 10MB;
mysql {
host .env (default: "127.0.0.1") MYSQL_HOST;
username .env (default: root) MYSQL_USERNAME;
password .env (default: root) MYSQL_PASSWORD;
port .env (default: 3306, type: int) MYSQL_PORT;
}
servers [
"172.28.0.10",
"172.28.0.5"
]
}
Because NACL is a superset of JSON, we will skim over the JSON syntax itself and describe how the language was extended below.
NACL allows the same types as JSON, which are string, number, object, array, boolean and the null
value.
NACL allows any one of the supported types of values as root elements of a configuration file.
Below are valid NACL examples
"Hello, World!"
true
[ 1000, 2000 ]
{ "foo": "bar" }
However, unlike JSON, NACL will provide an implicit {}
root object in two cases: when the NACL source file is composed of one or more key/value pairs, for example
"host": "localhost",
"port": 80
will be equivalent to
{"host": "localhost", "port": 80}
or when the NACL source is an empty NACL file, which will be the JSON equivalent to
{}
NACL allows unquoted strings for single word keys and values. Unquoted strings start with an ASCII letter or underscore, followed by any number of ASCII letters, ASCII digits, underscores, or dashes. As a regular expression, it would be expressed thus: ^[A-Za-z_][A-Za-z0-9_-]*$
.
For example
host: localhost
will be equivalent to
{"host": "localhost"}
NACL allows multiline string using the heredoc syntax.
For example
text: <<<END
This is
a multiline
string
END;
will be equivalent to
{"text": "This is\na multiline\nstring"}
Note: If you have really long text, you might want to put the text in a single file and use the file macro.
NACL allows value assignment using the column :
or the equal sign =
, however this assignment sign is optional and NACL allows you to leave the assignment sign out entirely.
For example
host: localhost
is equivalent to
host = localhost
and also equivalent to
host localhost
which are all equivalents to
{"host": "localhost"}
NACL statements (array or object elements) are separated using either ,
or ;
and NACL allows statements terminators, so you can safely use an extra separator after the last element of an array or object.
For example
key1 value1,
key2 value2
is equivalent to
key1 value1,
key2 value2,
which is also equivalent to
key1 value1;
key2 value2;
which are all equivalents to
{
"key1": "value1",
"key2": "value2"
}
Variables can be created or read using the ${VAR}
syntax.
For example
${TMP_DIR} = "/tmp";
temp_dir = ${TMP_DIR};
temp_file = "${TMP_DIR}/tempfile.txt";
is equivalent to
{
"temp_dir": "/tmp",
"temp_file": "/tmp/tempfile.txt"
}
PHP Related Note: The PHP library allows injection of variables using the API, for example
<?php $parser = Nuglif\Nacl\Nacl::createParser(); $parser->setVariable('TMP_DIR', sys_get_temp_dir()); $config = $parser->parseFile('application.conf');
NACL allows two single line comment styles and one multiline comment style.
- Single line comments can start with
//
or#
- Multiple line comments must start with
/*
and end with*/
NACL allows you to express your booleans using true
/ false
, but also yes
/ no
and on
/ off
. All will be interpreted as booleans, but having diversity in wording allows you to better express the intention behind a boolean configuration statement.
You can simply state
debug on;
which is more natural than
debug true;
or even worst
debug 1;
Suffix multipliers make the NACL more declarative and concise, and help you avoid mistakes. NACL allows the use of some of the common suffix multipliers.
In NACL, number can be suffixed with
k
M
G
prefixes for the International System of Units (SI) (1000^n)kB
MB
GB
1024^n bytesms
s
min
h
d
w
y
number in seconds .
For example
file_max_size 7MB; # 7 * 1024^2 (bytes)
file_ttl 9min; # 9 * 60 (seconds)
is equivalent to
file_max_size 7340032; # 7 * 1024^2 (bytes)
file_ttl 540; # 9 * 60 (seconds)
which is equivalent to
{
"file_max_size": 7340032,
"file_ttl": 540
}
NACL allows objects with the same key names to be redeclared along the way, objects keys with the same name will simply merge together recursively (deep merge). Merge only applies to object values, where non-object values overlap, the last declared value will be the final value.
For example
foo {
non-object-value-a true;
non-object-value-b [ 1, 2 ];
object-value { c: "c"}
}
foo {
non-object-value-a false;
non-object-value-b [ 3, 4 ];
object-value { x: "x" }
}
Will be recursively merged where there are object values, and the resulting structure will be equivalent to
{
"foo": {
"non-object-value-a": false,
"non-object-value-b": [ 3, 4 ],
"object-value": {
"c": "c",
"x": "x"
}
}
}
NACL allows you to set a value within a hierarchy using only the key as the hierarchical path by placing every keys of the hierarchy one after the other separated by spaces on the key side of the assignation.
For example
development server debug on;
production server url "example.com";
production server port 80;
will also be an NACL equivalent to
development {
server {
debug on;
}
}
production {
server {
url "example.com";
port 80;
}
}
which could also be an NACL equivalent of
development {
server {
debug on;
}
}
production {
server {
url "example.com";
}
}
production server {
port 80;
}
which will be a JSON equivalent to
{
"development": {
"server": {
"debug": true
}
},
"production": {
"server": {
"url": "example.com",
"port": 80
}
}
}
NACL offers some baseline macros, the .ref, .include, .file and .env Macros.
To differentiate them from keys and other language elements, macros names begin with a dot. They expect one value (which can be a primitive or a non-primitive), and possibly distinct optional parameters.
For example
.a_macro_name (param1: foo, param2: bar) "the primitive, array or object value"
would be a general NACL macro form representation.
The macro specification allows the language to be extended with custom macros specific to your domain and implementation.
NACL offers the .ref
macro, which can be used as a reference to another value within the NACL tree. The value you provide is a path which can be relative or absolute.
For example
foo bar;
baz .ref "foo";
which will become the JSON equivalent of
{
"foo": "bar",
"baz": "bar"
}
NACL offers the .include
macro, which can be used to include and evaluate NACL files in other NACL files. The .include
macro has three optional parameters which are described in the table below.
Option | Default value | Description |
---|---|---|
required |
true |
If false NACL will not trigger any error if included file doesn't exist. |
glob |
false |
If true NACL will include all files that match the pattern. If the provided inclusion path has a wildcard while glob is set to false, NACL will attempt to include a file matching the exact name, including its wildcard. |
filenameKey |
false |
If true , NACL will prefix the included file (or possibly files if glob is true ) with a key named after the included file name (without the extension). |
For example
.include "file.conf";
.include (required: false) "file.override.conf";
.include (glob: true, filenameKey: true) "conf.d/*.conf";
As an other example, if you have a file named file.conf
that contains only foo: "bar";
, then the following NACL example
.include "file.conf";
baz: "qux";
will become the JSON equivalent of
{
"foo": "bar",
"baz": "qux"
}
As an example usage of glob: true
and filenameKey: true
, say you have a file named person1.conf
containing "alice";
and a second file named person2.conf
containing "bob"
, and the following third NACL file including them
.include(glob: true, filenameKey: true) "*.conf";
will become the JSON equivalent of
{
"person1": "alice",
"person2": "bob"
}
NACL offers the .file
macro, which can be used to include other files within NACL files without evaluating them.
For example
email {
template .file "welcome.tpl";
}
will assign the content of the welcome.tpl
file to the template
variable. If the welcome.tpl
file contained only Welcome my friend
, the previous NACL example would become the JSON equivalent of
{
"email": {
"template": "Welcome my friend"
}
}
NACL offers the .env
macro, which can be used to evaluate the specified environment variable. The .env
macro has two optional parameters which are described in the table below.
Option | Default value | Description |
---|---|---|
default |
- | If the environment variable doesn't exist, this default value will be returned instead. |
type |
string |
Since environment variables are always string types, setting a type will cast the string value to the provided type within NACL. |
For example
port .env (default: 80, type: int) SERVER_PORT;
title .env TITLE;
on a system where the SERVER_PORT
is undefined, and TITLE
is set to "300"
, the previous NACL example would become the JSON equivalent of
{
"port": 80,
"title": "300"
}
This project provides an NACL specification implemented as a PHP library.
To install with composer:
composer require nuglif/nacl
The library will work on versions of PHP from 7.1 to 8.0 or newer.
Here's a basic usage example:
<?php
$config = Nuglif\Nacl\Nacl::parseFile('application.conf');
or
<?php
$parser = Nuglif\Nacl\Nacl::createParser();
$config = $parser->parseFile('application.conf');
It's easy to extend NACL using your own macro.
key .myMacro someParam;
key .myMacro(optionName: someValue, otherOption: otherValue) {
/* Some content here */
};
To create your macro you must implement the Nuglif\Nacl\MacroInterface
interface
<?php
interface MacroInterface
{
public function getName(): string;
public function execute(mixed $parameter, array $options = []): mixed;
}
and use the Nulig\Nacl\Parser::registerMacro($macro);
method.
<?php
Nuglif\Nacl\Nacl::registerMacro(new MyMacro);
$config = Nuglif\Nacl\Nacl::parseFile('application.conf');
or
<?php
$parser = Nuglif\Nacl\Nacl::createParser();
$parser->registerMacro(new MyMacro);
$config = $parser->parseFile('application.conf');
- Pierrick Charron ([email protected]) - Initial work
- Charle Demers ([email protected]) - Initial work
This project is licensed under the MIT License - for the full copyright and license information, please view the LICENSE file that was distributed with this source code.
Copyrights 2019 Nuglif (2018) Inc. All rights reserved.