File: main.c

package info (click to toggle)
libcyaml 1.4.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 980 kB
  • sloc: ansic: 20,884; makefile: 166; sh: 13
file content (468 lines) | stat: -rw-r--r-- 13,873 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
/*
 * 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;
}