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
|
***************************************
**Config**: Parsing configuration files
***************************************
.. highlight:: ada
`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 :file:`.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 :file:`.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
:file:`.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.
|