AlmaConf : A configuration file loader.

AlmaConf offers a language to write configuration files and an Ruby API to load them into hash objects.
The AlmaConf language is a subset of the Alma language and can be used as a descriptive language for other purposes.


1 Introduction.

First, I have to apologize for my poor English.

1.1 Overview.

AlmaConf loads configuration files written in the AlmaConf language in a hash, this hash entries may have a hash as value and so on..., they also may have arrays, strings, numbers, true, false, nil as value. Arrays may contain the same values and so on...
AlmaConf also has the ability to include or source other configuration files and perform variable substitution.

AlmaConf has some similarities with XmlSimple, There is an obvious difference, XmlSimple is based on XML as AlmaConf is based on Alma.
For now, AlmaConf only load files into hashes, and doesn't create a file from a hash, it doesn't have all the XmlSimple options, only those which I think important are implemented.

1.2 Quick start.

We'll take the example of the well-known YAFU (Yet Another Ftp Uploader) which behaviour is based on a configuration file that may be this one.

    Daily upload to mbros.org
    Maintainer    : John Doe john@doe.net
    Last modified : 2006-02-04 (groucho added)

&config{ 
    name=daily
    &mbros{
        host=ftp.mbos.org
        login=mbros
        passwd=blurb
    }
    &harpo{
        dir=/home/harpo/web/
        exclude=secrets/
    }
    &groucho{
        dir=/home/groucho/web/
    }
    &task{
        id=upload
        to=mbros
        from=&{ ; 
            harpo;
            groucho;
        }
    }
}

It will load this file into a hash using AlmaConf and get a reference to this object :

{
    "name"=>"daily",
    "harpo"=>
    {
        "dir"=>"/home/harpo/web/",
        "exclude"=>"secrets/"
    }
    ,
    "task"=>
    {
        "from"=>
        [
            "harpo",
            "groucho"
        ]
        ,
        "to"=>"mbros",
        "id"=>"upload"
    }
    ,
    "mbros"=>
    {
        "passwd"=>"blurb",
        "host"=>"ftp.mbos.org",
        "login"=>"mbros"
    }
    ,
    "groucho"=>
    {
        "dir"=>"/home/groucho/web/"
    }
}

This is the Yafu program :

unless ARGV.size == 1
  $stderr.puts "usage: yafu config-file"
  exit 1
end

require 'alma/almaconf.rb'
hConf = AlmaConf.get( ARGV[0] )

require 'alma/misc.rb'
print( Misc::pins( hConf ) )

Well, Yafu is still not finished yet...

There are three parts in this program, first it obtains the configuration file name from the command line. then it loads this file into a Hash by these two lines :

require 'alma/almaconf.rb'
hConf = AlmaConf.get( ARGV[0] )

The last two lines print the resulting object.
The Misc module is shipped with Alma, and the pins method name stands for 'pretty inspect', it returns a string representation of the object as shown above.

Yafu, after having loaded the file can access the task id by this simple line of code :

taskId = hConf['task']['id']

1.3 More.

The example above is the simplest way to invoke AlmaConf, The get method also accepts options, They are specified as key => value pairs in a hash as a second argument of the get method.

Some are options to the Alma parser, they corresponds to the command line options of the alma.rb script, but, in this context, only the almalib, trace and verbose options are meaningful.

The almalib option lets you specify the search path for the configuration file and the files included by the configuration file. It can be an array of directories, the search path is handled by Alma as specified in the Alma documentation.

Other options are specific to AlmaConf, some are borrowed from XmlSimple, and I gave these options the same name as in XmlSimple.
Currently, the ForceArray, KeepRoot and SuppressEmpty options are implemented.

2 Language.

2.1 Introduction.

The AlmaConf description language is based on the Alma language which is basically a procedural language. However, Alma can be used to define descriptive languaqes which use the Alma syntax and can be parsed by the Alma parser with few development needs,
Currently, AlmaConf is less than 150 lines of Ruby (including the license reminder) ... far less than this doc...

You may want to know more on the Alma syntax, but I hope that the following explanations will be sufficient to use AlmaConf which uses a little subset of Alma.
Moreover, I will not always use the same names to refer to the language constructs as the Alma names do not suit to a descriptive language.

