-- Created On : Fri Apr 26 08:13:44 1996 with Ada.Text_IO; with Ada.Strings.Fixed; with Ada.Strings.Maps; with Ada.Characters.Handling; with Ada.Unchecked_Deallocation; with Ada.IO_Exceptions; package body Config is procedure Free is new Ada.Unchecked_Deallocation (String, Str_Ptr); procedure Init (Cfg : out Configuration; Config_File_Name : String; Case_Sensitive : Boolean := True; On_Type_Mismatch : Type_Mismatch_Action := Raise_Data_Error; Variable_Terminator : Character := '=' ) is begin Free (Cfg.Config_File); Cfg.Config_File := new String'(Config_File_Name); Cfg.Case_Sensitive := Case_Sensitive; Cfg.On_Type_Mismatch := On_Type_Mismatch; Cfg.Variable_Terminator := Variable_Terminator; end Init; function Init (Config_File_Name : String; Case_Sensitive : Boolean := True; On_Type_Mismatch : Type_Mismatch_Action := Raise_Data_Error; Variable_Terminator : Character := '=' ) return Configuration is Cfg : Configuration; begin Init (Cfg, Config_File_Name, Case_Sensitive, On_Type_Mismatch, Variable_Terminator); return Cfg; end Init; function Is_Number_Start (c : Character) return Boolean is begin case c is when '0' .. '9' | '+' | '-' => return True; when others => return False; end case; end Is_Number_Start; -- Internal -- procedure Get_Value (Cfg : in Configuration; Section : in String; Mark : in String; Line : out String; Value_Start : out Natural; Value_End : out Natural; Found_Line : out Natural ) is use Ada.Text_IO; use Ada.Strings.Fixed; use Ada.Strings.Maps; use Ada.Strings; use Ada.Characters.Handling; File : File_Type; Line_End : Natural := 0; Line_Count : Natural := 0; Sect_End : Natural; Comment_Ind : Natural; Equal_Ind : Natural; Found_Section_End : Natural; Found_Mark_Start : Natural; Found_Mark_End : Natural := 0; In_Found_Section : Boolean := False; Value_Start_Try : Natural; begin -- Get_Value Value_Start := Line'First; Value_End := Line'First - 1; Found_Line := 0; Open (File, In_File, Cfg.Config_File.all); Read_File : while not End_Of_File (File) loop Get_Line (File, Line, Line_End); -- error if line end > line'Last Line_Count := Line_Count + 1; if Line_End > 1 then case Line (Line'First) is when '[' => Sect_End := Index (Source => Line (Line'First .. Line_End), Pattern => "]"); -- error if ext_end = 0 Found_Section_End := Sect_End - 1; -- pragma Debug(Put_Line("Config: found_section => " & -- Line(2..Found_Section_End))); if Cfg.Case_Sensitive then In_Found_Section := Section = Line (Line'First + 1 .. Found_Section_End); else In_Found_Section := To_Lower (Section) = To_Lower (Line (Line'First + 1 .. Found_Section_End)); end if; when ';' | '#' => null; -- This is a full-line comment when others => if Section = "*" then In_Found_Section := True; end if; if In_Found_Section then Comment_Ind := Index (Source => Line (Line'First .. Line_End), Set => To_Set ("#;")); if Comment_Ind >= Line'First then Line_End := Comment_Ind - 1; end if; Equal_Ind := Index (Source => Line (Line'First .. Line_End), Pattern => (1 => Cfg.Variable_Terminator)); if Equal_Ind >= Line'First then Found_Mark_Start := Index_Non_Blank (Line (Line'First .. Equal_Ind - 1), Forward); Found_Mark_End := Index_Non_Blank (Line (Line'First .. Equal_Ind - 1), Backward); else Found_Mark_Start := Index_Non_Blank (Line (Line'First .. Line_End), Forward); Found_Mark_End := Index_Non_Blank (Line (Line'First .. Line_End), Backward); end if; -- Put_Line("Config: found_mark => [" & -- Line(Found_Mark_start..Found_Mark_End) & "] equal ind=" & Equal_Ind'Img & -- " termi=[" & Cfg.Variable_Terminator & ']'); if Found_Mark_Start > 0 and then Found_Mark_End > 0 then if (Cfg.Case_Sensitive and then (Line (Found_Mark_Start .. Found_Mark_End) = Mark)) or else (not Cfg.Case_Sensitive and then (To_Lower (Line (Found_Mark_Start .. Found_Mark_End)) = To_Lower (Mark))) then Found_Line := Line_Count; if Equal_Ind >= Line'First then Value_Start_Try := Index_Non_Blank (Line (Equal_Ind + 1 .. Line_End), Forward); if Value_Start_Try >= Line'First then Value_End := Index_Non_Blank (Line (Value_Start_Try .. Line_End), Backward); Value_Start := Value_Start_Try; end if; end if; exit Read_File; end if; end if; end if; end case; end if; end loop Read_File; Close (File); end Get_Value; Max_Line_Length : constant := 1000; function Value_Of (Cfg : Configuration; Section : String; Mark : String; Default : String := "") return String is Line : String (1 .. Max_Line_Length); Value_Start : Natural := Line'First; Value_End : Natural := Line'First - 1; Found_Line : Natural := 0; begin Get_Value (Cfg, Section, Mark, Line, Value_Start, Value_End, Found_Line); if Line (Value_Start .. Value_End) = "" then return Default; else return Line (Value_Start .. Value_End); end if; end Value_Of; procedure Type_Error (Cfg : Configuration; Val, Desc : String) is use Ada.Text_IO; Err_Msg : constant String := "'" & Val & "' is not " & Desc; begin case Cfg.On_Type_Mismatch is when Raise_Data_Error => raise Ada.Text_IO.Data_Error with "Config (.ini files): " & Err_Msg; when Print_Warning => Put_Line (Standard_Error, "Config: warning: " & Err_Msg); when Be_Quiet => null; end case; end Type_Error; function Value_Of (Cfg : Configuration; Section : String; Mark : String; Default : Integer := 0) return Integer is Line : String (1 .. Max_Line_Length); Value_Start : Natural := Line'First; Value_End : Natural := Line'First - 1; Found_Line : Natural := 0; begin Get_Value (Cfg, Section, Mark, Line, Value_Start, Value_End, Found_Line); if Found_Line = 0 then return Default; end if; declare Value_As_String : constant String := Line (Value_Start .. Value_End); begin if Value_As_String'Length > 2 and then Line (Value_Start .. Value_Start + 1) = "0x" then return Integer'Value ("16#" & Line (Value_Start + 2 .. Value_End) & "#"); elsif Value_As_String'Length > 0 and then Is_Number_Start (Line (Value_Start)) then return Integer'Value (Value_As_String); else Type_Error (Cfg, Value_As_String, "an integer number in line" & Integer'Image (Found_Line)); return Default; end if; end; exception when Constraint_Error => -- raised by 'Value on faulty argument Type_Error (Cfg, Line (Value_Start .. Value_End), "an integer number in line" & Integer'Image (Found_Line)); return Default; end Value_Of; function Value_Of (Cfg : Configuration; Section : String; Mark : String; Default : Long_Float := 0.0) return Long_Float is Line : String (1 .. Max_Line_Length); Value_Start : Natural := Line'First; Value_End : Natural := Line'First - 1; Found_Line : Natural := 0; begin Get_Value (Cfg, Section, Mark, Line, Value_Start, Value_End, Found_Line); if Found_Line = 0 then return Default; end if; declare Value_As_String : constant String := Line (Value_Start .. Value_End); Val : Long_Float; package LFIO is new Ada.Text_IO.Float_IO (Long_Float); Last : Positive; begin if Value_As_String'Length > 0 and then Is_Number_Start (Value_As_String (Value_As_String'First)) then -- Val := Long_Float'Value(Value_As_String); -- ^ an old compiler doesn't like some floats repr. through 'Value LFIO.Get (Value_As_String, Val, Last); return Val; else Type_Error (Cfg, Value_As_String, "a floating-point number in line" & Integer'Image (Found_Line)); return Default; end if; exception -- raised by 'Value or Get (respectively) on faulty argument: when Constraint_Error | Ada.IO_Exceptions.Data_Error => Type_Error (Cfg, Value_As_String, "a floating-point number in line" & Integer'Image (Found_Line)); return Default; end; end Value_Of; function Value_Of (Cfg : Configuration; Section : String; Mark : String; Default : Boolean := False) return Boolean is begin return Boolean'Value (Value_Of (Cfg, Section, Mark, Boolean'Image (Default))); end Value_Of; -- Return True if one of the following conditions is met: -- o the Mark is within the Section, but no equal sign is in that line, -- o the Mark is set to either 1, True or Yes. -- All other cases return False. function Is_Set (Cfg : Configuration; Section : String; Mark : String) return Boolean is use Ada.Characters.Handling; Line : String (1 .. Max_Line_Length); Value_Start : Natural; Value_End : Natural; Found_Line : Natural; begin Get_Value (Cfg, Section, Mark, Line, Value_Start, Value_End, Found_Line); declare Value : constant String := To_Lower (Line (Value_Start .. Value_End)); begin return Found_Line > 0 and then (Value = "" or else Value = "1" or else Value = "true" or else Value = "yes"); end; end Is_Set; function File_Name (Cfg : Configuration) return String is begin return Cfg.Config_File.all; end File_Name; -- List of strings, for memorizing a config file. type Ini_Line; type Ini_Line_Ptr is access Ini_Line; type Ini_Line is record next : Ini_Line_Ptr := null; line : Str_Ptr; end record; procedure Free is new Ada.Unchecked_Deallocation (Ini_Line, Ini_Line_Ptr); procedure Write_and_Free (Cfg : in Configuration; new_contents : in out Ini_Line_Ptr) is curr, to_free : Ini_Line_Ptr := null; use Ada.Text_IO; File : File_Type; begin Create (File, Out_File, Cfg.Config_File.all); curr := new_contents; while curr /= null loop Put_Line (File, curr.line.all); to_free := curr; curr := curr.next; Free (to_free.line); Free (to_free); end loop; Close (File); new_contents := null; end Write_and_Free; procedure Replace_Value (Cfg : Configuration; Section : String; Mark : String; New_Value : String) is Line : String (1 .. Max_Line_Length); Value_Start : Natural; Value_End : Natural; Found_Line : Natural; Equal_Ind : Natural; Line_End : Natural := 0; Line_Count : Natural := 0; use Ada.Text_IO; File : File_Type; use Ada.Strings.Fixed; -- root, curr, new_ini_line : Ini_Line_Ptr := null; begin Get_Value (Cfg, Section, Mark, Line, Value_Start, Value_End, Found_Line); if Found_Line = 0 then raise Location_Not_Found with "(Config / .ini file)"; end if; Open (File, In_File, Cfg.Config_File.all); Read_File : while not End_Of_File (File) loop Get_Line (File, Line, Line_End); Line_Count := Line_Count + 1; -- new_ini_line := new Ini_Line; if root = null then root := new_ini_line; else curr.next := new_ini_line; end if; curr := new_ini_line; -- if Line_Count = Found_Line then -- Change this line Equal_Ind := Index (Source => Line (1 .. Line_End), Pattern => (1 => Cfg.Variable_Terminator)); if Equal_Ind < 1 then -- No '=' or so yet, will change... curr.line := new String'(Line (1 .. Line_End) & Cfg.Variable_Terminator & New_Value); else curr.line := new String'(Line (1 .. Equal_Ind) & New_Value); end if; else -- any other line: just copy curr.line := new String'(Line (1 .. Line_End)); end if; end loop Read_File; Close (File); -- Now, write the new file Write_and_Free (Cfg, root); end Replace_Value; procedure Replace_Section (Cfg : Configuration; Section : String; New_Contents : String) is Line : String (1 .. Max_Line_Length); Line_End : Natural := 0; Line_Count : Natural := 0; use Ada.Text_IO; File : File_Type; -- root, curr, new_ini_line : Ini_Line_Ptr := null; -- procedure List_progress is begin new_ini_line := new Ini_Line; if root = null then root := new_ini_line; else curr.next := new_ini_line; end if; curr := new_ini_line; end List_progress; -- Matched_section, Found_section : Boolean := False; I : Natural := New_Contents'First; use Ada.Characters.Handling; begin Open (File, In_File, Cfg.Config_File.all); Read_File : while not End_Of_File (File) loop Get_Line (File, Line, Line_End); Line_Count := Line_Count + 1; if Line_End > 0 and then Line (1) = '[' then -- It is a section header. Matched_section := Line_End >= 2 + Section'Length and then ( (Cfg.Case_Sensitive and then Line (2 .. 2 + Section'Length) = Section & ']' ) or else ((not Cfg.Case_Sensitive) and then To_Lower (Line (2 .. 2 + Section'Length)) = To_Lower (Section) & ']' ) ); List_progress; curr.line := new String'(Line (1 .. Line_End)); if Matched_section then Found_section := True; for J in New_Contents'Range loop -- copy new contents if New_Contents (J) = LF then List_progress; curr.line := new String'(New_Contents (I .. J - 1)); I := J + 1; end if; if J = New_Contents'Last then List_progress; curr.line := new String'(New_Contents (I .. J)); end if; -- NB: we can have have a LF at the end, hence both "if"-s end loop; end if; elsif Matched_section then null; -- don't copy old contents else List_progress; curr.line := new String'(Line (1 .. Line_End)); end if; end loop Read_File; Close (File); -- Now, write the new file Write_and_Free (Cfg, root); if not Found_section then raise Section_Not_Found with "(Config / .ini file)"; end if; end Replace_Section; -- Disable the Mark using a semicolon prefix -- procedure Disable (Cfg : Configuration; Section : String; Mark : String) is Line : String (1 .. Max_Line_Length); Value_Start : Natural; Value_End : Natural; Found_Line : Natural; Line_End : Natural := 0; Line_Count : Natural := 0; use Ada.Text_IO; File : File_Type; -- root, curr, new_ini_line : Ini_Line_Ptr := null; begin Get_Value (Cfg, Section, Mark, Line, Value_Start, Value_End, Found_Line); if Found_Line = 0 then raise Location_Not_Found with "(Config / .ini file)"; end if; Open (File, In_File, Cfg.Config_File.all); Read_File : while not End_Of_File (File) loop Get_Line (File, Line, Line_End); Line_Count := Line_Count + 1; -- new_ini_line := new Ini_Line; if root = null then root := new_ini_line; else curr.next := new_ini_line; end if; curr := new_ini_line; -- if Line_Count = Found_Line then -- Comment out this line curr.line := new String'("; " & Line (1 .. Line_End)); else -- any other line: just copy curr.line := new String'(Line (1 .. Line_End)); end if; end loop Read_File; Close (File); -- Now, write the new file Write_and_Free (Cfg, root); end Disable; function Read_Sections (Cfg : Configuration) return Section_List is use Ada.Text_IO; use Ada.Strings.Fixed; File : File_Type; Section_Names : Section_List; begin Open (File, In_File, Cfg.Config_File.all); Read_File : while not End_Of_File (File) loop declare Cfg_Line : constant String := Get_Line (File); Sect_End : Natural; begin if Cfg_Line'Length > 1 and then Cfg_Line (Cfg_Line'First) = '[' then Sect_End := Index (Cfg_Line, "]") - 1; String_Vector.Append (Section_Names, Cfg_Line (Cfg_Line'First + 1 .. Sect_End)); end if; end; end loop Read_File; Close (File); return Section_Names; end Read_Sections; end Config;