Introduction

In a program it is often necessary to be able to read values from a configuration file. This can be done in many ways, but for this article we're going to focus on how to do it using a generic.

Using a generic enables us to have several different configuration files with different default values. Each configuration file results in a distinct object, based on the parameters used when the generic is instantiated.

The Program

I've commented the program, so it should be fairly easy to understand what's going on. You can grab the entire program from this Git repository:

git://github.com/ThomasLocke/Simple_Config.git

Git is available for all Linux distros. The commands to clone Simple_Config and then compile it are:

$ git clone git://github.com/ThomasLocke/Simple_Config.git
$ cd Simple_Config
$ gnatmake -P simple_config.gpr

The executable can now be found in the exe/ folder.

If you don't feel like using Git, the full source code can be copied from below. I will of course do my best to make sure that the code on this page matches the code in the Git repository, but since this is a wiki where anybody can make changes, I cannot guarantee the safety of running the code on this page. So use with caution.

exe/config_one.ini and exe/config_two.ini

For this short example we're using two different configuration files. Both must be located in the exe/ directory. If you move them somewhere else, then remember to change the Config_File parameter(s) in src/configuration.ads.

config_one.ini

# This is a comment.
-- This is also a comment.
; And a third comment. Lines starting with #, -- and ; are treated as comments.

#  This config file only changes the Foo_Boolean parameter. For the remaining
#  parameters, the default values are used.
Foo_Boolean True

And

config_two.ini

# This is a comment.
-- This is also a comment.
; And a third comment. Lines starting with #, -- and ; are treated as comments.

#  This config file only changes the Foo_Boolean parameter. For the remaining
#  parameters, the default values are used.
Foo_Boolean True

simple_config.gpr

This file sets up compile options. It informs the compiler of the location of the source files and where to put the object and executable files. You must, of course, create the exe and build directories, if you haven't cloned the Git repository.

-------------------------------------------------------------------------------
--                                                                           --
--                            Simple_Config                                  --
--                                                                           --
--                            Project File                                   --
--                                                                           --
--                     Copyright (C) 2010, Thomas Løcke                      --
--                                                                           --
--  Simple_Config is free software;  you can  redistribute it  and/or modify --
--  it under terms of the  GNU General Public License as published  by the   --
--  Free Software  Foundation;  either version 2,  or (at your option) any   --
--  later version.  Simple_Config is distributed in the hope that it will be --
--  useful, but WITHOUT ANY WARRANTY;  without even the  implied warranty of --
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Gene-  --
--  ral Public License for  more details.  You should have  received  a copy --
--  of the GNU General Public License  distributed with Simple_Config.  If   --
--  not, write to  the  Free Software Foundation,  51  Franklin  Street,     --
--  Fifth Floor, Boston, MA 02110 - 1301, USA.                               --
--                                                                           --
-------------------------------------------------------------------------------
 
project Simple_Config is
   for Source_Dirs use (".",
                        "src");
 
   for Main use ("simple_config.adb");
 
   for Exec_Dir use "exe";
 
   for Object_Dir use "build";
 
   package Ide is
 
      for Compiler_Command ("ada") use "gnatmake";
 
   end Ide;
 
   package Compiler is
 
      Common_Options := ("-gnatwa",
                         "-gnaty3abcdefhiklmnoprstux",
                         "-Wall",
                         "-O2",
                         "-gnat05");
 
     for Default_Switches ("Ada") use Common_Options;
 
   end Compiler;
end Simple_Config;

simple_config.adb

This is the main program file.

-------------------------------------------------------------------------------
--                                                                           --
--                              Simple_Config                                --
--                                                                           --
--                     Copyright (C) 2010, Thomas Løcke                      --
--                                                                           --
--  Simple_Config is free software;  you can  redistribute it  and/or modify --
--  it under terms of the  GNU General Public License as published  by the   --
--  Free Software  Foundation;  either version 2,  or (at your option) any   --
--  later version.  Simple_Config is distributed in the hope that it will be --
--  useful, but WITHOUT ANY WARRANTY;  without even the  implied warranty of --
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Gene-  --
--  ral Public License for  more details.  You should have  received  a copy --
--  of the GNU General Public License  distributed with Simple_Config.  If   --
--  not, write to  the  Free Software Foundation,  51  Franklin  Street,     --
--  Fifth Floor, Boston, MA 02110 - 1301, USA.                               --
--                                                                           --
-------------------------------------------------------------------------------
 