2.2 Language constructs.

They are few and most appear in the Yafu example of the introduction.
This chapter is probably boring, but the language follows the 'less surprise' principle.
You may it read quickly, but look at the examples and refer to the doc when you'll use AlmaConf.

2.2.1 Sets.

If you already know Alma, they correspond to Alma functions with no positional arguments.

Syntax :

&name{ constructs }

Note : The opening curly brace must directly follow the name.

name is a string of characters, any combination of letters, digits and underscores, provided the first character is a letter and the name length is not greater than 27 characters.

constructs may be any number of languages constructs, arrays (discussed later) are silently but wisely ignored.

A set generates a hash wich is an entry with name as key in the higher level hash.
There may be several sets of the same name in the same set, in this case values are put into an Array :
name => [ value1, value2, ... ]
The ForceArray option may be use to put single sets into an array.

All constructs at the highest level of a configuration file must be sets to be recognized.

Example :

&config{
    &host{
        name=ftp://ftp.mbros.net
    }
}
result :
{
    "host"=>
    {
        "name"=>"ftp://ftp.mbros.net"
    }
}

The 'host' set contains an attribute wich name is 'name'. Attributes are discussed next chapter.
No config set entry appears in a higher level set, in order to keep shortest as possible the path to data, it is the default behaviour of AlmaConf when there is only 1 highest level set (which I recommand). This can be overriden by the KeepRoot option

There is a special kind of set, the so-called feedback set, its entries are put in the higher level set and it does not generate an entry with its name as a key.
This set is distinguished by its name <<, here is an example of its use :

