Bienvenue sur le site de la LibLapin.
Jetez un coup d'oeil en bas de la page pour choisir votre niveau de documentation en fonction de votre niveau avec la LibLapin.
Pour l'instant, c'est réglé sur 'Manuel complet'. Si c'est votre première fois avec la LibLapin, il vaudrait mieux choisir 'Débutant'.
De même, n'oubliez pas de préciser une version de la bibliothèque.
This tutorial "Configuration files" is about the configuration
module of the bunny library and will teach you how to use
the configuration itself but also details about the supported
format themselves.
The configuration module supports the following formats:
  The INI format. As a dialect, our version have some specificities.
  The DABSIC format. Which is an original format.
  The XML format. With some extensions.
  The LUA format.
  The CSV format.
  The JSON format.
  The LISP format.
  In a future version, the YAML format may be supported.
All those formats are extended with @insert and @include directives
that allow while loading a file to load some others, inserting them in place
or at root. Parameters of those directives can be file or directory.
If it is directory, then it is not recursive.
Configurations of bunny library are trees. Each node can have at the same time
named children, indexed children and a value: nodes are at the same time hashmap,
arrays and value.
Each node can have a value which can be an integer, a double or a string.
It is currently only supported while using a file format that support it
(and cannot be created by hand in your program), but a node can also be
a reference to another node. Reference are automatically resolved on request.
Expression available operators are:
  Assignation, with symbols '=', '<-' or ':='.
  Recursive assignation of value, hashmap and array, with symbols '[=]', '[<-]' or '[:=]'.
  Recursive assignation of value and hashmap, with symbols '[Hash=]', '[Hash<-]' or '[Hash:=]'.
  Recursive assignation of value and array, with symbols '[Array=]', '[Array<-]' or '[Array:=]'.
  Logic or assignation with symbol '||='.
  Logic xor assignation with symbol '^^='.
  Logic and assignation with symbol '&&='.
  Binary or assignation with symbol '|='.
  Binary xor assignation with symbol '^='.
  Binary and assignation with symbol '&='.
  Left shift assignation with symbol '<<='.
  Right shift assignation with symbol '>>='.
  Addition assignation with symbol '+='.
  Subtract assignation with symbol '-='.
  Multiply assignation with symbol '*='.
  Divide assignation with symbol '/='.
  Modulo assignation with symbol '%='.
  Power assignation with symbol '**='.
  Concat assignation with symbol '#='.
  Ternary with symbols '?' and ':'.
  Logic or with symbols '||', 'or' or 'ou'.
  Logic xor with symbols '^^', 'xor' or 'oux'.
  Logic and with symbols '&&', 'and' or 'et'.
  Equal test with symbols '==', 'is', '.eq.' or '-eq'.
  Recursive equal on hashmap, array and value test with symbols '[==]', '[is]'.
  Inequal test with symbols '!=', '<>', '.ne.' or '-ne'.
  Recursive inequal on hashmap, array and value test with symbols '[!=]', '[<>]'.
  Lower or equal test with symbols '<=', '.le.' or '-le'.
  Greater or equal test with symbols '>=', '.ge.' or '-ge'.
  Lower or equal test with symbols '<', '.lt.' or '-lt'.
  Greater or equal test with symbols '>', '.gt.' or '-gt'.
  Binary or with symbol '|'.
  Binary xor with symbol '^'.
  Binary and with symbol '&'.
  Left shift with symbol '<<'.
  Right shift with symbol '<<'.
  Addition with symbol '+'.
  Subtract with symbol '-'.
  Multiplication with symbol '*'.
  Division with symbol '/'.
  Modulo with symbol '%'.
  Power with symbol '**'.
  Concat with symbol '#'.
