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

469 lines
14 KiB
C

/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2019 Michael Drake <tlsa@netsurf-browser.org>
*/
#include <stdlib.h>
#include <stdio.h>
#include <cyaml/cyaml.h>
/******************************************************************************
* C data structure for storing a project plan.
*
* This is what we want to load the YAML into.
******************************************************************************/
/* Enumeration of months of the year */
enum months {
MONTH_JAN = 1,
MONTH_FEB,
MONTH_MAR,
MONTH_APR,
MONTH_MAY,
MONTH_JUN,
MONTH_JUL,
MONTH_AUG,
MONTH_SEP,
MONTH_OCT,
MONTH_NOV,
MONTH_DEC
};
/* Structure for storing dates */
struct date {
uint8_t day;
enum months month;
uint16_t year;
};
/* Structure for storing durations */
struct duration {
uint8_t hours;
unsigned days;
unsigned weeks;
unsigned years;
};
/* Enumeration of task flags */
enum task_flags {
FLAGS_NONE = 0,
FLAGS_IMPORTANT = (1 << 0),
FLAGS_ENGINEERING = (1 << 1),
FLAGS_DOCUMENTATION = (1 << 2),
FLAGS_MANAGEMENT = (1 << 3),
};
/* Structure for storing a task */
struct task {
const char *name;
enum task_flags flags;
struct duration estimate;
const char **depends;
unsigned depends_count;
const char **people;
unsigned n_people;
};
/* Top-level structure for storing a plan */
struct plan {
const char *name;
struct date *start;
const char **people;
unsigned n_people;
struct task *tasks;
uint64_t tasks_count;
};
/******************************************************************************
* CYAML schema to tell libcyaml about both expected YAML and data structure.
*
* (Our CYAML schema is just a bunch of static const data.)
******************************************************************************/
/* Mapping from "task_flags" strings to enum values for schema. */
static const cyaml_strval_t task_flags_strings[] = {
{ "None", FLAGS_NONE },
{ "Important", FLAGS_IMPORTANT },
{ "Engineering", FLAGS_ENGINEERING },
{ "Documentation", FLAGS_DOCUMENTATION },
{ "Management", FLAGS_MANAGEMENT },
};
/* Mapping from "month" strings to flag values for schema. */
static const cyaml_strval_t month_strings[] = {
{ "January", MONTH_JAN },
{ "February", MONTH_FEB },
{ "March", MONTH_MAR },
{ "April", MONTH_APR },
{ "May", MONTH_MAY },
{ "June", MONTH_JUN },
{ "July", MONTH_JUL },
{ "August", MONTH_AUG },
{ "September", MONTH_SEP },
{ "October", MONTH_OCT },
{ "November", MONTH_NOV },
{ "December", MONTH_DEC },
};
/* Schema for string pointer values (used in sequences of strings). */
static const cyaml_schema_value_t string_ptr_schema = {
CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED),
};
/* The duration mapping's field definitions schema is an array.
*
* All the field entries will refer to their parent mapping's structure,
* in this case, `struct duration`.
*/
static const cyaml_schema_field_t duration_fields_schema[] = {
/* The fields here are all optional unsigned integers.
*
* Note that if an optional field is unset in the YAML, its value
* will be zero in the C data structure.
*
* In all cases here, the YAML mapping key name (first parameter to
* the macros) matches the name of the associated member of the
* `duration` structure (fourth parameter).
*/
CYAML_FIELD_UINT(
"hours", CYAML_FLAG_OPTIONAL,
struct duration, hours),
CYAML_FIELD_UINT(
"days", CYAML_FLAG_OPTIONAL,
struct duration, days),
CYAML_FIELD_UINT(
"weeks", CYAML_FLAG_OPTIONAL,
struct duration, weeks),
CYAML_FIELD_UINT(
"years", CYAML_FLAG_OPTIONAL,
struct duration, years),
/* The field array must be terminated by an entry with a NULL key.
* Here we use the CYAML_FIELD_END macro for that. */
CYAML_FIELD_END
};
/* The date mapping's field definitions schema is an array.
*
* All the field entries will refer to their parent mapping's structure,
* in this case, `struct date`.
*/
static const cyaml_schema_field_t date_fields_schema[] = {
/* The "day" and "year" fields are just normal UNIT CYAML types.
* See the comments for duration_fields_schema for details.
* The only difference is neither of these are optional.
* Note: The order of the fields in this array doesn't matter.
*/
CYAML_FIELD_UINT(
"day", CYAML_FLAG_DEFAULT,
struct date, day),
CYAML_FIELD_UINT(
"year", CYAML_FLAG_DEFAULT,
struct date, year),
/* The month field is an enum.
*
* YAML key: "month".
* C structure member for this key: "month".
* Array mapping strings to values: month_strings
*
* Its CYAML type is ENUM, so an array of cyaml_strval_t must be
* provided to map from string to values.
* Note that we're not setting the strict flag here so both strings and
* numbers are accepted in the YAML. (For example, both "4" and "April"
* would be accepted.)
*/
CYAML_FIELD_ENUM(
"month", CYAML_FLAG_DEFAULT,
struct date, month, month_strings,
CYAML_ARRAY_LEN(month_strings)),
/* The field array must be terminated by an entry with a NULL key.
* Here we use the CYAML_FIELD_END macro for that. */
CYAML_FIELD_END
};
/* The task mapping's field definitions schema is an array.
*
* All the field entries will refer to their parent mapping's structure,
* in this case, `struct task`.
*/
static const cyaml_schema_field_t task_fields_schema[] = {
/* The first field in the mapping is a task name.
*
* YAML key: "name".
* C structure member for this key: "name".
*
* Its CYAML type is string pointer, and we have no minimum or maximum
* string length constraints.
*/
CYAML_FIELD_STRING_PTR(
"name", CYAML_FLAG_POINTER,
struct task, name, 0, CYAML_UNLIMITED),
/* The flags field is a flags value.
*
* YAML key: "flags".
* C structure member for this key: "flags".
* Array mapping strings to values: task_flags_strings
*
* In the YAML a CYAML flags value should be a sequence of scalars.
* The values of each set scalar is looked up the in array of
* string/value mappings, and the values are bitwise ORed together.
*
* Note that we're setting the strict flag here so only strings
* present in task_flags_strings are allowed, and numbers are not.
*
* We make the field optional so when there are no flags set, the field
* can be omitted from the YAML.
*/
CYAML_FIELD_FLAGS(
"flags", CYAML_FLAG_OPTIONAL | CYAML_FLAG_STRICT,
struct task, flags, task_flags_strings,
CYAML_ARRAY_LEN(task_flags_strings)),
/* The next field is the task estimate.
*
* YAML key: "estimate".
* C structure member for this key: "estimate".
*
* Its CYAML type is a mapping.
*
* Since it's a mapping type, the schema for its mapping's fields must
* be provided too. In this case, it's `duration_fields_schema`.
*/
CYAML_FIELD_MAPPING(
"estimate", CYAML_FLAG_DEFAULT,
struct task, estimate, duration_fields_schema),
/* The next field is the tasks that this task depends on.
*
* YAML key: "depends".
* C structure member for this key: "depends".
*
* Its CYAML type is a sequence.
*
* Since it's a sequence type, the value schema for its entries must
* be provided too. In this case, it's string_ptr_schema.
*
* Since it's not a sequence of a fixed-length, we must tell CYAML
* where the sequence entry count is to be stored. In this case, it
* goes in the "depends_count" C structure member in `struct task`.
* Since this is "depends" with the "_count" postfix, we can use
* the following macro, which assumes a postfix of "_count" in the
* struct member name.
*/
CYAML_FIELD_SEQUENCE(
"depends", CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL,
struct task, depends,
&string_ptr_schema, 0, CYAML_UNLIMITED),
/* The next field is the task people.
*
* YAML key: "people".
* C structure member for this key: "people".
*
* Its CYAML type is a sequence.
*
* Since it's a sequence type, the value schema for its entries must
* be provided too. In this case, it's string_ptr_schema.
*
* Since it's not a sequence of a fixed-length, we must tell CYAML
* where the sequence entry count is to be stored. In this case, it
* goes in the "n_people" C structure member in `struct plan`.
*/
CYAML_FIELD_SEQUENCE_COUNT(
"people", CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL,
struct task, people, n_people,
&string_ptr_schema, 0, CYAML_UNLIMITED),
/* The field array must be terminated by an entry with a NULL key.
* Here we use the CYAML_FIELD_END macro for that. */
CYAML_FIELD_END
};
/* The value for a task is a mapping.
*
* Its fields are defined in task_fields_schema.
*/
static const cyaml_schema_value_t task_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT,
struct task, task_fields_schema),
};
/* The plan mapping's field definitions schema is an array.
*
* All the field entries will refer to their parent mapping's structure,
* in this case, `struct plan`.
*/
static const cyaml_schema_field_t plan_fields_schema[] = {
/* The first field in the mapping is a project name.
*
* YAML key: "project".
* C structure member for this key: "name".
*
* Its CYAML type is string pointer, and we have no minimum or maximum
* string length constraints.
*/
CYAML_FIELD_STRING_PTR(
"project", CYAML_FLAG_POINTER,
struct plan, name, 0, CYAML_UNLIMITED),
/* The next field is the project start date.
*
* YAML key: "start".
* C structure member for this key: "start".
*
* Its CYAML type is a mapping pointer.
*
* Since it's a mapping type, the schema for its mapping's fields must
* be provided too. In this case, it's `date_fields_schema`.
*/
CYAML_FIELD_MAPPING_PTR(
"start", CYAML_FLAG_POINTER,
struct plan, start, date_fields_schema),
/* The next field is the project people.
*
* YAML key: "people".
* C structure member for this key: "people".
*
* Its CYAML type is a sequence.
*
* Since it's a sequence type, the value schema for its entries must
* be provided too. In this case, it's string_ptr_schema.
*
* Since it's not a sequence of a fixed-length, we must tell CYAML
* where the sequence entry count is to be stored. In this case, it
* goes in the "n_people" C structure member in `struct plan`.
*/
CYAML_FIELD_SEQUENCE_COUNT(
"people", CYAML_FLAG_POINTER,
struct plan, people, n_people,
&string_ptr_schema, 0, CYAML_UNLIMITED),
/* The next field is the project tasks.
*
* YAML key: "tasks".
* C structure member for this key: "tasks".
*
* Its CYAML type is a sequence.
*
* Since it's a sequence type, the value schema for its entries must
* be provided too. In this case, it's task_schema.
*
* Since it's not a sequence of a fixed-length, we must tell CYAML
* where the sequence entry count is to be stored. In this case, it
* goes in the "tasks_count" C structure member in `struct plan`.
* Since this is "tasks" with the "_count" postfix, we can use
* the following macro, which assumes a postfix of "_count" in the
* struct member name.
*/
CYAML_FIELD_SEQUENCE(
"tasks", CYAML_FLAG_POINTER,
struct plan, tasks,
&task_schema, 0, CYAML_UNLIMITED),
/* If the YAML contains a field that our program is not interested in
* we can ignore it, so the value of the field will not be loaded.
*
* Note that unless the OPTIONAL flag is set, the ignored field must
* be present.
*
* Another way of handling this would be to use the config flag
* to ignore unknown keys. This config is passed to libcyaml
* separately from the schema.
*/
CYAML_FIELD_IGNORE("irrelevant", CYAML_FLAG_OPTIONAL),
/* The field array must be terminated by an entry with a NULL key.
* Here we use the CYAML_FIELD_END macro for that. */
CYAML_FIELD_END
};
/* Top-level schema. The top level value for the plan is a mapping.
*
* Its fields are defined in plan_fields_schema.
*/
static const cyaml_schema_value_t plan_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct plan, plan_fields_schema),
};
/******************************************************************************
* Actual code to load and save YAML doc using libcyaml.
******************************************************************************/
/* Our CYAML config.
*
* If you want to change it between calls, don't make it const.
*
* Here we have a very basic config.
*/
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. */
};
/* Main entry point from OS. */
int main(int argc, char *argv[])
{
cyaml_err_t err;
struct plan *plan;
enum {
ARG_PROG_NAME,
ARG_PATH_IN,
ARG_PATH_OUT,
ARG__COUNT,
};
/* Handle args */
if (argc != ARG__COUNT) {
fprintf(stderr, "Usage:\n");
fprintf(stderr, " %s <INPUT> <OUTPUT>\n", argv[ARG_PROG_NAME]);
return EXIT_FAILURE;
}
/* Load input file. */
err = cyaml_load_file(argv[ARG_PATH_IN], &config,
&plan_schema, (void **) &plan, NULL);
if (err != CYAML_OK) {
fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err));
return EXIT_FAILURE;
}
/* Use the data. */
printf("Project: %s\n", plan->name);
for (unsigned i = 0; i < plan->tasks_count; i++) {
printf("%u. %s\n", i + 1, plan->tasks[i].name);
}
/* Modify the data */
plan->tasks[0].estimate.days += 3;
plan->tasks[0].estimate.weeks += 1;
/* Save data to new YAML file. */
err = cyaml_save_file(argv[ARG_PATH_OUT], &config,
&plan_schema, plan, 0);
if (err != CYAML_OK) {
fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err));
cyaml_free(&config, &plan_schema, plan, 0);
return EXIT_FAILURE;
}
/* Free the data */
cyaml_free(&config, &plan_schema, plan, 0);
return EXIT_SUCCESS;
}