19. Config: Parsing configuration files

gnatcoll provides a general framework for reading and manipulating configuration files. These files are in general static configuration for your application, and might be different from the preferences that a user might change interactively. However, it is possible to use them for both cases.

There are lots of possible formats for such configuration files: you could chose to use an XML file (but these are in general hard to edit manually), a binary file, or any other format. One format that is found very often is the one used by a lot of Windows applications (the .ini file format).

GNATCOLL.Config is independent from the actual format you are using, and you can add your own parsers compatible with the GNATCOLL.Config API. Out of the box, support is provided for .ini files, so let’s detail this very simply format:

# A single-line comment
[Section1]
key1 = value
key2=value2

[Section2]
key1 = value3

Comments are (by default) started with ‘#’ signs, but you can configure that and use any prefix you want. The (key, value) pairs are then organized into optional sections (if you do not start a section before the first key, that key will be considered as part of the “” section). A section then extends until the start of the next section.

The values associated with the various keys can be strings, integers or booleans. Spaces on the left and right of the values and keys are trimmed, and therefore irrelevant.

Support is providing for interpreting the values as file or directory names. In such a case, if a relative name is specified in the configuration file it will be assumed to be relative to the location of the configuration file (by default, but you can also configure that).

GNATCOLL.Config provides an abstract iterator over a config stream (in general, that stream will be a file, but you could conceptually read it from memory, a socket, or any other location). A specific implementation is provided for file-based streams, which is further specialized to parse .ini files.

Reading all the values from a configuration file is done with a loop similar to:

declare
   C : INI_Parser;
begin
   Open (C, "settings.txt");
   while not At_End (C) loop
      Put_Line ("Found key " & Key (C) & " with value " & Value (C));
      Next (C);
   end loop;
end;

This can be made slightly lighter by using the Ada05 dotted notation.

You would only use such a loop in your application if you intend to store the values in various typed constants in your application. But GNATCOLL.Config provides a slightly easier interface for this, in the form of a Config_Pool. Such a pool is filled by reading a configuration file, and then the values associated with each key can be read at any point during the lifetime of your application. You can also explicitely override the values when needed:

Config : Config_Pool;   --  A global variable

declare
   C : INI_Parser;
begin
   Open (C, "settings.txt");
   Fill (Config, C);
end;

Put_Line (Config.Get ("section.key"));  --  Ada05 dotted notation

Again, the values are by default read as strings, but you can interpret them as integers, booleans or files.

A third layer is provided in GNATCOLL.Config. This solves the issue of possible typos in code: in the above example, we could have made a typo when writting “section.key”. That would only be detected at run time. Another issue is that we might decide to rename the key in the configuration file. We would then have to go through all the application code to find all the places where this key is references (and that can’t be based on cross-references generated by the compiler, since that’s inside a string).

To solve this issue, it is possible to declare a set of constants that represent the keys, and then use these to access the values, solving the two problems above:

Section_Key1 : constant Config_Key := Create ("Key1", "Section");
Section_Key2 : constant Config_Key := Create ("Key2", "Section");

Put_Line (Section_Key1.Get);

You then access the value of the keys using the Ada05 dotted notation, providing a very natural syntax. When and if the key is renamed, you then have a single place to change.