libt3config
libt3config Documentation

Introduction

The libt3config library provides functions for reading and writing simple structured configuration files.

libt3config is part of the Tilde Terminal Toolkit (T3).

Information is available about the syntax of the configuration files, and schema syntax. Finally there is the API documentation.

Example

The example below shows a small program which loads a configuration file and prints it to screen. It can be passed a schema which will be used for validating the configuration, and can optionally enable the include mechanism.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <unistd.h>
#include <stdarg.h>
#include <t3config/config.h>
#define STRING_DFLT(x, dflt) ((x) != NULL ? (x) : (dflt))
static t3_config_t *config;
static t3_config_schema_t *schema;
/* Alert the user of a fatal error and quit. */
static void fatal(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
exit(EXIT_FAILURE);
}
/* Cleanup memory used by config and schema. */
static void cleanup(void) {
/* Both t3_config_delete and t3_config_delete_schema allow NULL pointers to be passed. */
}
int main(int argc, char *argv[]) {
FILE *file = stdin;
const char *path[2] = { NULL, NULL };
const char *name = "<stdin>";
int c;
/* Set locale */
setlocale(LC_ALL, "");
/* Request as much information about errors as possible. */
/* Make sure we free memory on exit (useful for debugging purposes). */
atexit(cleanup);
while ((c = getopt(argc, argv, "s:hi::")) >= 0) {
switch (c) {
case 's': {
/* Load a schema. */
FILE *schema_file;
if (schema != NULL)
fatal("more than one schema option\n");
if ((schema_file = fopen(optarg, "r")) == NULL)
fatal("error opening schema '%s': %m\n", optarg);
if ((schema = t3_config_read_schema_file(schema_file, &error, &opts)) == NULL)
fatal("%s:%d: error loading schema '%s': %s: %s\n", STRING_DFLT(error.file_name, optarg),
error.line_number, optarg, t3_config_strerror(error.error), STRING_DFLT(error.extra, ""));
fclose(schema_file);
break;
}
case 'h':
printf("Usage: test [-s <schema>] [-i[<include dir>]] [<input>]\n");
exit(EXIT_SUCCESS);
case 'i':
/* Configure the options for allowing the include mechanism. */
path[0] = optarg == 0 ? "." : optarg;
opts.include_callback.dflt.path = path;
opts.include_callback.dflt.flags = 0;
break;
default:
break;
}
}
/* Open the input file if necessary. */
if (argc - optind == 1) {
if ((file = fopen(argv[optind], "r")) == NULL)
fatal("could not open input '%s': %m\n");
name = argv[optind];
} else if (argc != optind) {
fatal("more than one input specified\n");
}
/* Read the configuration. */
if ((config = t3_config_read_file(file, &error, &opts)) == NULL)
fatal("%s:%d: error loading input: %s: %s\n", STRING_DFLT(error.file_name, name), error.line_number,
t3_config_strerror(error.error), STRING_DFLT(error.extra, ""));
/* Close the file now that we are done with it. */
fclose(file);
/* Use the schema (if any) to validate the input. */
if (schema != NULL && !t3_config_validate(config, schema, &error, T3_CONFIG_VERBOSE_ERROR | T3_CONFIG_ERROR_FILE_NAME))
fatal("%s:%d: error validating input: %s: %s\n", STRING_DFLT(error.file_name, name), error.line_number,
t3_config_strerror(error.error), STRING_DFLT(error.extra, ""));
/* Retrieve the section named "foo". */
foo = t3_config_get(config, "foo");
if (foo != NULL) {
/* Retrieve the integer "bar", with default value of 0. This verifies
that bar has type int, which can already be checked through the schema. */
int bar_int = -1;
t3_config_t *bar = t3_config_get(foo, "bar");
bar_int = t3_config_get_int(bar);
}
}
/* Add a boolean value named "xxx" with value true. */
t3_config_add_bool(config, "xxx", t3_true);
/* Write the input to screen for checking the result. */
t3_config_write_file(config, stdout);
return EXIT_SUCCESS;
}

Guidelines

The API of libt3config is designed such that it is possible to write code that only checks for errors in a limited number of places. That is, functions that fetch or store values in the config, all accept NULL as the pointer to the (sub-)config to add to, and successful return values are 0 to allow logical OR-ing of the values to check for simple errors. For functions that fetch values, reasonable defaults are provided.So, the following code is guaranteed not to crash:

int has_errors = 0;
has_errors |= config == NULL;
has_errors |= t3_config_add_bool(config, "flag", t3_true);
t3_bool value = t3_config_get_bool(t3_config_get(config, "flag"));