&config{
    &host{
        &<<{ name=ftp://ftp.mbros.net }
    }
}
result :
{
    "host"=>
    {
        "name"=>"ftp://ftp.mbros.net"
    }
}

It gives the same results as the previous example and does not seem to be very useful. Its usefulness will appear later.

2.2.2 Attributes.

They correspond to keyword arguments in Alma.

Syntax :

name=construct

Note : The equal sign must directly follow the name and directly preceed the construct. This may change in a later version of Alma.

name follows the same rules as set names.

A construct may be a string or an array.
A string is any number of characters. when whitespaces or closing braces appear in a string, they must be quoted (or the string be quoted) See the 'quoting chapter' below.

Arrays are discussed below too.

An attribute generates an entry in a higher level hash with name as a key and the string or array as value.
There should not be several attributes with the same name in a set, when it occurs, only the last is kept back.
An attribute may have the name of sets of the same set, in this case its value is put at the end of the array of values of these sets.

2.2.3 Arrays.

They correspond to the 'void' function in Alma.

Syntax :

&{ ; construct1; construct2; ... ; }

Array entries are semicolon-delimited.
Note : There must be a semicolon before the first array entry and after the last entry, leading unquoted whitespaces are not part of an entry. This is for indentation purpose.
The notation before is equivalent to :

&{ ;
    construct1;
    construct2;
    ... ;
}

An array entry may be a string, 1 array or 1 set.

A string is any number of characters after the last leading unquoted whitespace, when semicolons or closing braces appear in a string, they must be quoted (or the string be quoted) See the 'quoting chapter' below.
When whitespaces have to appear at the beginning of a string, they have to be quoted.
When more than 1 array or set is defined in an array entry, the last will be the array entry value.

Example :

This array is the value of the attribute 'foo', it contains 2 entries, a string and a set.
&config{
    foo=&{ ; 
        blah;
        &bar{ a=bar b=blob };
    }
}
result :
{
    "foo"=>
    [
        "blah",
        {
            "bar"=>
            {
                "a"=>"bar",
                "b"=>"blob"
            }
        }
    ]
}

The bar entry might not be useful, in this case the << set is useful to minimize the path to data.

Examples :

&config{
    foo=&{ ; 
        blah;
        &<<{ a=bar b=blob };
    }
}
result :
{
    "foo"=>
    [
        "blah",
        {
            "a"=>"bar",
            "b"=>"blob"
        }
    ]
}

Empty arrays :

&{}
&{ ; }

Arrays containing an empty string entry :

&{ ; ; }
&{ ;
   ;
}

2.2.4 Comments.

Everything (except AlmaConf directives described later) outside the highest level sets is treated as a comment, i.e. discarded.

Everything inside a construct which is not recognized as a construct or as part of a construct is treated as a comment.
Therefore, you must quote closing braces everywhere you want them to be part of a comment, you must quote semicolons in arrays and equal signs in sets.

The safest way to insert comments is to use the Alma comments. a Alma comment starts with &# and ends with an end-of-line included.
The comment below safely includes braces and a semicolon.

&config{
    name=bar    &# a bar is a {kind/instance} of foo ;
}

As noticed before, arrays output is discarded in sets, this may be useful to comment chunks of AlmaConf constructs.

Example :

This comment may contain ; = { } & but quote \&{ and `&foo{`
&conf{
    &# this is a safe &{comment
    &Set_01{
        a=A     this is a comment but quote \=
        b=&{ blob=ignored ;
            &# a safe comment too
            x; y; z;
        }
        &{  This array in a set will be discarded
            &bar{
                whatever=whatever ;
                &foobar{ c=C };
                xxx;
            }
        }
    }
}
Result :
{
    "Set_01"=>
    {
        "a"=>"A",
        "b"=>
        [
            "x",
            "y",
            "z"
        ]
    }
}

Note that directives, discussed later, when not quoted and outside an Alma comment are always executed, even in a discarded set.

2.2.5 Quoting.

Quoting inhibits the evaluation of characters by Alma.

The backslash '\' can be used to quote a single character, to prevent the backslash to be interpreted it needs to be quoted like this \\ or by using a backquotes pair.

The backquotes '`' can be used to quote a single character or a string by enclosing them. Inside a quoted string, only the '\`' expression is evaluated and replaced by a backquote.

Outside any set, strings corresponding to the beginning or a set or an array needs to be quoted, quote '&[name]{ ' if you do not want it to be interpreted (quoting the ampersand is sufficient).

Everywhere else closing braces '}' must be quoted, Equal signs '=' must be quoted in a set, semicolons ';' in an array

An example will be clearer :

    This the `&config{ id=conf01 }` file
&config{
    id=conf01   quote  \= and \}
    descr=`Shows the use of \`strings\` and \\.`
    array=&{ ;
        \;;
        \};
    }
}
returns
{
    "descr"=>"Shows the use of `strings` and \\.",
    "id"=>"conf01",
    "array"=>
    [
        ";",
        "}"
    ]
}
Note : A '\' of the '\\' at the end of the descr string was added by inspect.

2.2.6 Examples.

&config{    &# this set contains 2 attributes and a set

    attrib1=daily
    
    &# attribute value is an array containing
    &# a string a set and an array
    attrib2=&{ ;
        foo;
        &<<{ a=1 b=2 };
        &{ ; blah; blurb; };
    }

    &aSet{ x=y }
    
}

This configuration file, when loaded by AlmaConf.get, returns :

{
    "attrib1"=>"daily",
    "attrib2"=>
    [
        "foo",
        {
            "a"=>"1",
            "b"=>"2"
        }
        ,
        [
            "blah",
            "blurb"
        ]
    ]
    ,
    "aSet"=>
    {
        "x"=>"y"
    }
}

2.3 Directives.

Directives are constructs which lets you perform tasks as including other configuration files or define variables.

2.3.1 _SETV and substitution.

The _SETV directive lets you define and set variables.

Syntax :
&_SETV{ ; varName; value; }

varName follows the rules of Alma variables names which, more or less, follow the rules of Ruby variables names. For more precisions, you may have a look at the Alma documentation but you will probably only need a few kinds of variables.

Variable names like @var refer to the corresponding instance variables of the AlmaSem class. There is only one instance of the AlmaSem class created by the class AlmaConf get method, therefore you may consider this variables global to all the configuration documents parsed by a get invocation. You may use any instance variable name provided the character following the @ is not an underscore.

Variable names like var refer to variables which are local to a configuration file, they are shared with configuration files included by the _SOURCE directive described below.
These variables are said to be Alma-local You may use any sequence of letters, digit or underscore for these names provided they start with a lowercase letter.

