The SAL.Config_Files package supports simple configuration files. These are similar in intent to Windows "ini" files, Java property files, and X style files.
This document describes the API and the file format for SAL config files. See the actual package specification for more details on the API.
As supporting material, it gives example use cases for config files, gives some rationale for the specification, and discusses some design decisions that came up during writing of the reference implementation.
The requirements presented here are distilled from a discussion on the comp.lang.ada newsgroup in 2003 or so. The package SAL.Config_Files implements most but not all of these requirements; it also provides additional functionality that I've needed.
An application could use config files to store multiple saved games for one user. For example, a user could have two card game configurations; one that makes it easy to cheat and win, one that makes it harder to win.
One way to do this is to specify the config file on the command line of the card game application. The application should also allow the user to select a different config file after the application has started.
The file name may be relative or absolute.
An application may have a standard config file (/etc/app/config), a system-wide local config file (/usr/local/app/config), and a user config file (~/.appconfig). When a configuration setting is changed by the user, it is saved in the user config file. The other config files can be changed only with a text editor. If a setting is not given in the user file, it is looked for in the system-wide local config file, then in the standard config file.
To implement this, Config_Files must keep track of what file a configuration setting comes from, and where to write it. If it comes from the user file, it is always written back there. If from the local file, it is written to the user file if changed. One way to do this is keep a separate tree of values for each config file read; when a value from the local tree is written, it is moved to (or duplicated in) the user tree, so it will be written to the user file.
Key/value pairs can also be stored in the MS Windows Registry, or in the Gnome gconf package. The Config_Files package provides simpler functionality. In particular, the Registry and gconf provide ways for multiple independent processes to share key/value pairs, with atomic access and notification of change. Config_Files does not support this.
Config_Files supports multiple independent processes only for read-only config files. To ensure that config files are not corrupted on write, only one process is allowed to open a config file for writing. This is enforced using operating system file locking or an equivalent mechanism.
This avoids the need for a complex multiple access protocol; if your application needs write access by mulitple processes, use the Windows registry or Gnome gconf.
A configuration file is represented in the Config_Files package by an object of type Configuration_Type. A Configuration_Type object can be associated with several configuration files. The term "configuration file" will always refer to a single file; the term "configuration object" will refer to an object of type Configuration_Type.
A configuration object stores data as key/value pairs.
Keys consist of a sequence of identifiers, defining a grouping or hierarchy of subkeys and values. For example, the geometry of an application window could be stored in the following keys:
geometry.location.top = 10 geometry.location.left = 20 geometry.size.width = 100 geometry.size.height = 200
A key can denote a single value, or the list of values defined by the set of subkeys starting with the given key. A key that denotes a single value is called a leaf key. A key that denotes a list of subkeys is called a root key (a root key can also have a value). For example, the leaf key "geometry.location.top" denotes the value 20; the root key "geometry.location" denotes the list of keys "geometry.location.top" and "geometry.location.left". The root key "geometry" denotes the list of all four keys.
Values are normally of a scalar type; composite types are normally represented by a list of keys, as for the "location" type in the example. However, a mechanism is provided that allows users to define a representation for any type.
Note that the notion of “lowercase” is not very well defined when using Wide_String keys.
This may be used to store opaque binary values (ie bitmaps for icons).
The basic operations on a configuration object are:
The file name may be an absolute name, or it is looked for on the search path.
Configuration files may be read-only or read-write. Only one file in a configuration object may be opened read-write. Multiple processes, or multiple Ada tasks, may open a config file read-only; only one process or task may open a config file read-write. An exception is raised if a second process attempts to open the file read-write.
Implementation advice: One way to enforce the single-writer requirement is to hold an operating system lock on the file while it is open.
The user indicates what happens if the specified configuration file
does not exist; either an exception is raised, or the internal data
structure is created, with no key/value pairs.
A default search path is specified when the config files package is compiled. If the package is a shared library, the default path is operating system specific. If the package is statically linked with one application, the default path can be application specific.
The application can override the default search path at run time.
Directories on the search path are marked read-only or read-write;
files found in those directories are opened accordingly.
Note that the application should open a local read-write file whenever it expects to write changed configuration data.
The order in which key/value pairs are stored in a configuration file is not preserved when keys are read in and then flushed.
Files are also flushed when the configuration object object goes out of scope.
A file may be flushed many times; the data is written to the file each
time. The configuration object internal data is only discarded when
the object goes out of scope.
The user provides a default value that is returned if the key is not found in the configuration object. Alternately, the user may indicate that an exception should be raised if the key is not found.
If there are duplicate keys in a single configuration file (only possible due to editing outside the Config_Files API), the client may indicate whether the duplicate is ignored or an exception is raised when the file is opened. If ignored, the last found value is retained in the in-memory storage. Only one copy is written when the config file is flushed.
This operation is supported for user data types via generic
subprograms.
The Config_Files file format is the same on all operating systems, except that the normal operating system line-ending convention is used. This makes it possible to copy files between systems (with normal line-ending changes), and simplifies installation scripts that edit config files.
Editing may be necessary when a program is moved, drive numbers are rearranged on Windows, or a global/local scenario is used.
The file format is the Java property file format. A key/value pair is written on a single line; an '=' separates the key from the value. The end of line determines the end of the value.
For example:
Strings.Violins=Stradivarious Strings.Quoted=he said "hi there & goodbye" Numeric.Float.A_Float=3.14159E+00 Numeric.Interfaces.C.An_Int=2 Numeric.Interfaces.C.An_Unsigned=124076833
Several alternate file formats were considered and rejected.
This was considered too hard to edit directly. It requires matching tags, and quoting ampersands, quotes, <, >, etc.
This only provides one layer of key hierarchy; we want at least 5.
This is somewhat difficult to parse. It also is somewhat difficult to edit directly, since it requires matching Section and EndSection tags.
Last modified: Wed Apr 25 06:28:17 Central Daylight Time 2018