The program options library is intended to make managing of configuration in D programs easy. Instead of reinventing the wheel and implementing the same code to to manage configuration over and over again, it is possible now to use library with simple API for common tasks. Common tasks with managing program configuration include: reading configuration options from physical media, saving them back, presenting description of available options to user, checking for incorrect usage, working with different sources of options, checking option values for validity, synchronizing with physical media, etc.

The main goal for this release was to make library usage very easy for common use cases. Another goal was to make it easy to implement new physical backends for storing options, and also to make source code and usage of library more D-like.

To achieve the above goals I changed the architecture of the previous version, so it is currently based on a concept of a root class to access options (ProgramOptions class) and different classes which define specific backends (e.g. CommandLineStorage, ConfigFileStorage). The standard way to access options is through the root class (ProgramOptions), which then searches for concrete options in a prioritized set (stack) of physical storages. From the library users' point of view, the interface to an application's options is similar to that of an associative array.

This new library has gained several abilities which were not available in the original:

  • ability to store options in backends
  • available storages: CommandLineStorage, ConfigFileStorage, EnvironmentStorage, DbStorage (using ddbi)
  • interfacing to whole set (ProgramOptions) and every concrete storage
  • easy method to add new backends using [read|save]Physically[One|All] methods
  • prioritizing access of options backends
  • removed ability to store values into concrete variables (it was insecure)
  • notification about option changes (it is possible to register for specific events)
  • easy synchronization of options with backends using synchronize() method
  • different synchronization methods: direct or cached
  • interface to stack of options similar to associative arrays
  • possibility to have many stacks of options in a program (ProgramOptions is not a singleton)
  • custom file loaders and savers with simple ones set by default
  • possibility to compose one option which is defined in different storages into one value
  • generalization of short option, which now is just an alias for a normal option
  • reading/storing lists in backends (with user defined separators)
  • support for escape sequences
  • easy initialization of options stack, with no temporary variables
  • checking for options integrity (not possible to define same option twice in storage, option type must be same in whole stack)
  • Tango-like directory structure and naming convention similar to other D programs
  • built-in regular expressions for option values
  • user defined formatters for options
  • special types of options: SelfName, SelfDir, SelfPath, RegExpOptions and possibility to create user defined options
  • reworked positional options in CommandLineStorage (currently it is special type option)
  • built-in response files support for CommandLineStorage
  • functions for checking for obligatory options, conflicting options and dependant options
  • comments for DDoc
  • unit tests
  • many smaller improvements and bugfixes

Below you can find simplest usage example for command line:


import doost.util.config.ProgramOptions;
import doost.util.config.CommandLineStorage;

void main(char[][] args) {
  //Definition of simplest options stack
  auto po = (new ProgramOptions)
		.next(
            (new CommandLineStorage(args))
		.caption("Command line options")
                .options()
                  ("help,h", "produce help message")
                  ("compression", define!(uint), "set compression level")
                  ("sync", "stores cmdline options in persistent backend")
	 	  ()
            );

  //Conecting to storages
  po.connect();

  //Reading options

  if ("help" in po) 
    writefln(po);

  if ("compression" in po) 	
    writefln("Compresson level set to: ", po["compression"]);

  //Disconnecting storages
  po.disconnect;
}

And for combining command line storage and config file storage:

import std.stdio;

import doost.util.config.ProgramOptions;
import doost.util.config.CommandLineStorage;
import doost.util.config.ConfigFileStorage;

void main(char[][] args) {
    //Definition of simplest options stack
    auto po =   (new ProgramOptions)
                    .caption("Description of program.")
                    .next(
                (new CommandLineStorage(args))
                    .caption("Command line options")
                    .options()
                        ("help,h", "produce help message")
                        ("compression", define!(uint).defaultValue(5), "set compression level")
                        ("sync", "stores cmdline options in persistent backend")
                        ("shapes", define!(char[][]), "shapes of something")
                        ("shapes=2", "your description", new PositionalOption)
                        ()
                    .next(
                (new ConfigFileStorage("properties.ini"))
                    .caption("Config file options:")
                    .options()
                        ("compression", define!(uint), "set compression level")
                        ("database.hostip", define!(char[]), "ip of host")
                        ("database.user", define!(char[]), "default user")
                        ("database.port", define!(char[]), "port")
                        ()
                ));
    //Conecting to storages
    po.connect();

    //Reading options

    if ("help" in po)
        writefln(po);

    if ("compression" in po)
        writefln("Compression level set to: ", po["compression"]);

    if ("shapes" in po)
        writefln("Shapes: ", po["shapes"]);

    writefln;
    if ("database.hostip" in po)
        writefln("Host ip: ", po["database.hostip"]);

    if ("database.user" in po)
        writefln("User: ", po["database.user"]);

    if ("database.port" in po)
        writefln("Port: ", po["database.port"]);

    //Disconnecting storages
    po.disconnect;
}

There are a lot of different examples for different storages to be found in the file: examples/util/config/FunctionTest.d .

Currently there are following backends defined for program options:

  • CommandLineStorage - handles command line options
  • ConfigFileStorage - handles options from ini-like files
  • EnvironmentStorage - handles options from environment variables
  • DbStorage - handles options from database through the ddbi interface

I will be happy to get feedback. You can find few things to consider in file doc/todo.txt. You can also write other physical backends to the library. If you want I can host them in doost svn.

Also as I am not a native English-speaker, I would appreciate help (e.g. in form of patches or by e-mail) with improving documentation comments and corrections for misspelled words.