with Ada.Float_Text_IO;
with Ada.Integer_Text_IO;
with Ada.Text_IO;
with Ada.Text_IO.Unbounded_IO;
with Configuration;
 
procedure Simple_Config
is
   use Ada.Text_IO;
   use Configuration;
 
   package FIO renames Ada.Float_Text_IO;
   package IIO renames Ada.Integer_Text_IO;
   package UIO renames Ada.Text_IO.Unbounded_IO;
   --  Some convenience renames.
begin
   --  Lets see what config_one settings we have.
   New_Line;
   Put_Line ("Config_One:");
   Put_Line (Config_One.Get (Key => Foo_String));
   UIO.Put_Line (Config_One.Get (Key => Foo_Unbounded_String));
   FIO.Put (Config_One.Get (Key => Foo_Float));
   New_Line;
   IIO.Put (Config_One.Get (Key => Foo_Integer));
   New_Line;
 
   if Config_One.Get (Key => Foo_Boolean) then
      Put_Line ("We're true!");
   end if;
 
   --  And now for the config_two settings
   New_Line;
   Put_Line ("Config_Two:");
   Put_Line (Config_Two.Get (Key => Foo_String));
   UIO.Put_Line (Config_Two.Get (Key => Foo_Unbounded_String));
   IIO.Put (Config_Two.Get (Key => Foo_Integer));
   New_Line;
 
   if Config_Two.Get (Key => Foo_Boolean) then
      Put_Line ("We're true!");
   end if;
 
   if not Config_Two.Has_Value (Key => Foo_Float) then
      Put_Line ("Foo_Float is empty.");
   end if;
   --  Test if a value is empty. This can be done to avoid triggering the
   --  Empty_Key exception.
 
   FIO.Put (Config_Two.Get (Key => Foo_Float));
   --  Foo_Float is empty, so the Empty_Key exception is raised because we're
   --  trying to convert an empty key to a Float.
 
exception
   when Config_Two.Empty_Key =>
      Put_Line ("Nothing to see here...." & Config_Two.Get (Key => Foo_Float));
      --  It is allowed to convert an empty key to a String/Unbounded_String.
      New_Line;
end Simple_Config;

src/config_file_parser.ads

The Config_File_Parser generic specification file. You can read a lot more about generics here.

-------------------------------------------------------------------------------
--                                                                           --
--                             Simple_Config                                 --
--                                                                           --
--                           config_file_parser                              --
--                                                                           --
--                                  SPEC                                     --
--                                                                           --
--                     Copyright (C) 2010, Thomas Løcke                      --
--                                                                           --
--  Simple_Config is free software;  you can  redistribute it  and/or modify --
--  it under terms of the  GNU General Public License as published  by the   --
--  Free Software  Foundation;  either version 2,  or (at your option) any   --
--  later version.  Simple_Config is distributed in the hope that it will be --
--  useful, but WITHOUT ANY WARRANTY;  without even the  implied warranty of --
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Gene-  --
--  ral Public License for  more details.  You should have  received  a copy --
--  of the GNU General Public License  distributed with Simple_Config.  If   --
--  not, write to  the  Free Software Foundation,  51  Franklin  Street,     --
--  Fifth Floor, Boston, MA 02110 - 1301, USA.                               --
--                                                                           --
-------------------------------------------------------------------------------
 