value is a string, this string will be substituted to varName in substitution constructs.
It may also be one of the built-in variables name as nil, true, false or an integer which stand for these values.
An integer is a sequence starting with a digit or a '+' or '-', any value sarting with by a '+', '-', or digit should be a valid integer (other chars must be digits). This may be fixed.

A substitution construct has this format :
&${varName}
Substitution may occur about anywhere except in a set name (This may change in a future version of Alma).

Example :

&config{
    &_SETV{ ; foo; bar; }
    &${foo}=foo&${foo}
}
Result :
{
    "bar"=>"foobar"
}

2.3.2 _SOURCE.

The _SOURCE directive lets you include files,

Syntax :
&_SOURCE{; fileName;}

fileName is the name of a file wich may contain directives and/or sets at the highest level (no attributes nor arrays at the highest level).
It behaves exactly as if the file were in place of the _SOURCE directive.
The file is search by Alma following the rules described in the Alma documentation

2.3.3 _INCLUDE.

Syntax :
&_INCLUDE{; fileName;}

It behaves as the _SOURCE directive except that the Alma-local variables are not shared by the descriptions in the two files.

Example :

Say you have a file 'afile.alma',
&someSet{
    name=&${foo}
    &_SETV{ ; foo; Yep!; }
}
you can include it in this configuration file :
&_SETV{ ; foo; bar; }
&config{
    name0=&${foo}
    &_INCLUDE{ ; afile.alma; }
    name1=&${foo}
    &_SOURCE{ ; afile.alma; }
    name2=&${foo}
}
it will give this result :
{
    "someSet"=>
    [
        {
            "name"=>""
        }
        ,
        {
            "name"=>"bar"
        }
    ]
    ,
    "name0"=>"bar",
    "name1"=>"bar",
    "name2"=>"Yep!"
}

3 API.

3.1 Methods.

An AlmaConf instance can be created by the new method and a configuration file can be loaded by the get_conf instance method, however if you only have one configuration file to load, the class method get method is a shortcut.

AlmaConf.new( options-Hash )
returns an AlmaConf object

get_conf( fileName, optionsHash )
returns a hash or nil on error.

AlmaConf.get( fileName, optionsHash )
returns a hash or nil on error.

The options hash may contains following entries :

Alma options :
See Alma documentation for details.

AlmaConf options :
These options are discussed in next chapters.

They may be specified for any method but the almalib option is silently ignored by the get_conf method (this may change).
Except almalib, options supplied to the get_conf method override those supplied to the new method but only for this call of get_conf.

3.2 Options.

3.2.1 ForceArray.

Tnis option forces values associated with a set or an attribute name to be put in an array even when there is one set or attribute with this name.
When this option is set to true, it is applied to all attributes and sets, when it is an array of name, it is applied to sets or attributes which name appears in the array.

Example :

Following configuration file :

&conf{
    &task{
        id=upload
    }
}
normally returns this result :
{
    "task"=>
    {
        "id"=>"upload"
    }
}
with the ForceArray => true option, it returns
{
    "task"=>
    [
        {
            "id"=>
            [
                "upload"
            ]
        }
    ]
}
with the ForceArray => [ 'task' ] option, it returns
{
    "task"=>
    [
        {
            "id"=>"upload"
        }
    ]
}

3.2.2 KeepRoot.

When there is only one set at the highest level, AlmaConf normally discards this root entry from the resulting hash. Setting the 'KeepRoot' option to true will cause the root element name to be retained.

Example :

Following configuration file :

&config{
    foo=bar
}
normally returns this result :
{
    "foo"=>"bar"
}
with the ForceArray => true option, it returns
{
    "config"=>
    {
        "foo"=>"bar"
    }
}

3.2.3 SuppressEmpty.

This option allows empty set, like &someSet{} to be discarded. Recursively, sets which only contains empty sets are also removed.

3.3 Error handling.

When an error occurs in a configuration file, some messages are written to $stderr, the process continue but the get or get_conf method will return nil when it ends.
Ama gives up after 11 errors.

Messages give useful informations to determine the errors.

3.3.1 almaconf-check.rb.

