Per Ola Kristensson | ANSI C application settings management

Bio
Publications
Impact / Press
Teaching
Software
Other Stuff
Blog

Description

This is a module for managing application settings. It has the following features:

  1. Loading and saving application settings to/from a textual format that is easy to read and edit by humans
  2. Relatively robust to corruptions in the textual format
  3. Simple structure that is sufficiently expressive for most actual usage scenarios: a particular setting is a triple (section, key, value)
  4. Convenient interface for accessing string, integer, long and double values
  5. Convenient interface for accessing string, integer, long and double tuples of arbitrary lengths
  6. Supports iteration over all key-value pairs for a particular section

Source code

The API is documented in the header file (settings.h).

settings.h

settings.c

You will also need strmap (an ANSI C hash table implementation for strings).

strmap.h

strmap.c

License

The code ("settings" and "strmap") is licensed under the GNU Lesser General Public License.

Syntax

The structure of a settings entity is quite simple. A settings entity is a tree of depth 2. At the top level are the section nodes that represent isolated sections of application settings (e.g. general application properties, font attributes, color attributes, and so on). At the bottom level are the key-value pairs that represent set values for keys inside a particular section.

A settings entity is read from and written to disk using a textual format. The textual format is parsed by sequentially scanning each line. Blank lines are ignored. For all other lines, leading and trailing blanks are ignored. Sections are enclosed by square brackets ("[" and "]") on separate lines. Key-value pairs are written as the key followed by a single equal sign ("="), followed by the value, all of which must be together on one separate line. A key-value pair is associated to the last seen section. A key-value pair is not valid until at least one section has been seen. All lines that start with a hash ("#") are considered comments and are ignored. Empty sections, that is, sections that do not contain any key-value pairs, are not written to disk. Empty sections can be present in the textual format. However, they are ignored when creating a settings entity in memory.

A value can be a single or multi-valued. A multi-valued value is a tuple. Tuples are separated by comma and are singly-typed. This means that a tuple can only contain objects of one kind (e.g. integer tuples, double tuples, etc.).

The fragment below may serve as a reference:

# This comment will be ignored
[Section Title 1]
# Below are two examples of key-value pairs
Key1 = Value1
Key2 = Value2
# Below is an example of an integer tuple
Key3 = 0,1,2,3

[Section Title 2]
# The key-value pair below is distinct from the one above
Key1 = Value1

[Empty Section]
# This section is legal but empty and will be ignored

The parser is relatively robust to leading and trailing blank characters around section names, keys, values, tuple elements, etc.

Notes

First, the code is not thread-safe. Use external synchronization.

Second, the implementation copies string values upon insertion and retrieval. This guarantees that no internal references are exposed to the client after the settings entity has been deleted from memory. There is one exception, however. When iterating over all key-value associations in a section, the internal strings are temporarily exposed as constant pointers to the internal string buffers. This is because it is very slow and cumbersome to copy all key-value associations to a buffer via a callback function interface. It is recommended not to keep the pointers to the internal string buffers around out of scope of the callback function. Otherwise, you risk having wild pointers in your code after you have deleted the settings entity.

Example usage

The settings file below will be used in the examples:

[Application]
Title = Test Title
Version = 1.0.5

[Background]
Color = 255, 127, 127, 127

[Foreground]
Color = 255, 255, 255, 255
Text Color = 255, 0, 0, 0

[Edit Menu Options]
C&ut  Ctrl+X = 1
&Copy  Ctrl+C = 2
P&aste  Ctrl+V = 3

The code fragments below create a Settings object, insert and retrieve a couple of string associations, write out the Settings object to disk, and finally destroy the Settings object.

#include "settings.h"

...

FILE *f;
Settings *settings;
char buf[255];
int result;

f = fopen("settings.txt", "r");
if (f == NULL) {
    /* Handle error... */
}
settings = settings_open(f);
fclose(f);
if (settings == NULL) {
    /* Handle read error... */
}
/* Insert a new key-value pair */
settings_set(settings, "Application", "Has Started", "True");
/* Retrieve a value */
result = settings_get(settings, "Application", "Version", buf, sizeof(buf));
if (result == 0) {
    /* Handle value not found... */
}
printf("version of this application: %s\n", buf)
/* Insert a key-value pair that will create a new section */
settings_set(settings, "Window Properties", "X", "0");

...

/* Save the settings object to disk in textual form */
f = fopen("settings.txt", "w");
if (f == NULL) {
    /* Handle error... */
}
result = settings_save(settings, f);
fclose(f);
if (result == 0) {
    /* Handle write error... */
}
/* When done, destroy the settings object */
settings_delete(settings);

...

The code fragments below demonstrate how to read a tuple:

#include "settings.h"

...

Settings *settings;

...

int argb[4];
int result;

result = settings_get_int_tuple(settings, "Foreground",
    "Text Color", argb, sizeof(argb));
if (result == 0) {
    /* Handle value not found... */
}
printf("alpha: %d red: %d green: %d blue: %d\n",
    argb[0], argb[1], argb[2], argb[3]);

...

The code fragments below demonstrate how to iterate over all key-value associations in a section:

#include "settings.h"

...

Settings *settings;

...

static void iter(const char *key, const char *value, const void *obj)
{
    printf("menu item: %s menu position: %s\n", key, value);
}

...

settings_enum(settings, "Edit Menu Options", iter, NULL);

...