/* * SPDX-License-Identifier: ISC * * Copyright (C) 2019 Michael Drake */ #include #include #include /****************************************************************************** * 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 \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; }