2023-10-16 18:20:08 +07:00

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.