It may help to check configuration files before they are used in a 'real' program, The almaconf-check.rb little script just load configuration files, error messages are written to $stderr, and the resulting object, a hash or nil is print on stderr using Misc::pins (the 'pretty' inspect which prints the results in this doc).

Here is the output of a ruby almaconfig-check.rb --help run produced using opthash

Usage   : alma.rb [--almalib [arg],...] [--trace] 
		[--verbose [arg]] [--ForceArray] 
		[--ForceSet arg,...] [--KeepRoot] 
		[--SuppressEmpty] [--help] 
		[--version] 
Options :
	Alma options :
	--almalib -l [arg] ,... (default <>)
		Optional: Search directories for files to parse.
	--trace -t (default <false>)
		Optional: Thank you for debugging Alma!
	--verbose [arg] (default <0>)
		Optional: Specify verbosity level (0..4).
	AlmaConf options :
	--ForceArray --FA -A (default <false>)
		Optional: Set ForceArray option to true.
	--ForceSet --FS -F arg ,... (default <>)
		Optional: Set ForceArray to an array of sets.
	--KeepRoot --KR -K (default <false>)
		Optional: Set KeepRoot option to true.
	--SuppressEmpty --SE -S (default <false>)
		Optional: Set SuppressEmpty option to true.
	[file ...]
		configuration files to check.
	Or
	--help -h 
		Display these informations and exit successfully.
	Or
	--version -V 
		Display program version and exit successfully.

When the ForceSet option is supplied and not empty, it will be the ForceArray option passed to AlmaConf.

The script returns 0 when it didn't find any error, non-zero othewise. On success, no message is written on $stderr unless verbose is > 0.

Even gurus in any language had to write their first source in this language, it might give something like this:

&# file cr02.alma
&conf{
    $bar=` a bar is a bar
}

here are the results on $stdout :
nil
and the error messages on $stderr :
ALMA ERROR Non terminated string at end of data.
Alma backtrace :
File  'cr02.alma' : '' before line 3 col 11
File  'cr02.alma' : 'conf' before line 2 col 7
ALMA ERROR Unexpected end of data.
Alma backtrace :
File  'cr02.alma' : 'conf' before line 2 col 7
ALMA ERROR bad attribute name '$bar'.
Alma backtrace :
File  'cr02.alma' : 'conf' before line 2 col 7
cr02.alma parsed with errors

check ended with errors.

4 Further developments.

AlmaConf lacks a meta-description ability to define the use of constructs in a configuration file (something like an XML DTD), it will be added along with associated checks. These meta-descriptions will probably be written in AlmaConf as its own meta-meta-description (for meta-description checks).

5 Installation.

AlmaConf, its installation procedures and test suite are designed to work on any platform where Ruby works, but they have only been tested on a Debian GNU/Linux system.
AlmaConf had been developped with Ruby 1.8, but it may work on previous versions of Ruby.
Alma needs OptHash and Alma ( >= 0.0.93 ) to run.

Unzip the tarball with tar xzvf almaconf-version.tar.gz, it ceates the almaconf-version directory, then read the README file in that directory.

Download the latest version in the Download page

6 Bugs and deficiencies.

You can help by sending bugs reports and suggestions to almazz at wanadoo dot fr
Please, put '[Alma]' at the beginning of the subject field.

AlmaConf version is now 0.0.9x, it is a test version with no known bug which seems to be usable.

7 Legal informations.

AlmaConf is copyrighted : Copyright (C) 2006 Patrick Davalan. All rights reserved.

AlmaConf is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

AlmaConf programs are distributed in the hope that they will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

This software is OSI Certified Open Source Software.
"OSI Certified" is a certification mark of the Open Source Initiative.

keywords : Configuration files, Ruby, Alma, language, descriptive language, description language, micro language, mini language
1 Introduction.
1.1 Overview.
1.2 Quick start.
1.3 More.
2 Language.
2.1 Introduction.
2.2 Language constructs.
2.3 Directives.
3 API.
3.1 Methods.
3.2 Options.
3.3 Error handling.
4 Further developments.
5 Installation.
6 Bugs and deficiencies.
7 Legal informations.
Alma inside !