--  This package provides simple access to configuration files.
--  The format is:
--       KEY VALUE
--
--  Comments are prefixed with a # or a --:
--
--  # This is a comment
--  -- This is also a comment
--
--  Blank lines and comments are ignored and so is pre-/postfixed whitespace,
--  so this:
--
--  [[lots|of whitespace]]KEY[[lots|of whitespace]]VALUE[[lots|of whitespace]]
--
--  is treated as:
--
--  KEY VALUE
--
--  Values containing whitespace, eg. full sentences and similar, are returned
--  as is. It is not necessary to quote such values, so this:
--
--    KEY some value with whitespace
--
--  is perfectly valid, and will, when calling Get (KEY), return:
--
--    some value with whitespace
--
--  If VALUE is True or False (case-insensitive), then the KEY can be returned
--  as both a String or as a Boolean.
--  Conversions from VALUE to other types, such as Integer or Float, will raise
--  an exception on failure. It will NOT return some dummy value.
--
--  To clear a default value, simply add the key to the configuration file,
--  with no value set. Conversely, you must omit (or comment) keys for whích
--  you want to use the default value.
 
with Ada.Strings.Unbounded;
 
generic
   type Keys is (<>);
   --  The Keys type. Must be a discrete type.
 
   type Defaults_Array is array (Keys) of
     Ada.Strings.Unbounded.Unbounded_String;
   --  The array type used to hold the key/value pairs.
 
   Defaults : in out Defaults_Array;
   --  The actual key/value array.
 
   Config_File : in String;
   --  Path to the configuration file.
package Config_File_Parser is
   Unknown_Key             : exception;
   --  Is raised when an unknown KEY has been found in the config file.
   Cannot_Open_Config_File : exception;
   --  Is raised when the given config file cannot be opened, eg. due to bad
   --  path.
   Conversion_Error        : exception;
   --  Is raised when a value cannot be converted to a specific type.
   Empty_Key               : exception;
   --  Is raised when a key with the element Null_Unbounded_String is called.
 
   function Get (Key : in Keys) return Boolean;
   function Get (Key : in Keys) return Float;
   function Get (Key : in Keys) return Integer;
   function Get (Key : in Keys) return String;
   function Get (Key : in Keys) return Ada.Strings.Unbounded.Unbounded_String;
   --  Get the VALUE for Key and convert it to target type.
   --  Exceptions:
   --    Conversion_Error
 
   function Has_Value (Key : in Keys) return Boolean;
   --  Return True if Key is not a Null_Unbounded_String.
 
   procedure Load_File (Config_File : in String);
   --  Load the config file Config_File. This can be done over and over as many
   --  times as necessary. The values from the latest file overwrites the
   --  previous values.
   --  Exceptions:
   --    Cannot_Open_Ini_File
   --    Unknown_Key
private
   function Check_And_Convert (Key : in Keys) return String;
   --  Check if Key contains Null_Unbounded_String. If so, then raise the
   --  Empty_Key exception.
   --  This function is used when a Key must be converted to an Integer, Float
   --  or Boolean.
   --  Exceptions:
   --    Empty_Key
end Config_File_Parser;

src/config_file_parser.adb

The Config_File_Parser generic body. It's very straightforward. Do note the Check_And_Convert function that is used when a configuration value is being converted from it's inherently textual origin to a discrete value.

-------------------------------------------------------------------------------
--                                                                           --
--                              Simple_Config                                --
--                                                                           --
--                           config_file_parser                              --
--                                                                           --
--                                  BODY                                     --
--                                                                           --
--                     Copyright (C) 2010, Thomas Løcke                      --
--                                                                           --
--  Simple_Config is free software;  you can  redistribute it  and/or modify --
--  it under terms of the  GNU General Public License as published  by the   --
--  Free Software  Foundation;  either version 2,  or (at your option) any   --
--  later version.  Simple_Config is distributed in the hope that it will be --
--  useful, but WITHOUT ANY WARRANTY;  without even the  implied warranty of --
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Gene-  --
--  ral Public License for  more details.  You should have  received  a copy --
--  of the GNU General Public License  distributed with Simple_Config.  If   --
--  not, write to  the  Free Software Foundation,  51  Franklin  Street,     --
--  Fifth Floor, Boston, MA 02110 - 1301, USA.                               --
--                                                                           --
-------------------------------------------------------------------------------
 
with Ada.Strings;
with Ada.Strings.Fixed;
with Ada.Strings.Maps;
with Ada.Text_IO;
 
