161 lines
4.7 KiB
Markdown
161 lines
4.7 KiB
Markdown
LibCYAML: Tutorial
|
|
==================
|
|
|
|
This document is intended for C developers wishing to make use of
|
|
[LibCYAML](https://github.com/tlsa/libcyaml).
|
|
|
|
Overview
|
|
--------
|
|
|
|
If you want to use LibCYAML you'll need to have two things:
|
|
|
|
1. A consistent structure to the sort of YAML you want to load/save.
|
|
2. Some C data structure to load/save to/from.
|
|
|
|
LibCYAML's aim is to make this as simple as possible for the programmer.
|
|
However, LibCYAML knows nothing about either your data structure or the
|
|
"shape" of the YAML you want to load. You provide this information by
|
|
defining "schemas", and passing them to LibCYAML.
|
|
|
|
> **Note**: If you need to handle arbitrary "free-form" YAML (e.g. for a tool
|
|
> to convert between YAML and JSON), then LibCYAML would not be much help.
|
|
> In such a case, I'd recommend using [libyaml](https://github.com/yaml/libyaml)
|
|
> directly.
|
|
|
|
A simple example: loading YAML
|
|
------------------------------
|
|
|
|
Let's say you want to load the following YAML document:
|
|
|
|
```yaml
|
|
name: Fibonacci
|
|
data:
|
|
- 1
|
|
- 1
|
|
- 2
|
|
- 3
|
|
- 5
|
|
- 8
|
|
```
|
|
|
|
And you want to load it into the following C data structure:
|
|
|
|
```c
|
|
struct numbers {
|
|
char *name;
|
|
int *data;
|
|
unsigned data_count;
|
|
};
|
|
```
|
|
|
|
Then we need to define a CYAML schema to describe these to LibCYAML.
|
|
|
|
> **Note**: Use the doxygen API documentation, or else the documentation in
|
|
> [cyaml.h](https://github.com/tlsa/libcyaml/blob/main/include/cyaml/cyaml.h)
|
|
> in conjunction with this guide.
|
|
|
|
At the top level of the YAML is a mapping with two fields, "name" and
|
|
"data".
|
|
|
|
```yaml
|
|
name:
|
|
data:
|
|
```
|
|
|
|
The first field is just a simple scalar value (it's neither
|
|
a mapping nor a sequence). The second field has a sequence value.
|
|
|
|
We'll start by defining the CYAML schema for the "data" sequence,
|
|
since since that's the "deepest" non-scalar type. The reason for
|
|
starting here will become clear later.
|
|
|
|
```c
|
|
/* CYAML value schema for entries of the data sequence. */
|
|
static const cyaml_schema_value_t data_entry = {
|
|
CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int),
|
|
};
|
|
```
|
|
|
|
Here we're making a `cyaml_schema_value_t` for the entries in the
|
|
sequence. There are various `CYAML_VALUE_{TYPE}` macros to assist with
|
|
this. Here we're using `CYAML_VALUE_INT`, because the value is a signed
|
|
integer. The parameters passed to the macro are `enum cyaml_flag`, and
|
|
the C data type of the value.
|
|
|
|
Now we can write the schema for the mapping. First we'll construct
|
|
an array of `cyaml_schema_field_t` entries that describe each
|
|
field in the mapping.
|
|
|
|
```c
|
|
/* CYAML mapping schema fields array for the top level mapping. */
|
|
static const cyaml_schema_field_t top_mapping_schema[] = {
|
|
CYAML_FIELD_STRING_PTR(
|
|
"name", CYAML_FLAG_POINTER, struct numbers, name, 0, CYAML_UNLIMITED),
|
|
CYAML_FIELD_SEQUENCE(
|
|
"data", CYAML_FLAG_POINTER, struct numbers, data, &data_entry, 0, CYAML_UNLIMITED),
|
|
CYAML_FIELD_END
|
|
};
|
|
```
|
|
|
|
There are `CYAML_FIELD_{TYPE}` helper macros to construct the mapping field
|
|
entries. The array must be terminated by a `CYAML_FIELD_END` entry.
|
|
The helper macro parameters are specific to each `CYAML_FIELD_{TYPE}` macro.
|
|
|
|
The entry for the name field is of type string pointer. You can consult the
|
|
documentation for the `CYAML_FIELD_{TYPE}` macros to see what the parameters
|
|
mean.
|
|
|
|
> **Note**: The field for the sequence takes a pointer to the sequence entry
|
|
> data type that we defined earlier as `data_entry`.
|
|
|
|
Finally we can define the schema for the top level value that gets passed to
|
|
the LibCYAML.
|
|
|
|
```c
|
|
/* CYAML value schema for the top level mapping. */
|
|
static const cyaml_schema_value_t top_schema = {
|
|
CYAML_VALUE_MAPPING(
|
|
CYAML_FLAG_POINTER, struct numbers, top_mapping_schema),
|
|
};
|
|
```
|
|
|
|
In this case our top level value is a mapping type. One of the parameters
|
|
needed for mappings is the array of field definitions. In this case we're
|
|
passing the `top_mapping_schema` that we defined above.
|
|
|
|
```c
|
|
/* Create our CYAML configuration. */
|
|
static const cyaml_config_t config = {
|
|
.log_fn = cyaml_log, /* Use the default logging function. */
|
|
.mem_fn = cyaml_mem, /* Use the default memory allocator. */
|
|
.log_level = CYAML_LOG_WARNING, /* Logging errors and warnings only. */
|
|
};
|
|
|
|
/* Where to store the loaded data */
|
|
struct numbers *n;
|
|
|
|
/* Load the file into n */
|
|
cyaml_err_t err = cyaml_load_file(argv[ARG_PATH_IN], &config,
|
|
&top_schema, (cyaml_data_t **)&n, NULL);
|
|
if (err != CYAML_OK) {
|
|
/* Handle error */
|
|
}
|
|
|
|
/* Use the data. */
|
|
printf("%s:\n", n->name);
|
|
for (unsigned i = 0; i < n->data_count; i++) {
|
|
printf(" - %i\n", n->data[i]);
|
|
}
|
|
|
|
/* Free the data */
|
|
err = cyaml_free(&config, &top_schema, n, 0);
|
|
if (err != CYAML_OK) {
|
|
/* Handle error */
|
|
}
|
|
```
|
|
|
|
And that's it, the YAML is loaded into the custom C data structure.
|
|
|
|
You can find the code for this in the "numerical" example in the
|
|
[examples](../examples) directory.
|