/nacl

Nuglif Application Configuration Language (NACL) is a configuration data language intended to be both human and machine friendly.

Primary LanguagePHPMIT LicenseMIT

Nuglif Application Configuration Language (NACL)

Build Status License: MIT Latest Stable Version Code Coverage

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.

Table of content

NACL Example Source for the Impatient

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"
	]
}

NACL Extensions to the JSON Syntax

Because NACL is a superset of JSON, we will skim over the JSON syntax itself and describe how the language was extended below.

The Types

NACL allows the same types as JSON, which are string, number, object, array, boolean and the null value.

The Root Object

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

{}

The Unquoted Strings

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"}

The Multiline Strings

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.

The Optional Values Assignments Symbol

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"}

The Separator Symbol

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"
}

The Variables

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');

The Comments

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 */

The Boolean Values

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;

The Multipliers

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 bytes
  • ms 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
}

The NACL Object Structure Will Merge

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"
		}
	}
}

Using Key Names for Hierarchical Declaration

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

The NACL Macros

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.

The .ref Macro (Referencing)

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"
}

The .include Macro (Evaluated Inclusions)

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"
}

The .file Macro (Unevaluated Inclusions)

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"
	}
}

The .env Macro (Environment Variables)

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"
}

The PHP Library

This project provides an NACL specification implemented as a PHP library.

Installation

To install with composer:

composer require nuglif/nacl

The library will work on versions of PHP from 7.1 to 8.0 or newer.

Usage

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');

Extending NACL With Your Own Macros

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');

Authors

License

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.