You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
360 lines
11 KiB
360 lines
11 KiB
----------------------------------------------------------------------------
|
|
-- Generic Command Line Parser (gclp)
|
|
--
|
|
-- Copyright (C) 2012, Riccardo Bernardini
|
|
--
|
|
-- This file is part of gclp.
|
|
--
|
|
-- gclp 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.
|
|
--
|
|
-- gclp 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 General Public License for more details.
|
|
--
|
|
-- You should have received a copy of the GNU General Public License
|
|
-- along with gclp. If not, see <http://www.gnu.org/licenses/>.
|
|
----------------------------------------------------------------------------
|
|
--
|
|
with Ada.Command_Line;
|
|
with Ada.Strings.Fixed;
|
|
with Ada.Text_IO;
|
|
with Ada.Containers.Ordered_Maps;
|
|
with Ada.Strings.Maps.Constants;
|
|
with Ada.Containers.Doubly_Linked_Lists;
|
|
|
|
use Ada;
|
|
use Ada.Strings;
|
|
use Ada.Strings.Fixed;
|
|
|
|
package body Generic_Line_Parser is
|
|
|
|
function To_S (X : Unbounded_String) return String
|
|
renames To_String;
|
|
|
|
function To_U (X : String) return Unbounded_String
|
|
renames To_Unbounded_String;
|
|
|
|
-- In order to handle parameter aliases (see comments in the specs)
|
|
-- we keep a table that maps parameter names to parameter "index"
|
|
package Name_To_Index_Maps is
|
|
new Ada.Containers.Ordered_Maps (Key_Type => Unbounded_String,
|
|
Element_Type => Natural);
|
|
|
|
--------------------
|
|
-- Case_Normalize --
|
|
--------------------
|
|
|
|
-- If the user required case insensitive matching, force the
|
|
-- name to lower case
|
|
procedure Case_Normalize (Name : in out Unbounded_String) is
|
|
begin
|
|
if not Case_Sensitive then
|
|
Translate (Name, Maps.Constants.Lower_Case_Map);
|
|
end if;
|
|
end Case_Normalize;
|
|
|
|
---------------------
|
|
-- Fill_Name_Table --
|
|
---------------------
|
|
|
|
-- Fill the Parameter Name -> parameter index table with the
|
|
-- parameter names
|
|
procedure Fill_Name_Table (Parameters : in Parameter_Descriptor_Array;
|
|
Name_Table : in out Name_To_Index_Maps.Map)
|
|
is
|
|
package Name_Lists is
|
|
new Ada.Containers.Doubly_Linked_Lists (Unbounded_String);
|
|
|
|
use Name_Lists;
|
|
|
|
----------------
|
|
-- Parse_Name --
|
|
----------------
|
|
|
|
function Parse_Name (Name : Unbounded_String) return Name_Lists.List
|
|
is
|
|
------------------
|
|
-- Trimmed_Name --
|
|
------------------
|
|
|
|
function Trimmed_Name (Name : String)
|
|
return Unbounded_String
|
|
is
|
|
Trimmed : Unbounded_String;
|
|
begin
|
|
Trimmed := To_U (Fixed.Trim (Name, Both));
|
|
if Unbounded.Length (Trimmed) = 0 then
|
|
raise Constraint_Error
|
|
with "Empty alternative in label '" & Name & "'";
|
|
else
|
|
return Trimmed;
|
|
end if;
|
|
end Trimmed_Name;
|
|
|
|
Result : Name_Lists.List;
|
|
Buffer : String := To_S (Name);
|
|
First : Natural;
|
|
Comma_Pos : Natural;
|
|
begin
|
|
if Fixed.Index (Buffer, "=") /= 0 then
|
|
raise Constraint_Error with "Option label '" & Buffer & "' has '='";
|
|
end if;
|
|
|
|
if Buffer(Buffer'Last) = ',' then
|
|
raise Constraint_Error
|
|
with "Option label '" & Buffer & "' ends with ','";
|
|
end if;
|
|
|
|
First := Buffer'First;
|
|
loop
|
|
pragma Assert (First <= Buffer'Last);
|
|
|
|
Comma_Pos := Fixed.Index (Buffer (First .. Buffer'Last), ",");
|
|
exit when Comma_Pos = 0;
|
|
|
|
if First = Comma_Pos then
|
|
-- First should always point to the beginning of a
|
|
-- label, therefore it cannot be Buffer(First) = ','
|
|
raise Constraint_Error
|
|
with "Wrong syntax in Option label '" & Buffer & "'";
|
|
end if;
|
|
|
|
pragma Assert (Comma_Pos > First);
|
|
|
|
Result.Append (Trimmed_Name (Buffer(First .. Comma_Pos - 1)));
|
|
|
|
First := Comma_Pos + 1;
|
|
|
|
-- It cannot be First > Buffer'Last since Buffer(Comma_Pos) = '='
|
|
-- and Buffer(Buffer'Last) /= ','
|
|
pragma Assert (First <= Buffer'Last);
|
|
end loop;
|
|
|
|
pragma Assert (First <= Buffer'Last);
|
|
|
|
Result.Append (Trimmed_Name (Buffer (First .. Buffer'Last)));
|
|
|
|
return Result;
|
|
end Parse_Name;
|
|
|
|
Option_Names : Name_Lists.List;
|
|
Position : Name_Lists.Cursor;
|
|
|
|
Name : Unbounded_String;
|
|
begin
|
|
for Idx in Parameters'Range loop
|
|
Option_Names := Parse_Name (Parameters (Idx).Name);
|
|
|
|
Position := Option_Names.First;
|
|
|
|
while Position /= No_Element loop
|
|
Name := Name_Lists.Element (Position);
|
|
Name_Lists.Next (Position);
|
|
|
|
Case_Normalize(Name);
|
|
|
|
if Name_Table.Contains (Name) then
|
|
raise Constraint_Error
|
|
with "Ambiguous label '" & To_S (Name) & "'";
|
|
end if;
|
|
|
|
Name_Table.Insert (Name, Idx);
|
|
end loop;
|
|
end loop;
|
|
end Fill_Name_Table;
|
|
|
|
|
|
----------------
|
|
-- To_Natural --
|
|
----------------
|
|
|
|
function To_Natural (X : Unbounded_String)
|
|
return Natural is
|
|
begin
|
|
if X = Null_Unbounded_String then
|
|
raise Bad_Command with "Invalid integer '" & To_S(X) & "'";
|
|
end if;
|
|
|
|
return Natural'Value (To_S (X));
|
|
end To_Natural;
|
|
|
|
--------------
|
|
-- To_Float --
|
|
--------------
|
|
|
|
function To_Float (X : Unbounded_String)
|
|
return Float is
|
|
begin
|
|
if X = Null_Unbounded_String then
|
|
raise Bad_Command with "Invalid Float '" & To_S(X) & "'";
|
|
end if;
|
|
|
|
return Float'Value (To_S (X));
|
|
end To_Float;
|
|
|
|
|
|
------------------------
|
|
-- Parse_Command_Line --
|
|
------------------------
|
|
|
|
procedure Parse_Command_Line
|
|
(Parameters : in Parameter_Descriptor_Array;
|
|
Result : out Config_Data;
|
|
Help_Line : in String := "";
|
|
Help_Output : in Ada.Text_IO.File_Type := Ada.Text_IO.Standard_Error) is
|
|
|
|
package String_Lists is
|
|
new Ada.Containers.Doubly_Linked_Lists (Unbounded_String);
|
|
|
|
---------------------
|
|
-- Split_Parameter --
|
|
---------------------
|
|
|
|
procedure Split_Parameter (Param : in String;
|
|
Name : out Unbounded_String;
|
|
Value : out Unbounded_String)
|
|
is
|
|
Idx : Natural;
|
|
begin
|
|
Idx := Index (Source => Param,
|
|
Pattern => "=");
|
|
|
|
if (Idx = 0) then
|
|
Name := To_U (Param);
|
|
Value := Null_Unbounded_String;
|
|
else
|
|
Name := To_U (Param (Param'First .. Idx - 1));
|
|
Value := To_U (Param (Idx + 1 .. Param'Last));
|
|
end if;
|
|
|
|
Case_Normalize (Name);
|
|
end Split_Parameter;
|
|
|
|
function Missing_Message (Missing : String_Lists.List)
|
|
return String
|
|
is
|
|
function Join (Item : String_Lists.List) return String is
|
|
Result : Unbounded_String;
|
|
|
|
procedure Append (Pos : String_Lists.Cursor) is
|
|
begin
|
|
if Result /= Null_Unbounded_String then
|
|
Result := Result & ", ";
|
|
end if;
|
|
|
|
Result := Result & "'" & String_Lists.Element (Pos) & "'";
|
|
end Append;
|
|
begin
|
|
Item.Iterate (Append'Access);
|
|
|
|
return To_String(Result);
|
|
end Join;
|
|
|
|
use type Ada.Containers.Count_Type;
|
|
begin
|
|
if Missing.Length = 1 then
|
|
return "Missing mandatory option " & Join (Missing);
|
|
else
|
|
return "Missing mandatory options: " & Join (Missing);
|
|
end if;
|
|
end Missing_Message;
|
|
|
|
|
|
Found : array (Parameters'Range) of Boolean := (others => False);
|
|
|
|
Name : Unbounded_String;
|
|
Value : Unbounded_String;
|
|
|
|
use Name_To_Index_Maps;
|
|
|
|
Name_Table : Name_To_Index_Maps.Map;
|
|
Position : Name_To_Index_Maps.Cursor;
|
|
Param_Idx : Natural;
|
|
begin
|
|
Fill_Name_Table (Parameters, Name_Table);
|
|
|
|
for Pos in 1 .. Command_Line.Argument_Count loop
|
|
Split_Parameter (Command_Line.Argument (Pos), Name, Value);
|
|
|
|
Position := Name_Table.Find (Name);
|
|
|
|
if Position = No_Element then
|
|
raise Bad_Command with "Option '" & To_S (Name) & "' unknown";
|
|
end if;
|
|
|
|
Param_Idx := Name_To_Index_Maps.Element (Position);
|
|
|
|
if Found (Param_Idx) and then Parameters (Param_Idx).Only_Once then
|
|
raise Bad_Command with "Option '" & To_S (Name) & "' given twice";
|
|
end if;
|
|
|
|
Found (Param_Idx) := True;
|
|
Parameters (Param_Idx).Callback (Name => Name,
|
|
Value => Value,
|
|
Result => Result);
|
|
end loop;
|
|
|
|
declare
|
|
use type Name_To_Index_Maps.Cursor;
|
|
|
|
Missing : String_Lists.List;
|
|
Param_Idx : Natural;
|
|
Position : Name_To_Index_Maps.Cursor;
|
|
|
|
Reported : array (Parameters'Range) of Boolean := (others => False);
|
|
-- Reported(Idx) is true if the parameter with index Idx has
|
|
-- already processed as missing. We need this since we loop over
|
|
-- the option names and more than option can refer to the same
|
|
-- parameter.
|
|
begin
|
|
Position := Name_Table.First;
|
|
|
|
while Position /= Name_To_Index_Maps.No_Element loop
|
|
Param_Idx := Name_To_Index_Maps.Element (Position);
|
|
|
|
-- Ada.Text_IO.Put ("checking" & To_S(Parameters (Param_Idx).Name) & "->");
|
|
-- Ada.Text_IO.Put (Boolean'Image (Found (Param_Idx)));
|
|
-- Ada.Text_IO.Put_Line (" "& Boolean'Image (Reported (Param_Idx)));
|
|
|
|
if not Found (Param_Idx) and not Reported (Param_Idx) then
|
|
Reported (Param_Idx) := True;
|
|
|
|
case Parameters (Param_Idx).If_Missing is
|
|
when Die =>
|
|
Missing.Append (Name_To_Index_Maps.Key (Position));
|
|
|
|
when Use_Default =>
|
|
Parameters (Param_Idx).Callback
|
|
(Name => Parameters (Param_Idx).Name,
|
|
Value => Parameters (Param_Idx).Default,
|
|
Result => Result);
|
|
|
|
when Ignore =>
|
|
null;
|
|
end case;
|
|
end if;
|
|
|
|
Name_To_Index_Maps.Next (Position);
|
|
end loop;
|
|
|
|
|
|
if not Missing.Is_Empty then
|
|
raise Bad_Command with Missing_Message (Missing);
|
|
end if;
|
|
end;
|
|
exception
|
|
when Bad_Command =>
|
|
if Help_Line /= "" then
|
|
Ada.Text_IO.Put_Line (File => Help_Output,
|
|
Item => Help_Line);
|
|
end if;
|
|
|
|
raise;
|
|
end Parse_Command_Line;
|
|
|
|
end Generic_Line_Parser;
|