Module Rascl.ConfigObject


module ConfigObject: sig .. end
This is a framework for the object-oriented interface to RASCL configurations. See the tutorial below for usage instructions.

class virtual config_base : Rascl.Dict.t -> Rascl.Dict.t -> string list option -> object .. end
class virtual root_config : string list -> string -> Rascl.Dict.t -> Rascl.Dict.t -> object .. end
class virtual sub_config : Rascl.Dict.t -> Rascl.Dict.t -> string list -> object .. end

Tutorial

In order to use the OO interface, you need to create an object specific to your application. This can be done by using pa_rascl.cmo, a CamlP4-based code generator. pa_rascl.cmo parses a configuration template that you create and outputs a module containing a single function which creates a configuration object.

Templates

The configuration template syntax is as follows:

    template         : config-name space* '=' space* dictionary
    config-name      : ocaml-lowercase-ident
    dictionary       : '{' (property? | property (separator property)* ) '}'
    property         : name space* ':' space* type-and-default
                     | name space* '=' space* dictionary
    name             : ocaml-method-name
    type-and-default : 'string' (space* '=' space* string-expr)?
                     | 'int' (space* '=' space* int-expr)?
                     | 'float' (space* '=' space* float-expr)?
                     | 'bool' (space* '=' space* bool-expr)?
                     | 'string list' (space* '=' space* string-list-expr)?
                     | 'int list' (space* '=' space* int-list-expr)?
                     | 'float list' (space* '=' space* float-list-expr)?
                     | 'bool list' (space* '=' space* bool-list-expr)?
    

The prefix ocaml- indicates that the item conforms to OCaml syntax, and is fully documented in the OCaml manual (with the same name, minus 'ocaml-'.

Items with the suffix -expr are documented in the RASCL language specification. In general, the syntax for these types follows that of OCaml, with the following exceptions:

Default values are optional, but should be provided whenever possible; if an item has no default, a value must be loaded from a file before attempting to access it at runtime.

Property names must be unique within any given dictionary, but may be repeated in sub-dictionaries.

For a sample template, see food.ml. You may also find it useful to compare this file with the supplied config.example, which is the RASCL configuration generated from it.

Using the code generator

pa_rascl.cmo is a CamlP4 preprocessor. It depends on pa_extend.cmo and q_MLast.cmo. It can be invoked as follows:

      camlp4o -I <rascl_path> pa_extend.cmo q_MLast.cmo pa_rascl.cmo
    
So, the command to byte-compile a template called 'prefs.ml', using ocamlfind, might look like this:
      ocamlfind ocamlc -package rascl -pp \
        'camlp4o -I `ocamlfind query rascl` pa_extend.cmo q_MLast.cmo \
        pa_rascl.cmo' -c prefs.ml
    
The above command will produce two output files: the configuration module, in this case called 'prefs.cmo', and a sample RASCL file called 'config.example', which may be used as a default runtime configuration file.

Using the generated module

The module generated by pa_rascl.cmo contains a single public function that generates a configuration object. Its signature is as follows:

    <function_name> : string list -> <object>
    
The function name is taken from the config-name specified in the template. I.e., if the template contains:
    prefs = {
      ....
    }
    
then the function name will be prefs.

The argument to this function is a list of RASCL configuration files. These files will be loaded in the order that they appear in the list, and the values they contain will populate the configuration object. Later values replace earlier ones. Thus, typically the file list will contain a system-wide configuration file, then a user-specific file. If defaults have been defined for all properties, then the file list may be empty.

RASCL does not handle any errors encountered in trying to read configuration files, so your application code must ensure that all files exist and are readable.

The function returns an immediate object (i.e. it is not an instance of any class) which inherits from ConfigObject.root_config. Thus it it provides #load and #dump as described in the root_config documentation. Its other public methods correspond to the property names in your configuration template.

Using the configuration object

For every property defined in your configuration template, the configuration object will have a 0-argument 'getter' method named <prop_name>, and a 1-argument 'setter' named set_<prop_name>. The type returned by the 'getter', and of the argument to the 'setter', will be whatever you specified in the template. Thus, if your template consists of:

    prefs = {
      font_size : int = 12
      color : string = "black"
    }
    
then you can access the configuration object in the following manner:
    prefs_obj#font_size ==> 12  
    prefs_obj#color ==> "black"
    prefs_obj#set_font_size 10 ==> ()
    prefs_obj#set_color "red" ==> ()
    

Nested dictionaries in the template (and in configuration files) correspond to nested sub-objects in the configuration object. Thus, given the following template:

    prefs = {
      font = {
        family : string = "bitstream vera sans"
        size : int = 10
        weight : string = "bold"
        slant : string = "roman"
      }
    }
    
the object will have the following interface:
    <
      load : unit -> unit
      dump : unit
      font : <
        dump : unit
        family : string
        set_family : string -> unit
        size : int
        set_size : int -> unit
        weight : string
        set_weight : string -> unit
        slant : string
        set_slant : string -> unit
      >
    >
    
Note that there is no #set_font method. Nested objects cannot be added or removed.