package body Config_File_Parser is
 
   -------------------------
   --  Check_And_Convert  --
   -------------------------
 
   function Check_And_Convert (Key : in Keys) return String
   is
      use Ada.Strings.Unbounded;
   begin
      if Defaults (Key) = Null_Unbounded_String then
         raise Empty_Key with Keys'Image (Key);
      end if;
 
      return To_String (Defaults (Key));
   end Check_And_Convert;
 
   -----------
   --  Get  --
   -----------
 
   function Get (Key : in Keys) return Boolean
   is
   begin
      return Boolean'Value (Check_And_Convert (Key));
 
   exception
      when Constraint_Error =>
         raise Conversion_Error with Keys'Image (Key);
   end Get;
 
   -----------
   --  Get  --
   -----------
 
   function Get (Key : in Keys) return Float
   is
   begin
      return Float'Value (Check_And_Convert (Key));
 
   exception
      when Constraint_Error =>
         raise Conversion_Error with Keys'Image (Key);
   end Get;
 
   -----------
   --  Get  --
   -----------
 
   function Get (Key : in Keys) return Integer
   is
   begin
      return Integer'Value (Check_And_Convert (Key));
 
   exception
      when Constraint_Error =>
         raise Conversion_Error with Keys'Image (Key);
   end Get;
 
   -----------
   --  Get  --
   -----------
 
   function Get (Key : in Keys) return String
   is
      use Ada.Strings.Unbounded;
   begin
      return To_String (Defaults (Key));
   end Get;
 
   -----------
   --  Get  --
   -----------
 
   function Get (Key : in Keys)
                 return Ada.Strings.Unbounded.Unbounded_String
   is
   begin
      return Defaults (Key);
   end Get;
 
   -----------------
   --  Has_Value  --
   -----------------
 
   function Has_Value (Key : in Keys) return Boolean
   is
      use Ada.Strings.Unbounded;
   begin
      return Defaults (Key) /= Null_Unbounded_String;
   end Has_Value;
 
   -----------------
   --  Load_File  --
   -----------------
 
   procedure Load_File (Config_File : in String)
   is
      use Ada.Strings;
      use Ada.Strings.Unbounded;
      use Ada.Text_IO;
 
      function Key_String (Line : in String) return String;
      function Value_UString (Key   : in String;
                              Line  : in String) return Unbounded_String;
 
      ------------------
      --  Key_String  --
      ------------------
 
      function Key_String (Line : in String) return String
      is
         Key_End : Natural;
      begin
         if Line /= "" then
            Key_End := Fixed.Index (Source => Line,
                                    Set    => Maps.To_Set (Space),
                                    Going  => Forward);
            if Key_End > Line'First then
               return Line (Line'First .. Key_End - 1);
            end if;
         end if;
 
         return Line;
      end Key_String;
 
      ---------------------
      --  Value_UString  --
      ---------------------
 
      function Value_UString (Key   : in String;
                              Line  : in String) return Unbounded_String
      is
      begin
         if Line /= "" and then Key /= Line then
            return To_Unbounded_String (Line (Key'Last + 2 .. Line'Last));
         end if;
 
         return Null_Unbounded_String;
      end Value_UString;
 
      File : File_Type;
   begin
      Open (File => File,
            Mode => In_File,
            Name => Config_File);
 
      while not End_Of_File (File => File) loop
         declare
            Line     : constant String := Fixed.Trim (Get_Line (File), Both);
            Key      : constant String := Key_String (Line);
            Value    : constant Unbounded_String := Value_UString (Key, Line);
         begin
            --  Ignore empty lines and comments.
            if Line /= ""
              and then Line (1 .. 1) /= "#"
              and then Line (1 .. 1) /= ";"
              and then Line (1 .. 2) /= "--" then
               --  Note that if a valid key is found in the file, and it has no
               --  value set, then the default value is overwritten with
               --  Null_Unbounded_String.
               Defaults (Keys'Value (Key)) := Trim (Value, Left);
            end if;
 
         exception
            when Constraint_Error =>
               raise Unknown_Key with
                 "Unknown configuration key '" & Key & "' in file "
                   & Config_File;
         end;
      end loop;
 
      Close (File => File);
 
   exception
      when Name_Error | Use_Error | Device_Error =>
         raise Cannot_Open_Config_File with Config_File;
   end Load_File;
begin
   Load_File (Config_File => Config_File);
   --  Whenever the Config_File_Parser generic is instantiated, it initializes
   --  itself by calling Load_File. This populates the Defaults array with the
   --  values from the Config_File configuration file.
end Config_File_Parser;

src/configuration.ads

Here we define the valid configuration key and the default values, if any. We also instantiate the generic.

-------------------------------------------------------------------------------
--                                                                           --
--                             Simple_Config                                 --
--                                                                           --
--                             configuration                                 --
--                                                                           --
--                                  SPEC                                     --
--                                                                           --
--                     Copyright (C) 2010, Thomas Løcke                      --
--                                                                           --
--  Simple_Config is free software;  you can  redistribute it  and/or modify --
--  it under terms of the  GNU General Public License as published  by the   --
--  Free Software  Foundation;  either version 2,  or (at your option) any   --
--  later version.  Simple_Config is distributed in the hope that it will be --
--  useful, but WITHOUT ANY WARRANTY;  without even the  implied warranty of --
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Gene-  --
--  ral Public License for  more details.  You should have  received  a copy --
--  of the GNU General Public License  distributed with Simple_Config.  If   --
--  not, write to  the  Free Software Foundation,  51  Franklin  Street,     --
--  Fifth Floor, Boston, MA 02110 - 1301, USA.                               --
--                                                                           --
-------------------------------------------------------------------------------
 
with Ada.Strings.Unbounded;
with Config_File_Parser;
 
package Configuration is
   function TUS (S : String) return Ada.Strings.Unbounded.Unbounded_String
                 renames Ada.Strings.Unbounded.To_Unbounded_String;
   --  Convenience rename. TUS is much shorter than To_Unbounded_String.
 
   type Keys is (Foo_Boolean,
                 Foo_String,
                 Foo_Unbounded_String,
                 Foo_Integer,
                 Foo_Float);
   --  These are the valid configuration keys.
 
   type Defaults_Array is array (Keys) of
     Ada.Strings.Unbounded.Unbounded_String;
   --  The array type we'll use to hold our default configuration settings.
 
   -------------------------------------------------
   --  Set and load the first configuration file  --
   -------------------------------------------------
   Defaults_One : Defaults_Array : ======
                    (Foo_Boolean          => TUS ("False"),
                     Foo_String           => TUS ("Thomas Løcke"),
                     Foo_Unbounded_String => TUS ("Ada rocks!"),
                     Foo_Integer          => TUS ("42"),
                     Foo_Float            => TUS ("42.0"));
   --  Default values for the Config_One object. These can be overwritten by
   --  the contents of the config_one.ini file.
 
   package Config_One is new Config_File_Parser
     (Keys => Keys,
      Defaults_Array => Defaults_Array,
      Defaults => Defaults_One,
      Config_File    => "config_one.ini");
   --  Instantiate the Config_One configuration object.
 
   --------------------------------------------------
   --  Set and load the second configuration file  --
   --------------------------------------------------   
   Defaults_Two : Defaults_Array : ======
                    (others => Ada.Strings.Unbounded.Null_Unbounded_String);
   --  For the Config_Two object we have no default values, so all values have
   --  to be read from the config_two.ini file.
 
   package Config_Two is new Config_File_Parser
     (Keys => Keys,
      Defaults_Array => Defaults_Array,
      Defaults => Defaults_Two,
      Config_File    => "config_two.ini");
   --  Instantiate the Config_Two configuration object.
end Configuration;

Running The Program

When you run the simple_config file you should get output similar to this:

$ cd exe/
$ ./simple_config

Config_One:
Thomas Løcke
Ada rocks!
 4.20000E+01
         42
We're true!

Config_Two:
Thomas Løcke
Ada rocks!
         42
We're true!
Foo_Float is empty.
Nothing to see here....

Exciting stuff eh'?

I hope you've enjoyed this short article and maybe even learned something from it. I know I enjoyed writing it.


Navigation