469 lines
14 KiB
C
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;
|
|
}
|