# 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 ` 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 `. The `@include ` syntax uses for same relative paths logic as the path expression. ``` @include ./other_file.conf @include ./subdir/file.conf ```