Quire Tutorial¶
This tutorial assumes that reader is proficient with C and build tools, and have setup building as described in Developer Guide.
It is also assumed that you are familiar with YAML. There is quick cheat sheet in user guide.
Minimal Config¶
Let’s make minimal configuration definition file, which is by coincidence is
also a YAML file. By convention it’s called config.yaml
:
__meta__:
program-name: frog
default-config: /etc/frog.yaml
description: The test program that is called frog just because it
rhymes with prog (i.e. abbreviated "program")
Now let’s build them to see what we have (we use quire-tool
like it’s
installed in the system, you can use any build tool
to build the files):
quire-tool --source config.yaml --c-header config.h --c-header config.c
Let’s take a look at what we have in config.h
:
/* Main configuration structure */
struct cfg_main {
qu_config_head head;
};
/* API */
int cfg_load(struct cfg_main *cfg, int argc, char **argv);
void cfg_free(struct cfg_main *cfg);
Disclaimer
Generated code pieces are shown stripped and reformatted for teaching purposes
We don’t see anything useful here yet. But let’s make it work anyway. We need
a main.c
:
#include "config.h"
static void run_program(struct cfg_main *cfg) {
printf("The test program is doing nothing right now!\n");
}
int main(int argc, char **argv) {
int rc;
struct cfg_main cfg;
rc = cfg_load(&cfg, argc, argv);
if(rc == 0) {
run_program(&cfg);
}
cfg_free(&cfg);
if(rc > 0) {
/* rc > 0 means we had some configuration error */
return rc;
} else {
/* rc == 0 means we have run successfully */
/* rc < 0 means we've done some config action successfully */
return 0;
}
}
As you can see there is a tiny bit of boilerplate with handling error codes and freeing memory. Let’s build it:
gcc main.c config.c -o frog -lquire -g
Let’s see what batteries we have out of the box:
$ ./prog
Error parsing file /etc/frog.yaml: No such file or directory
Hm, we don’t have a configuration file, yet. And we don’t want to put
configuration into /etc
yet. Let’s see what we can do:
$ ./prog --help
Usage:
frog [-c CONFIG_PATH] [options]
The test program that is called frog just because it rhymes with prog (i.e.
abbreviated "program")
Configuration Options:
-h,--help Print this help
-c,--config PATH Configuration file name [default: /etc/frog.yaml]
-D,--config-var NAME=VALUE
Set value of configuration variable NAME to VALUE
-C,--config-check
Check configuration and exit
-P Print configuration after reading, then exit. The
configuration printed by this option includes values
overriden from command-line. Double flag `-PP` prints
comments.
--config-print TYPE
Print configuration file after reading. TYPE maybe
"current", "details", "example", "all", "full"
You can change path to configuration file, you can play with configuration checking and printing, you can put some variables into configuration (more below). And you get all of this for free.
So to run the command now, execute:
$ touch frog.yaml
$ ./frog -c frog.yaml
The test program is doing nothing right now!
Let’s make it easier to test by picking up configuration file from current directory:
__meta__:
...
default-config: frog.yaml
...
$ ./frog
The test program is doing nothing right now!
Adding Useful Stuff¶
Let’s add some integer knob to our config:
jumps: !Int 3
After building we have the following header:
struct cfg_main {
qu_config_head head;
long jumps;
};
And we can now make advantage of this variable:
void run_program(struct cfg_main *cfg) {
int i;
for(i = 0; i < cfg->jumps; ++i) {
printf("jump\n");
}
}
Let’s run and play with it a little bit:
$ ./frog
jump
jump
jump
$ echo "jumps: 4" > frog.yaml
$ ./frog
jump
jump
jump
jump
Note: I’m editing the file by shell command. It’s probably too freaky way to do that. You can just edit the file, and see how changes are reflected.
The tutorial gives you an overview of what quire is able to parse and generate, for full list of types supported see Developer Guide.
Nested Structures¶
Now the interesting begins. You can make hierarchical config, configuration sections of arbitrary depth:
jumping:
number: !Int 3
distance: !Float 1
Yields:
struct cfg_main {
qu_config_head head;
struct {
long number;
double distance;
} jumping;
};
In config it looks like:
jumping:
number: 5
distance: 2
Note
The presence of nested structures in quire doesn’t mean that nesting too deep is encouraged. Probably the example above is better written as:
jumping-number: !Int 3
jumping-distance: !Float 1
Particularly, flat structure is more convenient for merging maps. So use nested structures sparingly.
Command-line Arguments¶
Many values can be controlled from the command-line. Let’s return to the simpler example:
jumps: !Int 3
Command-line is enabled easily. First we should reformat our declaration, to equivalent one with mapping syntax:
jumps: !Int
default: 3
Now we can add a command-line option:
jumps: !Int
default: 3
command-line: [-j, --jumps]
Let’s see:
$ ./frog --help
Usage:
frog [-c CONFIG_PATH] [options]
The test program that is called frog just because it rhymes with prog (i.e.
abbreviated "program")
Configuration Options:
-h,--help Print this help
-c,--config PATH Configuration file name [default: /etc/frog.yaml]
-D,--config-var NAME=VALUE
Set value of configuration variable NAME to VALUE
-C,--config-check
Check configuration and exit
-P Print configuration after reading, then exit. The
configuration printed by this option includes values
overriden from command-line. Double flag `-PP` prints
comments.
--config-print TYPE
Print configuration file after reading. TYPE maybe
"current", "details", "example", "all", "full"
Options:
-j,--jumps INT Set "jumps"
$ ./frog
jump
jump
jump
$ ./frog -j 1
jump
$ ./frog --jumps=2
jump
jump
$ ./frog --ju 1
jump
For integer types there are increment and decrement arguments:
jumps: !Int
default: 3
command-line: [-j, --jumps]
command-line-incr: --jump-incr
command-line-decr: [-J,--jump-decr]
This works as following:
$ ./frog
jump
jump
jump
$ ./frog --jump-decr
jump
jump
$ ./frog -JJ
jump
$ ./frog -JJJ
$ ./frog --jump
Option error "--jump": Ambiguous option abbreviation
Note
Making command-line arguments is easy. However, too many command-line
options makes --help
output too long. There is another mechanism to
expose configuration variables to the command-line:
variables. Variables in quire are even more powerful, but
somewhat less easy to use. At the end of the day, declare command-line
arguments for options that either useful for almost every user, or
should only be specified in the command-line.
Arrays¶
So far we have only declared simple options, that every configuration library, supports. But here is where the power of the quire comes. The arrays are declared like the following:
sounds: !Array
element: !String
Here we declared array of strings. Here is how it looks like in C structure:
struct cfg_a_str {
struct cfg_a_str *next;
const char *val;
int val_len;
};
struct cfg_main {
qu_config_head head;
struct cfg_a_str *sounds;
struct cfg_a_str **sounds_tail;
int sounds_len;
};
It’s looks too ugly at the first glance. But the rules are:
- The array is a linked list
- The type of list element is named
cfg_a_TYPENAME
- The head of the linked list is named as variable in yaml
- The tail may be ignored unless you want to insert another element
- There is
_len
-suffixed element for the number of elements in array - The element of linked list is named
val
(suffixes work here too)
Ok, let’s see how to use it in code:
struct cfg_a_str *el;
for(el = cfg->sounds; el; el = el->next) {
printf("%s\n", el->val);
}
Now if we write following config:
sounds:
- croak
- ribbit
We can have a frog that can cry with both USA and UK slang :)
$ ./frog -c flog.yaml
croak
ribbit
You can also create nested arrays, and arrays of structures.
Mappings¶
We can also declare a mapping:
sounds: !Mapping
key-element: !String
value-element: !String
Here we declared mapping of string to string. Here is how it looks like in C structure:
struct cfg_m_str_str {
struct cfg_m_str_str *next;
const char *key;
int key_len;
const char *val;
int val_len;
};
struct cfg_main {
qu_config_head head;
struct cfg_m_str_str *sounds;
struct cfg_m_str_str **sounds_tail;
int sounds_len;
};
The structure is very similar to array’s one, but the element type is named
cfg_m_KEYTYPE_VALUETYPE
.
Ok, let’s see how to use it in code:
struct cfg_a_str *el;
for(el = cfg->sounds; el; el = el->next) {
printf("%s -- %s\n", el->key, el->val);
}
Now if we write following config:
sounds:
gb: croak
usa: ribbit
We can have a frog that can display both the slang and the text:
$ ./frog -c flog.yaml
gb -- croak
usa -- ribbit
Note
The mapping is represented by a linked list too. There is no hash table or other mapping structures that makes access by key fast. There are few reasons for this decision, the most imporant one is that most programs will copy the mapping into their own hash table implementation anyway.
Warning
The order of the elements in the linked list is preserved. But this shouldn’t be relied upon, as the YAML spec doesn’t guarantee that. For example some tool may rewrite yaml file and get keys reordered.
The key-element
can be any scalar type (string, int, float...).
The value-element
can be any type supported by quire, including nested
arrays and mappings.