2 releases
0.2.1 | Mar 16, 2024 |
---|---|
0.2.0 | Feb 14, 2024 |
#610 in Data structures
88 downloads per month
30KB
831 lines
Confiner - A tree-like config language.
/\
< >
\/
CONF
CONF
CONF||CONF
CONF||CONF
CONF||||CONF
CONF CONF CONF
CONF CONF CONF
CONF CONF CONF CONF
CONF |||| CONF
||||
(Config + Conifer, get it?)
For example
A simple webserver config...
my_server: server
{
addr = "127.0.0.1:80",
my_uri_handler: uri
{
mount = "/files",
pattern = [ "" ],
file_service: raw_files
{
path = ./files
}
}
}
Features
Blocks
Confiner files contain a set of blocks, each of which has a set of key-value properties, and child blocks. This is similar to HTML or device-tree files.
Blocks have a kind, and an optional globally unique name. The syntax for a block begins with an optional name identifier, followed by a colon and the block kind, then a pair of braces containing comma-separated properties and children. e.g.
block_name: block_kind
{
property="expr",
: child_1_kind
{
property="expr",
subchild: subchild_kind {}
},
child_2: child_2_kind {}
}
Expression types
Basic data types can be expressed in a sensible way:
- Integers (
1
,-2
,0x03
, ...) - Floats (
1.0
,1e-3
, ...) - Strings (
"hello world"
,"Line 1\nLine 2"
,"\"quoted\""
, ...) - Lists (
[1, 2, 3]
, ...) - Maps with string keys (
{ key = "value" }
, ...)
There are a couple of more unique types of expressions.
Enums
These help to unambiguously serialize Rust's enum types. They consist of an enum identifier prefixed with an exclamation mark, followed optionally by data for that enum variant.
For example, consider the following enum:
enum MyEnum
{
Unit,
NewType(String),
Tuple(i32, i32),
Struct { field: bool }
}
This could be serialized as follows:
!Unit
!NewType "value"
!Tuple [i32, i32]
!Struct { field = true }
Paths
Paths are expressions that expand to the absolute path of a file, specified
relative to the location of the confiner file. These expressions respect
@include
directives, and always evaluate to the same bytes, regardless of
whether the file was included or deserialized directly.
They are written as a relative path, prefixed with .
. They are not quoted,
and cannot contain unescaped spaces. For example:
.
(The directory containing the confiner file)./file.txt
(A file in the same directory as the confiner file)./directory\ with\ spaces/file.txt
References
At the top-level of a confiner file, references can be defined. Rather than diretly creating a block, these blocks can be placed elsewhere by "using" the reference. The same block can be placed in multiple places, allowing for more complex structures than simple trees.
The syntax for defining a reference is the same as for defining a normal top-level block, but with a prefixed "&". Reference blocks must be named.
The syntax for using a reference is simply an "&" followed by the name of the reference.
&my_reference: kind { }
normal_block: kind
{
&my_reference,
child_block: kind
{
&my_reference
}
}
Included files
Files can be included using the @include <path>
syntax. Each included file is
evaluated at most once, and any references or top-level blocks from the included
file are treated equivalently to those in the file containing the
@include <path>
.
The @include <path>
syntax uses for same relative paths logic as the path
expression.
@include ./other_file.conf
@include ./subdir/file.conf
Dependencies
~0.4–1MB
~23K SLoC