Configuration File Support

Sulfur allows applications to load option values from configuration files in a standard format, and represents the contents of those files in Registry objects which resemble dictionaries. Option values in a Registry may be automatically passed to plug-ins as they are loaded.

The configuration file format is inspired by the configuration files of the BIND nameserver and of APT, the new Debian GNU/Linux package management tool. The data model supported by this format is fairly straightforward: the configuration data is represented by a set of string values, or lists of string values, arranged in a hierarchy.

Each value's place in the hierarchy can be specified by a path, not unlike the paths used to refer to files and directories on disk. Sulfur uses :: as a path separator; thus, the name Foo::Bar::Baz refers to a value three levels down in the hierarchy. In a configuration file, braces can be used to indicate scope, instead of or in addition to fully- or partially-qualified names.

Otherwise, a Sulfur configuration file is simply a sequence of strings, separated by whitespace and grouped by the use of semicolons. Typically, they come in pairs: a name and a value, but that is not always the case as the sample configuration file shown here will illustrate.

Example 3-1. Sample Configuration File

defaults {
    ports {
        "/dev/pilot";  List of values for defaults::ports
        "/dev/ttyS0";
        "/dev/ttyS1";
    };
	
    user "Rob Tillotson";
    data-directory "/home/rob/Palm";
    default-port "/dev/pilot";
};

Pyrite {
    Sync::conduits {  Partially-qualified name
        Backup; Install; TimeSync; ... with another list of values.
    };

    Conduit "Backup" {  Like Conduit::Backup but more readable.
        debug 1;
    };
};
      

The Sulfur.Registry module contains the Registry class, which handles values loaded from configuration files. Conceptually, a Registry is similar to a dictionary, with one major difference: it is multi-leveled.

In order to support sets of configuration files, a Registry object is actually a stack. Each time a configuration file is loaded, it is pushed on this stack. Stack levels may be popped at any time, and marked so that your application knows where each one came from. At any given time, your application's view of the Registry's contents is an overlay of all of the stacked configuration sets.

Registry objects have the following methods:

has_key (path)

Similar to the same-named method on dictionary objects.

get (path, default=None)

Similar to the same-named method on dictionary objects.

load (file, label=None)

Load a configuration file, and push it onto the top of the stack. The file argument can be either a file name, or a file-like object (which only has to support read with an empty parameter list). The optional label parameter is used to mark this level of the stack, for later checking with the label method.

label

Return the label of the topmost stack level.

pop

Pop the top level off the stack, returning it.

push (object)

Push something onto the stack. In order for the Registry object not to break, the object pushed on the stack must have the same interface as a RegistryLevel.

Each level of the Registry is implemented as a RegistryLevel object; a RegistryLevel is essentially a dictionary (it is derived from UserDict in the Python library), but it also has a label attribute for use with Registry objects.