Table of Contents
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.