Next: , Previous: (dir), Up: (dir)

Config Files

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.


Next: , Previous: Top, Up: Top

1 Use Cases


Next: , Previous: Use Cases, Up: Use Cases

1.1 Saved Games

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.


Next: , Previous: Saved Games, Up: Use Cases

1.2 Global/Local

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.


Next: , Previous: Global/Local, Up: Use Cases

1.3 Registry

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.


Previous: Registry, Up: Use Cases

1.4 Multiple processes

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.


Next: , Previous: Use Cases, Up: Top

2 Package description

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.


Next: , Previous: Package description, Up: Package description

2.1 Keys and values

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.

  1. Keys are of type Standard.String or Standard.Wide_String. There are two separate packages (Config_Files and Wide_Config_Files) for String and Wide_String keys and values, following the Ada example of Text_IO and Wide_Text_IO.
  2. Each key denotes either a single value or a list of subkeys.
  3. Keys may be case-sensitive or case-insensitive. This choice is made on a config file basis; either all keys in a single file are case-sensitive, or all are case-insensitive. The user specifies the choice when a config file is created; the choice is indicated by the file when the file is read.

    Note that the notion of “lowercase” is not very well defined when using Wide_String keys.

  4. Keys consist of a sequence of identifiers separated by dots. The limit to the length of the sequence is implementation defined, but is at least 5.
  5. Reading and writing String and Wide_String values is directly supported; String values in package Config_Files, Wide_String values in package Wide_Config_Files. Leading and trailing whitespace (space and tab) are ignored.
  6. Reading and writing scalar numeric values is supported by generic routines. 'Value and 'Image are used in the body of the generic.
  7. Reading and writing Ada.Calendar.Time is directly supported, in the child package SAL.Config_Files.Time.
  8. Reading and writing composite types (records or arrays) is supported by using the layered key hierarchy; users must write Read and Write subprograms. See SAL.Config_Files.Time for an example.
  9. Generic read and write operations for an arbitrary user type is supported; the user must provide a To_String or From_String subprogram that represents the type as a String or Wide_String. The string value must be compatible with the Config_Files file format.

    This may be used to store opaque binary values (ie bitmaps for icons).


Previous: Keys and values, Up: Package description

2.2 Basic operations

The basic operations on a configuration object are:

Open
Associates a configuration file with the configuration object.

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.

Set_Search_Path
A search path is a list of directories; the operating system convention for search paths is used.

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.

Flush
Write data to the read-write configuration file in the configuration object. This may create a new file, or may update data in an existing file. If there is no read-write file, an exception is raised.

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.

Read
Read the value of a key from the configuration object. The configuration files associated with the configuration object are searched in the order they were opened.

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.

Read List
Return the list of subkey names for a given key.
Write
Write the value of a key to the configuration object, in the read-write file. An exception is raised if there is no read-write file.
Write Comment
Comments are associated with a key, and are preserved thru open and flush. Comments are intended to guide manually editing the file.
Delete
Delete a key from the configuration object. If the key is a root key, all subkeys it denotes are deleted.


Previous: Package description, Up: Top

3 File Format

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


Previous: File Format, Up: File Format

3.0.1 Rejected file formats

Several alternate file formats were considered and rejected.


Next: , Previous: Rejected file formats, Up: Rejected file formats
3.0.1.1 XML

This was considered too hard to edit directly. It requires matching tags, and quoting ampersands, quotes, <, >, etc.


Next: , Previous: XML, Up: Rejected file formats
3.0.1.2 Windows ini

This only provides one layer of key hierarchy; we want at least 5.


Previous: Windows ini, Up: Rejected file formats
3.0.1.3 X style files

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