Before being something modelized inside files, the configuration
module is an in-memory data manager.
In this first part, we will learn how to create, delete and
edit a configuration without talking about its storage.
We will use C11 features throught some functions, so
your compiler should be ready for it: recent and set to plain
C instead of C++. If you are using C++, alternatives to
listed functions will be provided.
This function build a configuration node. It is the very start of a configuration when it does not came from a file. This configuration node must be freed with bunny_delete_configuration when you are done.
Delete a configuration node.
The create mode is a mode where some access provoke the creation of configuration node. It is mandatory to write a configuration from a virgin one. It is not mandatory to only write inside existing nodes.
This function return the node, children of configuration and characterized
by id. id can be a string containing an address or an integer containing
an index. Addresses in configuration work almost like structure access in C:
This function set a value into the sent configuration. This value can be a
string, an int or a double.
This functionnality needs C11.
Alternative functions are bunny_configuration_set_string,
bunny_configuration_set_double and bunny_configuration_set_int.
This function get a value from the sent configuration. This value can be a
char**, an int* or a double*.
If value inside the node is not of the correct type but could be converted,
it will. If the value was retrieved, the function returns true.
This functionnality needs C11.
Alternative functions are bunny_configuration_get_string,
bunny_configuration_get_double and bunny_configuration_get_int.
This function set a value into the sent configuration at the sent address.
This value can be a string, an int or a double.
This functionnality needs C11.
Alternative functions are bunny_configuration_go_set_string,
bunny_configuration_go_set_double and bunny_configuration_go_set_int.
This function get a value from the sent configuration at the sent address.
This value can be a char**, an int* or a double*.
If value inside the node is not of the correct type but could be converted,
it will. If the value was retrieved, the function returns true.
This functionnality needs C11.
Alternative functions are bunny_configuration_go_get_string,
bunny_configuration_go_get_double and bunny_configuration_go_get_int.
The below program show several operations made on a configuration with
the most useful functions of the configuration module.
It starts by creating a first configuration node with bunny_new_configuration.
This creates a single and empty, for both named children and indexed children,
but also as value, node: no children, no value.
Following this operation, a little demo of what is the create mode. It is
currently disabled, as it is by default: when calling the
bunny_configuration_access function which is designed to get a node by
its address, because this mode is disabled, the function cannot get it because
it does not exist yet and it returns NULL.
Right after, we call bunny_configuration_create_mode with true as parameter
to enable it and retry with bunny_configuration_access. This time, the node
is created.
Now we have a Team.Player node and a pointer on it inside adr, we will
store a value inside it. With bunny_configuration_set, we can set
a value inside a node by its pointer.
Right after, we try to fetch it and store it inside an integer variable
and check its value. It matches the one we set before.
Sometimes, it is more convenient to set a value from far away, without
having to get its node before and then call a set function. Same thing
goes for get functions.
For this purpose, the bunny_configuration_go_* family allow to set and
get values from a parent node with relative address.
In this part of the program, we set the Team.Player field from the root
node we create at the very beginning to an integer value.
From the root node again, we fetch it and check it is the same we set before.
Finally, to prove that we set effectively the node that was pointed
by adr before, we will fetch the value from it with the function we
previously used, bunny_configuration_get and check it matches 68000.
Do not forget to delete the configuration if you do not need it anymore.
Note that it delete every data that was inside. If integers and doubles
are not big deal, you should pay attention to strings you retrieved, their
pointers will not be valid anymore!
Also pay attention to pointers to node that were below the node you
delete: they will not be valid either after that.
The whole program, setting values with different fashion and fetching
them right after.
Browsing a configuration is often required to fully handle it.
If some program may only need a few fixed field with set values,
most of them need to read all field a node can have. With what
you have learnt in the previous part, you can alreayd browse a
few nodes: those who are arrays, with bunny_configuration_access,
but not hashmap!
In this part, you will learn how to browse hashmap too and how
to retrieve informations from nodes.
This function returns the first child of the node you sent. Children are sorted by name. This function may return bunny_configuration_end(configuration) if the sent node was empty.
This function returns the next brother (or sister) of the node you sent. Children are sorted by name. This function may bunny_configuration_end(configuration) if the sent node was the last one.
This function returns a the sent node children terminator.
This function returns true if the sent node is the last children of its parent.
This function returns the parent of the sent node. NULL if the sent node is the root node.
This function returns the root of the sent node: the only node without parent in its genealogy.
This function returns the name of the sent node. Every node have a name, even nodes that are in an array, they are simply the name of their parent followed by brackets containing their address.
This function returns the address of the sent node. It is made of all names from all nodes from the one you sent to the root. If there is some arrays between them, the address is designed intelligently: you will not find Array.Array[1].Node kind of stuff, but Array[1].node, of course.
This function returns how many named children the node have: the size of its hashmap.
This function returns how many indexed children the node have: the size of its array.
The first step of this program is the filling of the configuration by hand.
It starts with the node creation followed by the call to
bunny_configuration_create_mode with true as parameter. After that,
the configuration is filled and then the create mode is disabled so
access to the configuration does not create new nodes.
In this following part, the "Country" node hashmap is browsed.
The bunny_configuration_first, end and next functions make the for loop.
The first line inside the loop try to retrieve from the children a string.
If it fails, the program stops.
The second line print the name of the children and its retrieved value.
This time, the "Country.Region[0].Department" is browsed. This field
does not contain a hashmap like before but have some elements inside its
array: Seine-et-Marne, Val-de-Marne and Paris.
As we treat an array, this time we use an int. By requested the node at
the sent index, we can retrieve an existing node or NULL if there is
none at the sent index. If the create mode was enabled with this fashion,
we could have created new nodes!
An alternative, without disabling the create mode would be to use
bunny_configuration_get_nbr_case as termination condition.
In the loop, we try to retrieve a string from the extracted node.
After that, we print its name and its value.
Here, we check the Country.Region[0].Department array is 3 node long. It
is supposed to be, as it contains "Seine-et-Marne", "Val-de-Marne" and
"Paris".
Right after, the access to the Country node to check there is 4 child:
Name, Population, Regime and Region. We also check the parent node of
Country is the root node.
The final part check Coutry.Region address and Country.Region[1].Department
are correct.
The whole program.
The INI format is a non recursive simple format. Non recursive means all nodes
cannot have any kind of nodes.
The basic structure of an INI file is Scope.Field.Index.Value or
Field.Index.Value, Index being optionnal if Field contains only a single
value at index 0. Section with addresses are extensions brought by the
bunny library. Directives are extensions.
The general appeirence of INI is the following:
@include "./header_file.ini"
#This is inside the global scope, so it is a children of the root
GlobalField=42
[Section]
Field="1", $1 + 1, 3
[AnotherSection.DeepSection[2].Hell]
DeepField=1
@insert "./other_file.ini"
InsertCanBeAnywhere=1, @insert "./even_here.ini", 3
Inserts and includes appart, this evolves into this serie of fields:
GlobalField = 42
Section.Field[0] = "1"
Section.Field[1] = 1 + 1
Section.Field[2] = 3
AnotherSection.DeepSection[2].Hell.DeepField = 1
The '$' token in INI format allow you to write a mathematical expression.
This mathematical expression can use all operators and variables, except assignation.
The include directive will include at the root of the file the precised
file. The position of the include directive is not important.
The insert directive will include at the written position in the file the
precised file. The position of the insert directive show the insertion
position.
The commentary token is '#' and is inline. There is no block commentary token.
This function load the sent file and add the configuration inside the the sent one. If configuration is NULL, a configuration node is created.
Generate a string containing the saved configuration.
This function save with the sent grammar the sent configuration into the sent file.
Here is a small program that load a file, modify the loaded configuration
and then print on stdout the modified configuration in the INI format.
The XML format is a partially recursive format: most nodes contains
hashmap and arrays but only array can contain nodes.
The general appeirence of XML is the following:
@include "another_file.xml"
<conf_node named_children="data">
ArrayInput
<array_input named_children="data","second_data" />
<array_input="extensions" named_children="data">
<!-- Magic -->
Data
@insert "another_file2.xml"
</array_input>
</conf_node>
Inserts and includes appart, this evolves into this serie of fields:
conf_node.named_children = "data"
conf_node[0] = "ArrayInput"
conf_node[1].named_children[0] = "data"
conf_node[1].named_children[1] = "second_data"
conf_node[2] = "extensions"
conf_node[2].named_children = "data"
conf_node[2][0] = "Data"
Note that array_input2 and array_input3 are indexed nodes even if they
have a name. This is because what is looking like a XML field name is
not a field name but the markup type, so it can be used several time inside
the same array. This name is still registered and will be returned
as the node name if requested, and regenerated if the configuration is saved.
The include directive will include at the root of the file the precised
file. The position of the include directive is not important.
The insert directive will include at the written position in the file the
precised file. The position of the insert directive show the insertion
position.
XML commentaries are block only, start with '<!--' and end with '-->'.
An XML file is supposed to have a doctype, but the bunny configuration does not
support it. Also, an XML file is supposed to have a single root markup written,
but the bunny configuration XML does not require it.
The markup value is a bunny configuration extensions.
The markup property as array is a bunny configuration extension.
The LUA format is a fully recursive format: arrays can have nodes as children,
hasmap can have nodes as children... But nodes can only be an array, an hashmap
or a value. It is very close to the JSON format.
The general appeirence of LUA is the following:
{
#! Commentary
field = "value",
--[[
Another comment
]]
scope =
{
field = "value"
},
array =
[
1,
2 -- Inline comment again
]
}
Inserts and includes appart, this evolves into this serie of fields:
field = "value"
scope.field = "value"
array[0] = 1
array[1] = 2
The bunny configuration LUA format support expressions as field value. Unlike
the INI format for example, no '$' is required.
The include directive will include at the root of the file the precised
file. The position of the include directive is not important.
The insert directive will include at the written position in the file the
precised file. The position of the insert directive show the insertion
position.
Inline LUA commentaries start with "#!" or "--". Block LUA commentaries
start with "--[[" and end with "]]"
The LUA programming language is not supported: only the data structure is.
The JSON format is a fully recursive format: arrays can have nodes as children,
hasmap can have nodes as children... But nodes can only be an array, an hashmap
or a value. It is very close to the LUA format.
The general appeirence of JSON is the following:
{
// Commentary
"field": "value",
/*
Another comment
*/
"scope":
{
"field" = "value"
},
"array":
[
1,
2 // Inline comment again
]
}
Inserts and includes appart, this evolves into this serie of fields:
field = "value"
scope.field = "value"
array[0] = 1
array[1] = 2
The bunny configuration JSON format support expressions as field value. Unlike
the INI format for example, no '$' is required.
The include directive will include at the root of the file the precised
file. The position of the include directive is not important.
The insert directive will include at the written position in the file the
precised file. The position of the insert directive show the insertion
position.
Inline JSON commentaries start with "//" Block JSON commentaries
start with "/*" and end with "*/". Commentaries are not standard in JSON
but are provided by the bunny configuration module.
The Javascript programming language is not supported: only the JSON itself is.
The CSV format represents 2D matrices. The bunny configuration version
of CSV use ';' as row separator and new line '\n' as line separator.
The general appeirence of CSV is the following:
"name";"game";"date"
"roger";"space quest";1985
"link";"zelda";1985
Inserts and includes appart, this evolves into this serie of fields:
[0][0] = "name"
[0][1] = "game"
[0][2] = "date"
[1][0] = "roger"
[1][1] = "space quest"
[1][2] = 1985
[2][0] = "link"
[2][1] = "zelda"
[2][2] = 1985
The include directive will include at the root of the file the precised
file. The position of the include directive is not important.
The insert directive will include at the written position in the file the
precised file. The position of the insert directive show the insertion
position.
The LISP format parse ML like structure.
The general appeirence of LISP is the following:
(hashmap ; comment
(value "42")
(array 0 1 2)
(another_array 0 1 2)
)
Inserts and includes appart, this evolves into this serie of fields:
hashmap.array[0] = 0
hashmap.array[1] = 1
hashmap.array[2] = 2
hashmap.value = "42"
hashmap.another_array[0] = 0
hashmap.another_array[0] = 1
hashmap.another_array[0] = 2
The include directive will include at the root of the file the precised
file. The position of the include directive is not important.
The insert directive will include at the written position in the file the
precised file. The position of the insert directive show the insertion
position.
LISP commentaries are only inline. The ';' token make it starts.
The DABSIC format is an original format inspired by INI. It is fully recursive:
hashmaps contains nodes, arrays contains nodes and contrary the JSON format
or the LUA format, a DABSIC format node can be defined as an array, as an
hashmap and as a value at the same time.
The general appeirence of DABSIC is the following:
Field = "Value" 'This is an inline comment
[*
Block comment
*]
[Scope = "Value2"
[Scope
DeepField = "ValueX"
]
Array=800,600
]
AnotherScope = [Scope
Field = "Val"
]
{Array = "Value3"
Entry0,
Entry1
}
AnotherArray = {
{ = "Even here"
Entry00, Entry01
},
[
Field = "We are in a scope"
]
Entry10
}
Inserts and includes appart, this evolves into this serie of fields:
Field = "Value"
Scope = "Value2"
Scope.Scope.DeepField = "ValueX"
Scope.Array[0] = 800
Scope.Array[0] = 600
AnotherScope.Field = "Val"
Array = "Value3"
Array[0] = "Entry0"
Array[1] = "Entry1"
AnotherArray[0] = "Even here"
AnotherArray[0][0] = Entry00
AnotherArray[0][1] = Entry01
AnotherArray[1].Field = "We are in a scope"
AnotherArray[2] = "Entry10"
The DABSIC format supports expressions as field value. Unlike
the INI format for example, no '$' is required.
The include directive will include at the root of the file the precised
file. The position of the include directive is not important.
The insert directive will include at the written position in the file the
precised file. The position of the insert directive show the insertion
position.
Inline DABSIC commentaries start with token "'".
Block DABSIC commentaries start with token '[*' and end with token '*]'.
The DABSIC format syntax is rich and various. There is different ways
to declare an array or a scope, all fitting for a specific context as their
esthetic is different.
The DABSIC format can include other formats thanks to specific scopes
opening.
The DABSIC format is not only a data format in the bunny configuration
module but a complete programming language.
All those aspects are described in another chapter:
the Dabsic Programming Language.