| ------------------------------------------------------------------------------ |
| -- -- |
| -- GNAT COMPILER COMPONENTS -- |
| -- -- |
| -- G N A T . C O M M A N D _ L I N E -- |
| -- -- |
| -- B o d y -- |
| -- -- |
| -- Copyright (C) 1999-2022, Free Software Foundation, Inc. -- |
| -- -- |
| -- GNAT is free software; you can redistribute it and/or modify it under -- |
| -- terms of the GNU General Public License as published by the Free Soft- -- |
| -- ware Foundation; either version 3, or (at your option) any later ver- -- |
| -- sion. GNAT is distributed in the hope that it will be useful, but WITH- -- |
| -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -- |
| -- or FITNESS FOR A PARTICULAR PURPOSE. -- |
| -- -- |
| -- As a special exception under Section 7 of GPL version 3, you are granted -- |
| -- additional permissions described in the GCC Runtime Library Exception, -- |
| -- version 3.1, as published by the Free Software Foundation. -- |
| -- -- |
| -- You should have received a copy of the GNU General Public License and -- |
| -- a copy of the GCC Runtime Library Exception along with this program; -- |
| -- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see -- |
| -- <http://www.gnu.org/licenses/>. -- |
| -- -- |
| -- GNAT was originally developed by the GNAT team at New York University. -- |
| -- Extensive contributions were provided by Ada Core Technologies Inc. -- |
| -- -- |
| ------------------------------------------------------------------------------ |
| |
| with Ada.Characters.Handling; use Ada.Characters.Handling; |
| with Ada.Strings.Unbounded; |
| with Ada.Text_IO; use Ada.Text_IO; |
| with Ada.Unchecked_Deallocation; |
| |
| with GNAT.Directory_Operations; use GNAT.Directory_Operations; |
| with GNAT.OS_Lib; use GNAT.OS_Lib; |
| |
| package body GNAT.Command_Line is |
| |
| -- General note: this entire body could use much more commenting. There |
| -- are large sections of uncommented code throughout, and many formal |
| -- parameters of local subprograms are not documented at all ??? |
| |
| package CL renames Ada.Command_Line; |
| |
| type Switch_Parameter_Type is |
| (Parameter_None, |
| Parameter_With_Optional_Space, -- ':' in getopt |
| Parameter_With_Space_Or_Equal, -- '=' in getopt |
| Parameter_No_Space, -- '!' in getopt |
| Parameter_Optional); -- '?' in getopt |
| |
| procedure Set_Parameter |
| (Variable : out Parameter_Type; |
| Arg_Num : Positive; |
| First : Positive; |
| Last : Natural; |
| Extra : Character := ASCII.NUL); |
| pragma Inline (Set_Parameter); |
| -- Set the parameter that will be returned by Parameter below |
| -- |
| -- Extra is a character that needs to be added when reporting Full_Switch. |
| -- (it will in general be the switch character, for instance '-'). |
| -- Otherwise, Full_Switch will report 'f' instead of '-f'. In particular, |
| -- it needs to be set when reporting an invalid switch or handling '*'. |
| -- |
| -- Parameters need to be defined ??? |
| |
| function Goto_Next_Argument_In_Section (Parser : Opt_Parser) return Boolean; |
| -- Go to the next argument on the command line. If we are at the end of |
| -- the current section, we want to make sure there is no other identical |
| -- section on the command line (there might be multiple instances of |
| -- -largs). Returns True iff there is another argument. |
| |
| function Get_File_Names_Case_Sensitive return Integer; |
| pragma Import (C, Get_File_Names_Case_Sensitive, |
| "__gnat_get_file_names_case_sensitive"); |
| |
| File_Names_Case_Sensitive : constant Boolean := |
| Get_File_Names_Case_Sensitive /= 0; |
| |
| procedure Canonical_Case_File_Name (S : in out String); |
| -- Given a file name, converts it to canonical case form. For systems where |
| -- file names are case sensitive, this procedure has no effect. If file |
| -- names are not case sensitive (i.e. for example if you have the file |
| -- "xyz.adb", you can refer to it as XYZ.adb or XyZ.AdB), then this call |
| -- converts the given string to canonical all lower case form, so that two |
| -- file names compare equal if they refer to the same file. |
| |
| procedure Internal_Initialize_Option_Scan |
| (Parser : Opt_Parser; |
| Switch_Char : Character; |
| Stop_At_First_Non_Switch : Boolean; |
| Section_Delimiters : String); |
| -- Initialize Parser, which must have been allocated already |
| |
| function Argument (Parser : Opt_Parser; Index : Integer) return String; |
| -- Return the index-th command line argument |
| |
| procedure Find_Longest_Matching_Switch |
| (Switches : String; |
| Arg : String; |
| Index_In_Switches : out Integer; |
| Switch_Length : out Integer; |
| Param : out Switch_Parameter_Type); |
| -- Return the Longest switch from Switches that at least partially matches |
| -- Arg. Index_In_Switches is set to 0 if none matches. What are other |
| -- parameters??? in particular Param is not always set??? |
| |
| procedure Unchecked_Free is new Ada.Unchecked_Deallocation |
| (Argument_List, Argument_List_Access); |
| |
| procedure Unchecked_Free is new Ada.Unchecked_Deallocation |
| (Command_Line_Configuration_Record, Command_Line_Configuration); |
| |
| procedure Remove (Line : in out Argument_List_Access; Index : Integer); |
| -- Remove a specific element from Line |
| |
| procedure Add |
| (Line : in out Argument_List_Access; |
| Str : String_Access; |
| Before : Boolean := False); |
| -- Add a new element to Line. If Before is True, the item is inserted at |
| -- the beginning, else it is appended. |
| |
| procedure Add |
| (Config : in out Command_Line_Configuration; |
| Switch : Switch_Definition); |
| procedure Add |
| (Def : in out Alias_Definitions_List; |
| Alias : Alias_Definition); |
| -- Add a new element to Def |
| |
| procedure Initialize_Switch_Def |
| (Def : out Switch_Definition; |
| Switch : String := ""; |
| Long_Switch : String := ""; |
| Help : String := ""; |
| Section : String := ""; |
| Argument : String := "ARG"); |
| -- Initialize [Def] with the contents of the other parameters. |
| -- This also checks consistency of the switch parameters, and will raise |
| -- Invalid_Switch if they do not match. |
| |
| procedure Decompose_Switch |
| (Switch : String; |
| Parameter_Type : out Switch_Parameter_Type; |
| Switch_Last : out Integer); |
| -- Given a switch definition ("name:" for instance), extracts the type of |
| -- parameter that is expected, and the name of the switch |
| |
| function Can_Have_Parameter (S : String) return Boolean; |
| -- True if S can have a parameter |
| |
| function Require_Parameter (S : String) return Boolean; |
| -- True if S requires a parameter |
| |
| function Actual_Switch (S : String) return String; |
| -- Remove any possible trailing '!', ':', '?' and '=' |
| |
| generic |
| with procedure Callback |
| (Simple_Switch : String; |
| Separator : String; |
| Parameter : String; |
| Index : Integer); -- Index in Config.Switches, or -1 |
| procedure For_Each_Simple_Switch |
| (Config : Command_Line_Configuration; |
| Section : String; |
| Switch : String; |
| Parameter : String := ""; |
| Unalias : Boolean := True); |
| -- Breaks Switch into as simple switches as possible (expanding aliases and |
| -- ungrouping common prefixes when possible), and call Callback for each of |
| -- these. |
| |
| procedure Sort_Sections |
| (Line : not null GNAT.OS_Lib.Argument_List_Access; |
| Sections : GNAT.OS_Lib.Argument_List_Access; |
| Params : GNAT.OS_Lib.Argument_List_Access); |
| -- Reorder the command line switches so that the switches belonging to a |
| -- section are grouped together. |
| |
| procedure Group_Switches |
| (Cmd : Command_Line; |
| Result : Argument_List_Access; |
| Sections : Argument_List_Access; |
| Params : Argument_List_Access); |
| -- Group switches with common prefixes whenever possible. Once they have |
| -- been grouped, we also check items for possible aliasing. |
| |
| procedure Alias_Switches |
| (Cmd : Command_Line; |
| Result : Argument_List_Access; |
| Params : Argument_List_Access); |
| -- When possible, replace one or more switches by an alias, i.e. a shorter |
| -- version. |
| |
| function Looking_At |
| (Type_Str : String; |
| Index : Natural; |
| Substring : String) return Boolean; |
| -- Return True if the characters starting at Index in Type_Str are |
| -- equivalent to Substring. |
| |
| generic |
| with function Callback (S : String; Index : Integer) return Boolean; |
| procedure Foreach_Switch |
| (Config : Command_Line_Configuration; |
| Section : String); |
| -- Iterate over all switches defined in Config, for a specific section. |
| -- Index is set to the index in Config.Switches. Stop iterating when |
| -- Callback returns False. |
| |
| -------------- |
| -- Argument -- |
| -------------- |
| |
| function Argument (Parser : Opt_Parser; Index : Integer) return String is |
| begin |
| if Parser.Arguments /= null then |
| return Parser.Arguments (Index + Parser.Arguments'First - 1).all; |
| else |
| return CL.Argument (Index); |
| end if; |
| end Argument; |
| |
| ------------------------------ |
| -- Canonical_Case_File_Name -- |
| ------------------------------ |
| |
| procedure Canonical_Case_File_Name (S : in out String) is |
| begin |
| if not File_Names_Case_Sensitive then |
| for J in S'Range loop |
| if S (J) in 'A' .. 'Z' then |
| S (J) := Character'Val |
| (Character'Pos (S (J)) + |
| (Character'Pos ('a') - Character'Pos ('A'))); |
| end if; |
| end loop; |
| end if; |
| end Canonical_Case_File_Name; |
| |
| --------------- |
| -- Expansion -- |
| --------------- |
| |
| function Expansion (Iterator : Expansion_Iterator) return String is |
| type Pointer is access all Expansion_Iterator; |
| |
| It : constant Pointer := Iterator'Unrestricted_Access; |
| S : String (1 .. 1024); |
| Last : Natural; |
| |
| Current : Depth := It.Current_Depth; |
| NL : Positive; |
| |
| begin |
| -- It is assumed that a directory is opened at the current level. |
| -- Otherwise GNAT.Directory_Operations.Directory_Error will be raised |
| -- at the first call to Read. |
| |
| loop |
| Read (It.Levels (Current).Dir, S, Last); |
| |
| -- If we have exhausted the directory, close it and go back one level |
| |
| if Last = 0 then |
| Close (It.Levels (Current).Dir); |
| |
| -- If we are at level 1, we are finished; return an empty string |
| |
| if Current = 1 then |
| return String'(1 .. 0 => ' '); |
| |
| -- Otherwise continue with the directory at the previous level |
| |
| else |
| Current := Current - 1; |
| It.Current_Depth := Current; |
| end if; |
| |
| -- If this is a directory, that is neither "." or "..", attempt to |
| -- go to the next level. |
| |
| elsif Is_Directory |
| (It.Dir_Name (1 .. It.Levels (Current).Name_Last) & |
| S (1 .. Last)) |
| and then S (1 .. Last) /= "." |
| and then S (1 .. Last) /= ".." |
| then |
| -- We can go to the next level only if we have not reached the |
| -- maximum depth, |
| |
| if Current < It.Maximum_Depth then |
| NL := It.Levels (Current).Name_Last; |
| |
| -- And if relative path of this new directory is not too long |
| |
| if NL + Last + 1 < Max_Path_Length then |
| Current := Current + 1; |
| It.Current_Depth := Current; |
| It.Dir_Name (NL + 1 .. NL + Last) := S (1 .. Last); |
| NL := NL + Last + 1; |
| It.Dir_Name (NL) := Directory_Separator; |
| It.Levels (Current).Name_Last := NL; |
| Canonical_Case_File_Name (It.Dir_Name (1 .. NL)); |
| |
| -- Open the new directory, and read from it |
| |
| GNAT.Directory_Operations.Open |
| (It.Levels (Current).Dir, It.Dir_Name (1 .. NL)); |
| end if; |
| end if; |
| end if; |
| |
| -- Check the relative path against the pattern |
| |
| -- Note that we try to match also against directory names, since |
| -- clients of this function may expect to retrieve directories. |
| |
| declare |
| Name : String := |
| It.Dir_Name (It.Start .. It.Levels (Current).Name_Last) |
| & S (1 .. Last); |
| |
| begin |
| Canonical_Case_File_Name (Name); |
| |
| -- If it matches return the relative path |
| |
| if GNAT.Regexp.Match (Name, Iterator.Regexp) then |
| return Name; |
| end if; |
| end; |
| end loop; |
| end Expansion; |
| |
| --------------------- |
| -- Current_Section -- |
| --------------------- |
| |
| function Current_Section |
| (Parser : Opt_Parser := Command_Line_Parser) return String |
| is |
| begin |
| if Parser.Current_Section = 1 then |
| return ""; |
| end if; |
| |
| for Index in reverse 1 .. Integer'Min (Parser.Current_Argument - 1, |
| Parser.Section'Last) |
| loop |
| if Parser.Section (Index) = 0 then |
| return Argument (Parser, Index); |
| end if; |
| end loop; |
| |
| return ""; |
| end Current_Section; |
| |
| ----------------- |
| -- Full_Switch -- |
| ----------------- |
| |
| function Full_Switch |
| (Parser : Opt_Parser := Command_Line_Parser) return String |
| is |
| begin |
| if Parser.The_Switch.Extra = ASCII.NUL then |
| return Argument (Parser, Parser.The_Switch.Arg_Num) |
| (Parser.The_Switch.First .. Parser.The_Switch.Last); |
| else |
| return Parser.The_Switch.Extra |
| & Argument (Parser, Parser.The_Switch.Arg_Num) |
| (Parser.The_Switch.First .. Parser.The_Switch.Last); |
| end if; |
| end Full_Switch; |
| |
| ------------------ |
| -- Get_Argument -- |
| ------------------ |
| |
| function Get_Argument |
| (Do_Expansion : Boolean := False; |
| Parser : Opt_Parser := Command_Line_Parser) return String |
| is |
| End_Of_Args : Boolean; |
| begin |
| return Get_Argument (Do_Expansion, Parser, End_Of_Args); |
| end Get_Argument; |
| |
| ------------------ |
| -- Get_Argument -- |
| ------------------ |
| |
| function Get_Argument |
| (Do_Expansion : Boolean := False; |
| Parser : Opt_Parser := Command_Line_Parser; |
| End_Of_Arguments : out Boolean) return String is |
| begin |
| End_Of_Arguments := False; |
| |
| if Parser.In_Expansion then |
| declare |
| S : constant String := Expansion (Parser.Expansion_It); |
| begin |
| if S'Length /= 0 then |
| return S; |
| else |
| Parser.In_Expansion := False; |
| end if; |
| end; |
| end if; |
| |
| if Parser.Current_Argument > Parser.Arg_Count then |
| |
| -- If this is the first time this function is called |
| |
| if Parser.Current_Index = 1 then |
| Parser.Current_Argument := 1; |
| while Parser.Current_Argument <= Parser.Arg_Count |
| and then Parser.Section (Parser.Current_Argument) /= |
| Parser.Current_Section |
| loop |
| Parser.Current_Argument := Parser.Current_Argument + 1; |
| end loop; |
| |
| else |
| End_Of_Arguments := True; |
| return String'(1 .. 0 => ' '); |
| end if; |
| |
| elsif Parser.Section (Parser.Current_Argument) = 0 then |
| while Parser.Current_Argument <= Parser.Arg_Count |
| and then Parser.Section (Parser.Current_Argument) /= |
| Parser.Current_Section |
| loop |
| Parser.Current_Argument := Parser.Current_Argument + 1; |
| end loop; |
| end if; |
| |
| Parser.Current_Index := Integer'Last; |
| |
| while Parser.Current_Argument <= Parser.Arg_Count |
| and then Parser.Is_Switch (Parser.Current_Argument) |
| loop |
| Parser.Current_Argument := Parser.Current_Argument + 1; |
| end loop; |
| |
| if Parser.Current_Argument > Parser.Arg_Count then |
| End_Of_Arguments := True; |
| return String'(1 .. 0 => ' '); |
| |
| elsif Parser.Section (Parser.Current_Argument) = 0 then |
| return Get_Argument (Do_Expansion, Parser, End_Of_Arguments); |
| end if; |
| |
| Parser.Current_Argument := Parser.Current_Argument + 1; |
| |
| -- Could it be a file name with wildcards to expand? |
| |
| if Do_Expansion then |
| declare |
| Arg : constant String := |
| Argument (Parser, Parser.Current_Argument - 1); |
| begin |
| for Index in Arg'Range loop |
| if Arg (Index) in '*' | '?' | '[' then |
| Parser.In_Expansion := True; |
| Start_Expansion (Parser.Expansion_It, Arg); |
| return Get_Argument (Do_Expansion, Parser, End_Of_Arguments); |
| end if; |
| end loop; |
| end; |
| end if; |
| |
| return Argument (Parser, Parser.Current_Argument - 1); |
| end Get_Argument; |
| |
| ---------------------- |
| -- Decompose_Switch -- |
| ---------------------- |
| |
| procedure Decompose_Switch |
| (Switch : String; |
| Parameter_Type : out Switch_Parameter_Type; |
| Switch_Last : out Integer) |
| is |
| begin |
| if Switch = "" then |
| Parameter_Type := Parameter_None; |
| Switch_Last := Switch'Last; |
| return; |
| end if; |
| |
| case Switch (Switch'Last) is |
| when ':' => |
| Parameter_Type := Parameter_With_Optional_Space; |
| Switch_Last := Switch'Last - 1; |
| |
| when '=' => |
| Parameter_Type := Parameter_With_Space_Or_Equal; |
| Switch_Last := Switch'Last - 1; |
| |
| when '!' => |
| Parameter_Type := Parameter_No_Space; |
| Switch_Last := Switch'Last - 1; |
| |
| when '?' => |
| Parameter_Type := Parameter_Optional; |
| Switch_Last := Switch'Last - 1; |
| |
| when others => |
| Parameter_Type := Parameter_None; |
| Switch_Last := Switch'Last; |
| end case; |
| end Decompose_Switch; |
| |
| ---------------------------------- |
| -- Find_Longest_Matching_Switch -- |
| ---------------------------------- |
| |
| procedure Find_Longest_Matching_Switch |
| (Switches : String; |
| Arg : String; |
| Index_In_Switches : out Integer; |
| Switch_Length : out Integer; |
| Param : out Switch_Parameter_Type) |
| is |
| Index : Natural; |
| Length : Natural := 1; |
| Last : Natural; |
| P : Switch_Parameter_Type; |
| |
| begin |
| Param := Parameter_None; |
| Index_In_Switches := 0; |
| Switch_Length := 0; |
| |
| -- Remove all leading spaces first to make sure that Index points |
| -- at the start of the first switch. |
| |
| Index := Switches'First; |
| while Index <= Switches'Last and then Switches (Index) = ' ' loop |
| Index := Index + 1; |
| end loop; |
| |
| while Index <= Switches'Last loop |
| |
| -- Search the length of the parameter at this position in Switches |
| |
| Length := Index; |
| while Length <= Switches'Last |
| and then Switches (Length) /= ' ' |
| loop |
| Length := Length + 1; |
| end loop; |
| |
| -- Length now marks the separator after the current switch. Last will |
| -- mark the last character of the name of the switch. |
| |
| if Length = Index + 1 then |
| P := Parameter_None; |
| Last := Index; |
| else |
| Decompose_Switch (Switches (Index .. Length - 1), P, Last); |
| end if; |
| |
| -- If it is the one we searched, it may be a candidate |
| |
| if Arg'First + Last - Index <= Arg'Last |
| and then Switches (Index .. Last) = |
| Arg (Arg'First .. Arg'First + Last - Index) |
| and then Last - Index + 1 > Switch_Length |
| and then |
| (P /= Parameter_With_Space_Or_Equal |
| or else Arg'Last = Arg'First + Last - Index |
| or else Arg (Arg'First + Last - Index + 1) = '=') |
| then |
| Param := P; |
| Index_In_Switches := Index; |
| Switch_Length := Last - Index + 1; |
| end if; |
| |
| -- Look for the next switch in Switches |
| |
| while Index <= Switches'Last |
| and then Switches (Index) /= ' ' |
| loop |
| Index := Index + 1; |
| end loop; |
| |
| Index := Index + 1; |
| end loop; |
| end Find_Longest_Matching_Switch; |
| |
| ------------ |
| -- Getopt -- |
| ------------ |
| |
| function Getopt |
| (Switches : String; |
| Concatenate : Boolean := True; |
| Parser : Opt_Parser := Command_Line_Parser) return Character |
| is |
| Dummy : Boolean; |
| |
| begin |
| <<Restart>> |
| |
| -- If we have finished parsing the current command line item (there |
| -- might be multiple switches in a single item), then go to the next |
| -- element. |
| |
| if Parser.Current_Argument > Parser.Arg_Count |
| or else (Parser.Current_Index > |
| Argument (Parser, Parser.Current_Argument)'Last |
| and then not Goto_Next_Argument_In_Section (Parser)) |
| then |
| return ASCII.NUL; |
| end if; |
| |
| -- By default, the switch will not have a parameter |
| |
| Parser.The_Parameter := |
| (Integer'Last, Integer'Last, Integer'Last - 1, ASCII.NUL); |
| Parser.The_Separator := ASCII.NUL; |
| |
| declare |
| Arg : constant String := |
| Argument (Parser, Parser.Current_Argument); |
| Index_Switches : Natural := 0; |
| Max_Length : Natural := 0; |
| End_Index : Natural; |
| Param : Switch_Parameter_Type; |
| begin |
| -- If we are on a new item, test if this might be a switch |
| |
| if Parser.Current_Index = Arg'First then |
| if Arg = "" or else Arg (Arg'First) /= Parser.Switch_Character then |
| |
| -- If it isn't a switch, return it immediately. We also know it |
| -- isn't the parameter to a previous switch, since that has |
| -- already been handled. |
| |
| if Switches (Switches'First) = '*' then |
| Set_Parameter |
| (Parser.The_Switch, |
| Arg_Num => Parser.Current_Argument, |
| First => Arg'First, |
| Last => Arg'Last); |
| Parser.Is_Switch (Parser.Current_Argument) := True; |
| Dummy := Goto_Next_Argument_In_Section (Parser); |
| return '*'; |
| end if; |
| |
| if Parser.Stop_At_First then |
| Parser.Current_Argument := Positive'Last; |
| return ASCII.NUL; |
| |
| elsif not Goto_Next_Argument_In_Section (Parser) then |
| return ASCII.NUL; |
| |
| else |
| -- Recurse to get the next switch on the command line |
| |
| goto Restart; |
| end if; |
| end if; |
| |
| -- We are on the first character of a new command line argument, |
| -- which starts with Switch_Character. Further analysis is needed. |
| |
| Parser.Current_Index := Parser.Current_Index + 1; |
| Parser.Is_Switch (Parser.Current_Argument) := True; |
| end if; |
| |
| Find_Longest_Matching_Switch |
| (Switches => Switches, |
| Arg => Arg (Parser.Current_Index .. Arg'Last), |
| Index_In_Switches => Index_Switches, |
| Switch_Length => Max_Length, |
| Param => Param); |
| |
| -- If switch is not accepted, it is either invalid or is returned |
| -- in the context of '*'. |
| |
| if Index_Switches = 0 then |
| |
| -- Find the current switch that we did not recognize. This is in |
| -- fact difficult because Getopt does not know explicitly about |
| -- short and long switches. Ideally, we would want the following |
| -- behavior: |
| |
| -- * for short switches, with Concatenate: |
| -- if -a is not recognized, and the command line has -daf |
| -- we should report the invalid switch as "-a". |
| |
| -- * for short switches, wihtout Concatenate: |
| -- we should report the invalid switch as "-daf". |
| |
| -- * for long switches: |
| -- if the commadn line is "--long" we should report --long |
| -- as unrecongized. |
| |
| -- Unfortunately, the fact that long switches start with a |
| -- duplicate switch character is just a convention (so we could |
| -- have a long switch "-long" for instance). We'll still rely on |
| -- this convention here to try and get as helpful an error message |
| -- as possible. |
| |
| -- Long switch case (starting with double switch character) |
| |
| if Arg (Arg'First + 1) = Parser.Switch_Character then |
| End_Index := Arg'Last; |
| |
| -- Short switch case |
| |
| else |
| End_Index := |
| (if Concatenate then Parser.Current_Index else Arg'Last); |
| end if; |
| |
| if Switches /= "" and then Switches (Switches'First) = '*' then |
| |
| -- Always prepend the switch character, so that users know |
| -- that this comes from a switch on the command line. This |
| -- is especially important when Concatenate is False, since |
| -- otherwise the current argument first character is lost. |
| |
| if Parser.Section (Parser.Current_Argument) = 0 then |
| |
| -- A section transition should not be returned to the user |
| |
| Dummy := Goto_Next_Argument_In_Section (Parser); |
| goto Restart; |
| |
| else |
| Set_Parameter |
| (Parser.The_Switch, |
| Arg_Num => Parser.Current_Argument, |
| First => Parser.Current_Index, |
| Last => Arg'Last, |
| Extra => Parser.Switch_Character); |
| Parser.Is_Switch (Parser.Current_Argument) := True; |
| Dummy := Goto_Next_Argument_In_Section (Parser); |
| return '*'; |
| end if; |
| end if; |
| |
| if Parser.Current_Index = Arg'First then |
| Set_Parameter |
| (Parser.The_Switch, |
| Arg_Num => Parser.Current_Argument, |
| First => Parser.Current_Index, |
| Last => End_Index); |
| else |
| Set_Parameter |
| (Parser.The_Switch, |
| Arg_Num => Parser.Current_Argument, |
| First => Parser.Current_Index, |
| Last => End_Index, |
| Extra => Parser.Switch_Character); |
| end if; |
| |
| Parser.Current_Index := End_Index + 1; |
| |
| raise Invalid_Switch with |
| "Unrecognized option '" & Full_Switch (Parser) & '''; |
| end if; |
| |
| End_Index := Parser.Current_Index + Max_Length - 1; |
| Set_Parameter |
| (Parser.The_Switch, |
| Arg_Num => Parser.Current_Argument, |
| First => Parser.Current_Index, |
| Last => End_Index); |
| |
| case Param is |
| when Parameter_With_Optional_Space => |
| if End_Index < Arg'Last then |
| Set_Parameter |
| (Parser.The_Parameter, |
| Arg_Num => Parser.Current_Argument, |
| First => End_Index + 1, |
| Last => Arg'Last); |
| Dummy := Goto_Next_Argument_In_Section (Parser); |
| |
| elsif Parser.Current_Argument < Parser.Arg_Count |
| and then Parser.Section (Parser.Current_Argument + 1) /= 0 |
| then |
| Parser.Current_Argument := Parser.Current_Argument + 1; |
| Parser.The_Separator := ' '; |
| Set_Parameter |
| (Parser.The_Parameter, |
| Arg_Num => Parser.Current_Argument, |
| First => Argument (Parser, Parser.Current_Argument)'First, |
| Last => Argument (Parser, Parser.Current_Argument)'Last); |
| Parser.Is_Switch (Parser.Current_Argument) := True; |
| Dummy := Goto_Next_Argument_In_Section (Parser); |
| |
| else |
| Parser.Current_Index := End_Index + 1; |
| raise Invalid_Parameter; |
| end if; |
| |
| when Parameter_With_Space_Or_Equal => |
| |
| -- If the switch is of the form <switch>=xxx |
| |
| if End_Index < Arg'Last then |
| if Arg (End_Index + 1) = '=' |
| and then End_Index + 1 < Arg'Last |
| then |
| Parser.The_Separator := '='; |
| Set_Parameter |
| (Parser.The_Parameter, |
| Arg_Num => Parser.Current_Argument, |
| First => End_Index + 2, |
| Last => Arg'Last); |
| Dummy := Goto_Next_Argument_In_Section (Parser); |
| |
| else |
| Parser.Current_Index := End_Index + 1; |
| raise Invalid_Parameter; |
| end if; |
| |
| -- Case of switch of the form <switch> xxx |
| |
| elsif Parser.Current_Argument < Parser.Arg_Count |
| and then Parser.Section (Parser.Current_Argument + 1) /= 0 |
| then |
| Parser.Current_Argument := Parser.Current_Argument + 1; |
| Parser.The_Separator := ' '; |
| Set_Parameter |
| (Parser.The_Parameter, |
| Arg_Num => Parser.Current_Argument, |
| First => Argument (Parser, Parser.Current_Argument)'First, |
| Last => Argument (Parser, Parser.Current_Argument)'Last); |
| Parser.Is_Switch (Parser.Current_Argument) := True; |
| Dummy := Goto_Next_Argument_In_Section (Parser); |
| |
| else |
| Parser.Current_Index := End_Index + 1; |
| raise Invalid_Parameter; |
| end if; |
| |
| when Parameter_No_Space => |
| if End_Index < Arg'Last then |
| Set_Parameter |
| (Parser.The_Parameter, |
| Arg_Num => Parser.Current_Argument, |
| First => End_Index + 1, |
| Last => Arg'Last); |
| Dummy := Goto_Next_Argument_In_Section (Parser); |
| |
| else |
| Parser.Current_Index := End_Index + 1; |
| raise Invalid_Parameter; |
| end if; |
| |
| when Parameter_Optional => |
| if End_Index < Arg'Last then |
| Set_Parameter |
| (Parser.The_Parameter, |
| Arg_Num => Parser.Current_Argument, |
| First => End_Index + 1, |
| Last => Arg'Last); |
| end if; |
| |
| Dummy := Goto_Next_Argument_In_Section (Parser); |
| |
| when Parameter_None => |
| if Concatenate or else End_Index = Arg'Last then |
| Parser.Current_Index := End_Index + 1; |
| |
| else |
| -- If Concatenate is False and the full argument is not |
| -- recognized as a switch, this is an invalid switch. |
| |
| if Switches (Switches'First) = '*' then |
| Set_Parameter |
| (Parser.The_Switch, |
| Arg_Num => Parser.Current_Argument, |
| First => Arg'First, |
| Last => Arg'Last); |
| Parser.Is_Switch (Parser.Current_Argument) := True; |
| Dummy := Goto_Next_Argument_In_Section (Parser); |
| return '*'; |
| end if; |
| |
| Set_Parameter |
| (Parser.The_Switch, |
| Arg_Num => Parser.Current_Argument, |
| First => Parser.Current_Index, |
| Last => Arg'Last, |
| Extra => Parser.Switch_Character); |
| Parser.Current_Index := Arg'Last + 1; |
| raise Invalid_Switch with |
| "Unrecognized option '" & Full_Switch (Parser) & '''; |
| end if; |
| end case; |
| |
| return Switches (Index_Switches); |
| end; |
| end Getopt; |
| |
| ----------------------------------- |
| -- Goto_Next_Argument_In_Section -- |
| ----------------------------------- |
| |
| function Goto_Next_Argument_In_Section |
| (Parser : Opt_Parser) return Boolean |
| is |
| begin |
| Parser.Current_Argument := Parser.Current_Argument + 1; |
| |
| if Parser.Current_Argument > Parser.Arg_Count |
| or else Parser.Section (Parser.Current_Argument) = 0 |
| then |
| loop |
| Parser.Current_Argument := Parser.Current_Argument + 1; |
| |
| if Parser.Current_Argument > Parser.Arg_Count then |
| Parser.Current_Index := 1; |
| return False; |
| end if; |
| |
| exit when Parser.Section (Parser.Current_Argument) = |
| Parser.Current_Section; |
| end loop; |
| end if; |
| |
| Parser.Current_Index := |
| Argument (Parser, Parser.Current_Argument)'First; |
| |
| return True; |
| end Goto_Next_Argument_In_Section; |
| |
| ------------------ |
| -- Goto_Section -- |
| ------------------ |
| |
| procedure Goto_Section |
| (Name : String := ""; |
| Parser : Opt_Parser := Command_Line_Parser) |
| is |
| Index : Integer; |
| |
| begin |
| Parser.In_Expansion := False; |
| |
| if Name = "" then |
| Parser.Current_Argument := 1; |
| Parser.Current_Index := 1; |
| Parser.Current_Section := 1; |
| return; |
| end if; |
| |
| Index := 1; |
| while Index <= Parser.Arg_Count loop |
| if Parser.Section (Index) = 0 |
| and then Argument (Parser, Index) = Parser.Switch_Character & Name |
| then |
| Parser.Current_Argument := Index + 1; |
| Parser.Current_Index := 1; |
| |
| if Parser.Current_Argument <= Parser.Arg_Count then |
| Parser.Current_Section := |
| Parser.Section (Parser.Current_Argument); |
| end if; |
| |
| -- Exit from loop if we have the start of another section |
| |
| if Index = Parser.Section'Last |
| or else Parser.Section (Index + 1) /= 0 |
| then |
| return; |
| end if; |
| end if; |
| |
| Index := Index + 1; |
| end loop; |
| |
| Parser.Current_Argument := Positive'Last; |
| Parser.Current_Index := 2; -- so that Get_Argument returns nothing |
| end Goto_Section; |
| |
| ---------------------------- |
| -- Initialize_Option_Scan -- |
| ---------------------------- |
| |
| procedure Initialize_Option_Scan |
| (Switch_Char : Character := '-'; |
| Stop_At_First_Non_Switch : Boolean := False; |
| Section_Delimiters : String := "") |
| is |
| begin |
| Internal_Initialize_Option_Scan |
| (Parser => Command_Line_Parser, |
| Switch_Char => Switch_Char, |
| Stop_At_First_Non_Switch => Stop_At_First_Non_Switch, |
| Section_Delimiters => Section_Delimiters); |
| end Initialize_Option_Scan; |
| |
| ---------------------------- |
| -- Initialize_Option_Scan -- |
| ---------------------------- |
| |
| procedure Initialize_Option_Scan |
| (Parser : out Opt_Parser; |
| Command_Line : GNAT.OS_Lib.Argument_List_Access; |
| Switch_Char : Character := '-'; |
| Stop_At_First_Non_Switch : Boolean := False; |
| Section_Delimiters : String := "") |
| is |
| begin |
| Free (Parser); |
| |
| if Command_Line = null then |
| Parser := new Opt_Parser_Data (CL.Argument_Count); |
| Internal_Initialize_Option_Scan |
| (Parser => Parser, |
| Switch_Char => Switch_Char, |
| Stop_At_First_Non_Switch => Stop_At_First_Non_Switch, |
| Section_Delimiters => Section_Delimiters); |
| else |
| Parser := new Opt_Parser_Data (Command_Line'Length); |
| Parser.Arguments := Command_Line; |
| Internal_Initialize_Option_Scan |
| (Parser => Parser, |
| Switch_Char => Switch_Char, |
| Stop_At_First_Non_Switch => Stop_At_First_Non_Switch, |
| Section_Delimiters => Section_Delimiters); |
| end if; |
| end Initialize_Option_Scan; |
| |
| ------------------------------------- |
| -- Internal_Initialize_Option_Scan -- |
| ------------------------------------- |
| |
| procedure Internal_Initialize_Option_Scan |
| (Parser : Opt_Parser; |
| Switch_Char : Character; |
| Stop_At_First_Non_Switch : Boolean; |
| Section_Delimiters : String) |
| is |
| Section_Num : Section_Number; |
| Section_Index : Integer; |
| Last : Integer; |
| Delimiter_Found : Boolean; |
| |
| Discard : Boolean; |
| pragma Warnings (Off, Discard); |
| |
| begin |
| Parser.Current_Argument := 0; |
| Parser.Current_Index := 0; |
| Parser.In_Expansion := False; |
| Parser.Switch_Character := Switch_Char; |
| Parser.Stop_At_First := Stop_At_First_Non_Switch; |
| Parser.Section := [others => 1]; |
| |
| -- If we are using sections, we have to preprocess the command line to |
| -- delimit them. A section can be repeated, so we just give each item |
| -- on the command line a section number |
| |
| Section_Num := 1; |
| Section_Index := Section_Delimiters'First; |
| while Section_Index <= Section_Delimiters'Last loop |
| Last := Section_Index; |
| while Last <= Section_Delimiters'Last |
| and then Section_Delimiters (Last) /= ' ' |
| loop |
| Last := Last + 1; |
| end loop; |
| |
| Delimiter_Found := False; |
| Section_Num := Section_Num + 1; |
| |
| for Index in 1 .. Parser.Arg_Count loop |
| pragma Assert (Argument (Parser, Index)'First = 1); |
| if Argument (Parser, Index) /= "" |
| and then Argument (Parser, Index)(1) = Parser.Switch_Character |
| and then |
| Argument (Parser, Index) = Parser.Switch_Character & |
| Section_Delimiters |
| (Section_Index .. Last - 1) |
| then |
| Parser.Section (Index) := 0; |
| Delimiter_Found := True; |
| |
| elsif Parser.Section (Index) = 0 then |
| |
| -- A previous section delimiter |
| |
| Delimiter_Found := False; |
| |
| elsif Delimiter_Found then |
| Parser.Section (Index) := Section_Num; |
| end if; |
| end loop; |
| |
| Section_Index := Last + 1; |
| while Section_Index <= Section_Delimiters'Last |
| and then Section_Delimiters (Section_Index) = ' ' |
| loop |
| Section_Index := Section_Index + 1; |
| end loop; |
| end loop; |
| |
| Discard := Goto_Next_Argument_In_Section (Parser); |
| end Internal_Initialize_Option_Scan; |
| |
| --------------- |
| -- Parameter -- |
| --------------- |
| |
| function Parameter |
| (Parser : Opt_Parser := Command_Line_Parser) return String |
| is |
| begin |
| if Parser.The_Parameter.First > Parser.The_Parameter.Last then |
| return String'(1 .. 0 => ' '); |
| else |
| return Argument (Parser, Parser.The_Parameter.Arg_Num) |
| (Parser.The_Parameter.First .. Parser.The_Parameter.Last); |
| end if; |
| end Parameter; |
| |
| --------------- |
| -- Separator -- |
| --------------- |
| |
| function Separator |
| (Parser : Opt_Parser := Command_Line_Parser) return Character |
| is |
| begin |
| return Parser.The_Separator; |
| end Separator; |
| |
| ------------------- |
| -- Set_Parameter -- |
| ------------------- |
| |
| procedure Set_Parameter |
| (Variable : out Parameter_Type; |
| Arg_Num : Positive; |
| First : Positive; |
| Last : Natural; |
| Extra : Character := ASCII.NUL) |
| is |
| begin |
| Variable.Arg_Num := Arg_Num; |
| Variable.First := First; |
| Variable.Last := Last; |
| Variable.Extra := Extra; |
| end Set_Parameter; |
| |
| --------------------- |
| -- Start_Expansion -- |
| --------------------- |
| |
| procedure Start_Expansion |
| (Iterator : out Expansion_Iterator; |
| Pattern : String; |
| Directory : String := ""; |
| Basic_Regexp : Boolean := True) |
| is |
| Directory_Separator : Character; |
| pragma Import (C, Directory_Separator, "__gnat_dir_separator"); |
| |
| First : Positive := Pattern'First; |
| Pat : String := Pattern; |
| |
| begin |
| Canonical_Case_File_Name (Pat); |
| Iterator.Current_Depth := 1; |
| |
| -- If Directory is unspecified, use the current directory ("./" or ".\") |
| |
| if Directory = "" then |
| Iterator.Dir_Name (1 .. 2) := "." & Directory_Separator; |
| Iterator.Start := 3; |
| |
| else |
| Iterator.Dir_Name (1 .. Directory'Length) := Directory; |
| Iterator.Start := Directory'Length + 1; |
| Canonical_Case_File_Name (Iterator.Dir_Name (1 .. Directory'Length)); |
| |
| -- Make sure that the last character is a directory separator |
| |
| if Directory (Directory'Last) /= Directory_Separator then |
| Iterator.Dir_Name (Iterator.Start) := Directory_Separator; |
| Iterator.Start := Iterator.Start + 1; |
| end if; |
| end if; |
| |
| Iterator.Levels (1).Name_Last := Iterator.Start - 1; |
| |
| -- Open the initial Directory, at depth 1 |
| |
| GNAT.Directory_Operations.Open |
| (Iterator.Levels (1).Dir, Iterator.Dir_Name (1 .. Iterator.Start - 1)); |
| |
| -- If in the current directory and the pattern starts with "./" or ".\", |
| -- drop the "./" or ".\" from the pattern. |
| |
| if Directory = "" and then Pat'Length > 2 |
| and then Pat (Pat'First) = '.' |
| and then Pat (Pat'First + 1) = Directory_Separator |
| then |
| First := Pat'First + 2; |
| end if; |
| |
| Iterator.Regexp := |
| GNAT.Regexp.Compile (Pat (First .. Pat'Last), Basic_Regexp, True); |
| |
| Iterator.Maximum_Depth := 1; |
| |
| -- Maximum_Depth is equal to 1 plus the number of directory separators |
| -- in the pattern. |
| |
| for Index in First .. Pat'Last loop |
| if Pat (Index) = Directory_Separator then |
| Iterator.Maximum_Depth := Iterator.Maximum_Depth + 1; |
| exit when Iterator.Maximum_Depth = Max_Depth; |
| end if; |
| end loop; |
| end Start_Expansion; |
| |
| ---------- |
| -- Free -- |
| ---------- |
| |
| procedure Free (Parser : in out Opt_Parser) is |
| procedure Unchecked_Free is new |
| Ada.Unchecked_Deallocation (Opt_Parser_Data, Opt_Parser); |
| begin |
| if Parser /= null and then Parser /= Command_Line_Parser then |
| Free (Parser.Arguments); |
| Unchecked_Free (Parser); |
| end if; |
| end Free; |
| |
| ------------------ |
| -- Define_Alias -- |
| ------------------ |
| |
| procedure Define_Alias |
| (Config : in out Command_Line_Configuration; |
| Switch : String; |
| Expanded : String; |
| Section : String := "") |
| is |
| Def : Alias_Definition; |
| |
| begin |
| if Config = null then |
| Config := new Command_Line_Configuration_Record; |
| end if; |
| |
| Def.Alias := new String'(Switch); |
| Def.Expansion := new String'(Expanded); |
| Def.Section := new String'(Section); |
| Add (Config.Aliases, Def); |
| end Define_Alias; |
| |
| ------------------- |
| -- Define_Prefix -- |
| ------------------- |
| |
| procedure Define_Prefix |
| (Config : in out Command_Line_Configuration; |
| Prefix : String) |
| is |
| begin |
| if Config = null then |
| Config := new Command_Line_Configuration_Record; |
| end if; |
| |
| Add (Config.Prefixes, new String'(Prefix)); |
| end Define_Prefix; |
| |
| --------- |
| -- Add -- |
| --------- |
| |
| procedure Add |
| (Config : in out Command_Line_Configuration; |
| Switch : Switch_Definition) |
| is |
| procedure Unchecked_Free is new Ada.Unchecked_Deallocation |
| (Switch_Definitions, Switch_Definitions_List); |
| |
| Tmp : Switch_Definitions_List; |
| |
| begin |
| if Config = null then |
| Config := new Command_Line_Configuration_Record; |
| end if; |
| |
| Tmp := Config.Switches; |
| |
| if Tmp = null then |
| Config.Switches := new Switch_Definitions (1 .. 1); |
| else |
| Config.Switches := new Switch_Definitions (1 .. Tmp'Length + 1); |
| Config.Switches (1 .. Tmp'Length) := Tmp.all; |
| Unchecked_Free (Tmp); |
| end if; |
| |
| if Switch.Switch /= null and then Switch.Switch.all = "*" then |
| Config.Star_Switch := True; |
| end if; |
| |
| Config.Switches (Config.Switches'Last) := Switch; |
| end Add; |
| |
| --------- |
| -- Add -- |
| --------- |
| |
| procedure Add |
| (Def : in out Alias_Definitions_List; |
| Alias : Alias_Definition) |
| is |
| procedure Unchecked_Free is new |
| Ada.Unchecked_Deallocation |
| (Alias_Definitions, Alias_Definitions_List); |
| |
| Tmp : Alias_Definitions_List := Def; |
| |
| begin |
| if Tmp = null then |
| Def := new Alias_Definitions (1 .. 1); |
| else |
| Def := new Alias_Definitions (1 .. Tmp'Length + 1); |
| Def (1 .. Tmp'Length) := Tmp.all; |
| Unchecked_Free (Tmp); |
| end if; |
| |
| Def (Def'Last) := Alias; |
| end Add; |
| |
| --------------------------- |
| -- Initialize_Switch_Def -- |
| --------------------------- |
| |
| procedure Initialize_Switch_Def |
| (Def : out Switch_Definition; |
| Switch : String := ""; |
| Long_Switch : String := ""; |
| Help : String := ""; |
| Section : String := ""; |
| Argument : String := "ARG") |
| is |
| P1, P2 : Switch_Parameter_Type := Parameter_None; |
| Last1, Last2 : Integer; |
| |
| begin |
| if Switch /= "" then |
| Def.Switch := new String'(Switch); |
| Decompose_Switch (Switch, P1, Last1); |
| end if; |
| |
| if Long_Switch /= "" then |
| Def.Long_Switch := new String'(Long_Switch); |
| Decompose_Switch (Long_Switch, P2, Last2); |
| end if; |
| |
| if Switch /= "" and then Long_Switch /= "" then |
| if (P1 = Parameter_None and then P2 /= P1) |
| or else (P2 = Parameter_None and then P1 /= P2) |
| or else (P1 = Parameter_Optional and then P2 /= P1) |
| or else (P2 = Parameter_Optional and then P2 /= P1) |
| then |
| raise Invalid_Switch |
| with "Inconsistent parameter types for " |
| & Switch & " and " & Long_Switch; |
| end if; |
| end if; |
| |
| if Section /= "" then |
| Def.Section := new String'(Section); |
| end if; |
| |
| if Argument /= "ARG" then |
| Def.Argument := new String'(Argument); |
| end if; |
| |
| if Help /= "" then |
| Def.Help := new String'(Help); |
| end if; |
| end Initialize_Switch_Def; |
| |
| ------------------- |
| -- Define_Switch -- |
| ------------------- |
| |
| procedure Define_Switch |
| (Config : in out Command_Line_Configuration; |
| Switch : String := ""; |
| Long_Switch : String := ""; |
| Help : String := ""; |
| Section : String := ""; |
| Argument : String := "ARG") |
| is |
| Def : Switch_Definition; |
| begin |
| if Switch /= "" or else Long_Switch /= "" then |
| Initialize_Switch_Def |
| (Def, Switch, Long_Switch, Help, Section, Argument); |
| Add (Config, Def); |
| end if; |
| end Define_Switch; |
| |
| ------------------- |
| -- Define_Switch -- |
| ------------------- |
| |
| procedure Define_Switch |
| (Config : in out Command_Line_Configuration; |
| Output : access Boolean; |
| Switch : String := ""; |
| Long_Switch : String := ""; |
| Help : String := ""; |
| Section : String := ""; |
| Value : Boolean := True) |
| is |
| Def : Switch_Definition (Switch_Boolean); |
| begin |
| if Switch /= "" or else Long_Switch /= "" then |
| Initialize_Switch_Def (Def, Switch, Long_Switch, Help, Section); |
| Def.Boolean_Output := Output.all'Unchecked_Access; |
| Def.Boolean_Value := Value; |
| Add (Config, Def); |
| end if; |
| end Define_Switch; |
| |
| ------------------- |
| -- Define_Switch -- |
| ------------------- |
| |
| procedure Define_Switch |
| (Config : in out Command_Line_Configuration; |
| Output : access Integer; |
| Switch : String := ""; |
| Long_Switch : String := ""; |
| Help : String := ""; |
| Section : String := ""; |
| Initial : Integer := 0; |
| Default : Integer := 1; |
| Argument : String := "ARG") |
| is |
| Def : Switch_Definition (Switch_Integer); |
| begin |
| if Switch /= "" or else Long_Switch /= "" then |
| Initialize_Switch_Def |
| (Def, Switch, Long_Switch, Help, Section, Argument); |
| Def.Integer_Output := Output.all'Unchecked_Access; |
| Def.Integer_Default := Default; |
| Def.Integer_Initial := Initial; |
| Add (Config, Def); |
| end if; |
| end Define_Switch; |
| |
| ------------------- |
| -- Define_Switch -- |
| ------------------- |
| |
| procedure Define_Switch |
| (Config : in out Command_Line_Configuration; |
| Output : access GNAT.Strings.String_Access; |
| Switch : String := ""; |
| Long_Switch : String := ""; |
| Help : String := ""; |
| Section : String := ""; |
| Argument : String := "ARG") |
| is |
| Def : Switch_Definition (Switch_String); |
| begin |
| if Switch /= "" or else Long_Switch /= "" then |
| Initialize_Switch_Def |
| (Def, Switch, Long_Switch, Help, Section, Argument); |
| Def.String_Output := Output.all'Unchecked_Access; |
| Add (Config, Def); |
| end if; |
| end Define_Switch; |
| |
| ------------------- |
| -- Define_Switch -- |
| ------------------- |
| |
| procedure Define_Switch |
| (Config : in out Command_Line_Configuration; |
| Callback : not null Value_Callback; |
| Switch : String := ""; |
| Long_Switch : String := ""; |
| Help : String := ""; |
| Section : String := ""; |
| Argument : String := "ARG") |
| is |
| Def : Switch_Definition (Switch_Callback); |
| begin |
| if Switch /= "" or else Long_Switch /= "" then |
| Initialize_Switch_Def |
| (Def, Switch, Long_Switch, Help, Section, Argument); |
| Def.Callback := Callback; |
| Add (Config, Def); |
| end if; |
| end Define_Switch; |
| |
| -------------------- |
| -- Define_Section -- |
| -------------------- |
| |
| procedure Define_Section |
| (Config : in out Command_Line_Configuration; |
| Section : String) |
| is |
| begin |
| if Config = null then |
| Config := new Command_Line_Configuration_Record; |
| end if; |
| |
| Add (Config.Sections, new String'(Section)); |
| end Define_Section; |
| |
| -------------------- |
| -- Foreach_Switch -- |
| -------------------- |
| |
| procedure Foreach_Switch |
| (Config : Command_Line_Configuration; |
| Section : String) |
| is |
| begin |
| if Config /= null and then Config.Switches /= null then |
| for J in Config.Switches'Range loop |
| if (Section = "" and then Config.Switches (J).Section = null) |
| or else |
| (Config.Switches (J).Section /= null |
| and then Config.Switches (J).Section.all = Section) |
| then |
| exit when Config.Switches (J).Switch /= null |
| and then not Callback (Config.Switches (J).Switch.all, J); |
| |
| exit when Config.Switches (J).Long_Switch /= null |
| and then |
| not Callback (Config.Switches (J).Long_Switch.all, J); |
| end if; |
| end loop; |
| end if; |
| end Foreach_Switch; |
| |
| ------------------ |
| -- Get_Switches -- |
| ------------------ |
| |
| function Get_Switches |
| (Config : Command_Line_Configuration; |
| Switch_Char : Character := '-'; |
| Section : String := "") return String |
| is |
| Ret : Ada.Strings.Unbounded.Unbounded_String; |
| use Ada.Strings.Unbounded; |
| |
| function Add_Switch (S : String; Index : Integer) return Boolean; |
| -- Add a switch to Ret |
| |
| ---------------- |
| -- Add_Switch -- |
| ---------------- |
| |
| function Add_Switch (S : String; Index : Integer) return Boolean is |
| pragma Unreferenced (Index); |
| begin |
| if S = "*" then |
| Ret := "*" & Ret; -- Always first |
| elsif S (S'First) = Switch_Char then |
| Append (Ret, " " & S (S'First + 1 .. S'Last)); |
| else |
| Append (Ret, " " & S); |
| end if; |
| |
| return True; |
| end Add_Switch; |
| |
| Tmp : Boolean; |
| pragma Unreferenced (Tmp); |
| |
| procedure Foreach is new Foreach_Switch (Add_Switch); |
| |
| -- Start of processing for Get_Switches |
| |
| begin |
| if Config = null then |
| return ""; |
| end if; |
| |
| Foreach (Config, Section => Section); |
| |
| -- Add relevant aliases |
| |
| if Config.Aliases /= null then |
| for A in Config.Aliases'Range loop |
| if Config.Aliases (A).Section.all = Section then |
| Tmp := Add_Switch (Config.Aliases (A).Alias.all, -1); |
| end if; |
| end loop; |
| end if; |
| |
| return To_String (Ret); |
| end Get_Switches; |
| |
| ------------------------ |
| -- Section_Delimiters -- |
| ------------------------ |
| |
| function Section_Delimiters |
| (Config : Command_Line_Configuration) return String |
| is |
| use Ada.Strings.Unbounded; |
| Result : Unbounded_String; |
| |
| begin |
| if Config /= null and then Config.Sections /= null then |
| for S in Config.Sections'Range loop |
| Append (Result, " " & Config.Sections (S).all); |
| end loop; |
| end if; |
| |
| return To_String (Result); |
| end Section_Delimiters; |
| |
| ----------------------- |
| -- Set_Configuration -- |
| ----------------------- |
| |
| procedure Set_Configuration |
| (Cmd : in out Command_Line; |
| Config : Command_Line_Configuration) |
| is |
| begin |
| Cmd.Config := Config; |
| end Set_Configuration; |
| |
| ----------------------- |
| -- Get_Configuration -- |
| ----------------------- |
| |
| function Get_Configuration |
| (Cmd : Command_Line) return Command_Line_Configuration |
| is |
| begin |
| return Cmd.Config; |
| end Get_Configuration; |
| |
| ---------------------- |
| -- Set_Command_Line -- |
| ---------------------- |
| |
| procedure Set_Command_Line |
| (Cmd : in out Command_Line; |
| Switches : String; |
| Getopt_Description : String := ""; |
| Switch_Char : Character := '-') |
| is |
| Tmp : Argument_List_Access; |
| Parser : Opt_Parser; |
| S : Character; |
| Section : String_Access := null; |
| |
| function Real_Full_Switch |
| (S : Character; |
| Parser : Opt_Parser) return String; |
| -- Ensure that the returned switch value contains the Switch_Char prefix |
| -- if needed. |
| |
| ---------------------- |
| -- Real_Full_Switch -- |
| ---------------------- |
| |
| function Real_Full_Switch |
| (S : Character; |
| Parser : Opt_Parser) return String |
| is |
| begin |
| if S = '*' then |
| return Full_Switch (Parser); |
| else |
| return Switch_Char & Full_Switch (Parser); |
| end if; |
| end Real_Full_Switch; |
| |
| -- Start of processing for Set_Command_Line |
| |
| begin |
| Free (Cmd.Expanded); |
| Free (Cmd.Params); |
| |
| if Switches /= "" then |
| Tmp := Argument_String_To_List (Switches); |
| Initialize_Option_Scan (Parser, Tmp, Switch_Char); |
| |
| loop |
| begin |
| if Cmd.Config /= null then |
| |
| -- Do not use Getopt_Description in this case. Otherwise, |
| -- if we have defined a prefix -gnaty, and two switches |
| -- -gnatya and -gnatyL!, we would have a different behavior |
| -- depending on the order of switches: |
| |
| -- -gnatyL1a => -gnatyL with argument "1a" |
| -- -gnatyaL1 => -gnatya and -gnatyL with argument "1" |
| |
| -- This is because the call to Getopt below knows nothing |
| -- about prefixes, and in the first case finds a valid |
| -- switch with arguments, so returns it without analyzing |
| -- the argument. In the second case, the switch matches "*", |
| -- and is then decomposed below. |
| |
| -- Note: When a Command_Line object is associated with a |
| -- Command_Line_Config (which is mostly the case for tools |
| -- that let users choose the command line before spawning |
| -- other tools, for instance IDEs), the configuration of |
| -- the switches must be taken from the Command_Line_Config. |
| |
| S := Getopt (Switches => "* " & Get_Switches (Cmd.Config), |
| Concatenate => False, |
| Parser => Parser); |
| |
| else |
| S := Getopt (Switches => "* " & Getopt_Description, |
| Concatenate => False, |
| Parser => Parser); |
| end if; |
| |
| exit when S = ASCII.NUL; |
| |
| declare |
| Sw : constant String := Real_Full_Switch (S, Parser); |
| Is_Section : Boolean := False; |
| |
| begin |
| if Cmd.Config /= null |
| and then Cmd.Config.Sections /= null |
| then |
| Section_Search : |
| for S in Cmd.Config.Sections'Range loop |
| if Sw = Cmd.Config.Sections (S).all then |
| Section := Cmd.Config.Sections (S); |
| Is_Section := True; |
| |
| exit Section_Search; |
| end if; |
| end loop Section_Search; |
| end if; |
| |
| if not Is_Section then |
| if Section = null then |
| Add_Switch (Cmd, Sw, Parameter (Parser)); |
| else |
| Add_Switch |
| (Cmd, Sw, Parameter (Parser), |
| Section => Section.all); |
| end if; |
| end if; |
| end; |
| |
| exception |
| when Invalid_Parameter => |
| |
| -- Add it with no parameter, if that's the way the user |
| -- wants it. |
| |
| -- Specify the separator in all cases, as the switch might |
| -- need to be unaliased, and the alias might contain |
| -- switches with parameters. |
| |
| if Section = null then |
| Add_Switch |
| (Cmd, Switch_Char & Full_Switch (Parser)); |
| else |
| Add_Switch |
| (Cmd, Switch_Char & Full_Switch (Parser), |
| Section => Section.all); |
| end if; |
| end; |
| end loop; |
| |
| Free (Parser); |
| end if; |
| end Set_Command_Line; |
| |
| ---------------- |
| -- Looking_At -- |
| ---------------- |
| |
| function Looking_At |
| (Type_Str : String; |
| Index : Natural; |
| Substring : String) return Boolean |
| is |
| begin |
| return Index + Substring'Length - 1 <= Type_Str'Last |
| and then Type_Str (Index .. Index + Substring'Length - 1) = Substring; |
| end Looking_At; |
| |
| ------------------------ |
| -- Can_Have_Parameter -- |
| ------------------------ |
| |
| function Can_Have_Parameter (S : String) return Boolean is |
| begin |
| if S'Length <= 1 then |
| return False; |
| end if; |
| |
| case S (S'Last) is |
| when '!' | ':' | '?' | '=' => |
| return True; |
| when others => |
| return False; |
| end case; |
| end Can_Have_Parameter; |
| |
| ----------------------- |
| -- Require_Parameter -- |
| ----------------------- |
| |
| function Require_Parameter (S : String) return Boolean is |
| begin |
| if S'Length <= 1 then |
| return False; |
| end if; |
| |
| case S (S'Last) is |
| when '!' | ':' | '=' => |
| return True; |
| when others => |
| return False; |
| end case; |
| end Require_Parameter; |
| |
| ------------------- |
| -- Actual_Switch -- |
| ------------------- |
| |
| function Actual_Switch (S : String) return String is |
| begin |
| if S'Length <= 1 then |
| return S; |
| end if; |
| |
| case S (S'Last) is |
| when '!' | ':' | '?' | '=' => |
| return S (S'First .. S'Last - 1); |
| when others => |
| return S; |
| end case; |
| end Actual_Switch; |
| |
| ---------------------------- |
| -- For_Each_Simple_Switch -- |
| ---------------------------- |
| |
| procedure For_Each_Simple_Switch |
| (Config : Command_Line_Configuration; |
| Section : String; |
| Switch : String; |
| Parameter : String := ""; |
| Unalias : Boolean := True) |
| is |
| function Group_Analysis |
| (Prefix : String; |
| Group : String) return Boolean; |
| -- Perform the analysis of a group of switches |
| |
| Found_In_Config : Boolean := False; |
| function Is_In_Config |
| (Config_Switch : String; Index : Integer) return Boolean; |
| -- If Switch is the same as Config_Switch, run the callback and sets |
| -- Found_In_Config to True. |
| |
| function Starts_With |
| (Config_Switch : String; Index : Integer) return Boolean; |
| -- if Switch starts with Config_Switch, sets Found_In_Config to True. |
| -- The return value is for the Foreach_Switch iterator. |
| |
| -------------------- |
| -- Group_Analysis -- |
| -------------------- |
| |
| function Group_Analysis |
| (Prefix : String; |
| Group : String) return Boolean |
| is |
| Idx : Natural; |
| Found : Boolean; |
| |
| function Analyze_Simple_Switch |
| (Switch : String; Index : Integer) return Boolean; |
| -- "Switches" is one of the switch definitions passed to the |
| -- configuration, not one of the switches found on the command line. |
| |
| --------------------------- |
| -- Analyze_Simple_Switch -- |
| --------------------------- |
| |
| function Analyze_Simple_Switch |
| (Switch : String; Index : Integer) return Boolean |
| is |
| pragma Unreferenced (Index); |
| |
| Full : constant String := Prefix & Group (Idx .. Group'Last); |
| |
| Sw : constant String := Actual_Switch (Switch); |
| -- Switches definition minus argument definition |
| |
| Last : Natural; |
| Param : Natural; |
| |
| begin |
| -- Verify that sw starts with Prefix |
| |
| if Looking_At (Sw, Sw'First, Prefix) |
| |
| -- Verify that the group starts with sw |
| |
| and then Looking_At (Full, Full'First, Sw) |
| then |
| Last := Idx + Sw'Length - Prefix'Length - 1; |
| Param := Last + 1; |
| |
| if Can_Have_Parameter (Switch) then |
| |
| -- Include potential parameter to the recursive call. Only |
| -- numbers are allowed. |
| |
| while Last < Group'Last |
| and then Group (Last + 1) in '0' .. '9' |
| loop |
| Last := Last + 1; |
| end loop; |
| end if; |
| |
| if not Require_Parameter (Switch) or else Last >= Param then |
| if Idx = Group'First |
| and then Last = Group'Last |
| and then Last < Param |
| then |
| -- The group only concerns a single switch. Do not |
| -- perform recursive call. |
| |
| -- Note that we still perform a recursive call if |
| -- a parameter is detected in the switch, as this |
| -- is a way to correctly identify such a parameter |
| -- in aliases. |
| |
| return False; |
| end if; |
| |
| Found := True; |
| |
| -- Recursive call, using the detected parameter if any |
| |
| if Last >= Param then |
| For_Each_Simple_Switch |
| (Config, |
| Section, |
| Prefix & Group (Idx .. Param - 1), |
| Group (Param .. Last)); |
| |
| else |
| For_Each_Simple_Switch |
| (Config, Section, Prefix & Group (Idx .. Last), ""); |
| end if; |
| |
| Idx := Last + 1; |
| return False; |
| end if; |
| end if; |
| |
| return True; |
| end Analyze_Simple_Switch; |
| |
| procedure Foreach is new Foreach_Switch (Analyze_Simple_Switch); |
| |
| -- Start of processing for Group_Analysis |
| |
| begin |
| Idx := Group'First; |
| while Idx <= Group'Last loop |
| Found := False; |
| Foreach (Config, Section); |
| |
| if not Found then |
| For_Each_Simple_Switch |
| (Config, Section, Prefix & Group (Idx), ""); |
| Idx := Idx + 1; |
| end if; |
| end loop; |
| |
| return True; |
| end Group_Analysis; |
| |
| ------------------ |
| -- Is_In_Config -- |
| ------------------ |
| |
| function Is_In_Config |
| (Config_Switch : String; Index : Integer) return Boolean |
| is |
| Last : Natural; |
| P : Switch_Parameter_Type; |
| |
| begin |
| Decompose_Switch (Config_Switch, P, Last); |
| |
| if Config_Switch (Config_Switch'First .. Last) = Switch then |
| case P is |
| when Parameter_None => |
| if Parameter = "" then |
| Callback (Switch, "", "", Index => Index); |
| Found_In_Config := True; |
| return False; |
| end if; |
| |
| when Parameter_With_Optional_Space => |
| Callback (Switch, " ", Parameter, Index => Index); |
| Found_In_Config := True; |
| return False; |
| |
| when Parameter_With_Space_Or_Equal => |
| Callback (Switch, "=", Parameter, Index => Index); |
| Found_In_Config := True; |
| return False; |
| |
| when Parameter_No_Space |
| | Parameter_Optional |
| => |
| Callback (Switch, "", Parameter, Index); |
| Found_In_Config := True; |
| return False; |
| end case; |
| end if; |
| |
| return True; |
| end Is_In_Config; |
| |
| ----------------- |
| -- Starts_With -- |
| ----------------- |
| |
| function Starts_With |
| (Config_Switch : String; Index : Integer) return Boolean |
| is |
| Last : Natural; |
| Param : Natural; |
| P : Switch_Parameter_Type; |
| |
| begin |
| -- This function is called when we believe the parameter was |
| -- specified as part of the switch, instead of separately. Thus we |
| -- look in the config to find all possible switches. |
| |
| Decompose_Switch (Config_Switch, P, Last); |
| |
| if Looking_At |
| (Switch, Switch'First, |
| Config_Switch (Config_Switch'First .. Last)) |
| then |
| -- Set first char of Param, and last char of Switch |
| |
| Param := Switch'First + Last; |
| Last := Switch'First + Last - Config_Switch'First; |
| |
| case P is |
| |
| -- None is already handled in Is_In_Config |
| |
| when Parameter_None => |
| null; |
| |
| when Parameter_With_Space_Or_Equal => |
| if Param <= Switch'Last |
| and then |
| (Switch (Param) = ' ' or else Switch (Param) = '=') |
| then |
| Callback (Switch (Switch'First .. Last), |
| "=", Switch (Param + 1 .. Switch'Last), Index); |
| Found_In_Config := True; |
| return False; |
| end if; |
| |
| when Parameter_With_Optional_Space => |
| if Param <= Switch'Last and then Switch (Param) = ' ' then |
| Param := Param + 1; |
| end if; |
| |
| Callback (Switch (Switch'First .. Last), |
| " ", Switch (Param .. Switch'Last), Index); |
| Found_In_Config := True; |
| return False; |
| |
| when Parameter_No_Space |
| | Parameter_Optional |
| => |
| Callback (Switch (Switch'First .. Last), |
| "", Switch (Param .. Switch'Last), Index); |
| Found_In_Config := True; |
| return False; |
| end case; |
| end if; |
| return True; |
| end Starts_With; |
| |
| procedure Foreach_In_Config is new Foreach_Switch (Is_In_Config); |
| procedure Foreach_Starts_With is new Foreach_Switch (Starts_With); |
| |
| -- Start of processing for For_Each_Simple_Switch |
| |
| begin |
| -- First determine if the switch corresponds to one belonging to the |
| -- configuration. If so, run callback and exit. |
| |
| -- ??? Is this necessary. On simple tests, we seem to have the same |
| -- results with or without this call. |
| |
| Foreach_In_Config (Config, Section); |
| |
| if Found_In_Config then |
| return; |
| end if; |
| |
| -- If adding a switch that can in fact be expanded through aliases, |
| -- add separately each of its expansions. |
| |
| -- This takes care of expansions like "-T" -> "-gnatwrs", where the |
| -- alias and its expansion do not have the same prefix. Given the order |
| -- in which we do things here, the expansion of the alias will itself |
| -- be checked for a common prefix and split into simple switches. |
| |
| if Unalias |
| and then Config /= null |
| and then Config.Aliases /= null |
| then |
| for A in Config.Aliases'Range loop |
| if Config.Aliases (A).Section.all = Section |
| and then Config.Aliases (A).Alias.all = Switch |
| and then Parameter = "" |
| then |
| For_Each_Simple_Switch |
| (Config, Section, Config.Aliases (A).Expansion.all, ""); |
| return; |
| end if; |
| end loop; |
| end if; |
| |
| -- If adding a switch grouping several switches, add each of the simple |
| -- switches instead. |
| |
| if Config /= null and then Config.Prefixes /= null then |
| for P in Config.Prefixes'Range loop |
| if Switch'Length > Config.Prefixes (P)'Length + 1 |
| and then |
| Looking_At (Switch, Switch'First, Config.Prefixes (P).all) |
| then |
| -- Alias expansion will be done recursively |
| |
| if Config.Switches = null then |
| for S in Switch'First + Config.Prefixes (P)'Length |
| .. Switch'Last |
| loop |
| For_Each_Simple_Switch |
| (Config, Section, |
| Config.Prefixes (P).all & Switch (S), ""); |
| end loop; |
| |
| return; |
| |
| elsif Group_Analysis |
| (Config.Prefixes (P).all, |
| Switch |
| (Switch'First + Config.Prefixes (P)'Length .. Switch'Last)) |
| then |
| -- Recursive calls already done on each switch of the group: |
| -- Return without executing Callback. |
| |
| return; |
| end if; |
| end if; |
| end loop; |
| end if; |
| |
| -- Test if added switch is a known switch with parameter attached |
| -- instead of being specified separately |
| |
| if Parameter = "" |
| and then Config /= null |
| and then Config.Switches /= null |
| then |
| Found_In_Config := False; |
| Foreach_Starts_With (Config, Section); |
| |
| if Found_In_Config then |
| return; |
| end if; |
| end if; |
| |
| -- The switch is invalid in the config, but we still want to report it. |
| -- The config could, for instance, include "*" to specify it accepts |
| -- all switches. |
| |
| Callback (Switch, " ", Parameter, Index => -1); |
| end For_Each_Simple_Switch; |
| |
| ---------------- |
| -- Add_Switch -- |
| ---------------- |
| |
| procedure Add_Switch |
| (Cmd : in out Command_Line; |
| Switch : String; |
| Parameter : String := ""; |
| Separator : Character := ASCII.NUL; |
| Section : String := ""; |
| Add_Before : Boolean := False) |
| is |
| Success : Boolean; |
| begin |
| Add_Switch (Cmd, Switch, Parameter, Separator, |
| Section, Add_Before, Success); |
| end Add_Switch; |
| |
| ---------------- |
| -- Add_Switch -- |
| ---------------- |
| |
| procedure Add_Switch |
| (Cmd : in out Command_Line; |
| Switch : String; |
| Parameter : String := ""; |
| Separator : Character := ASCII.NUL; |
| Section : String := ""; |
| Add_Before : Boolean := False; |
| Success : out Boolean) |
| is |
| procedure Add_Simple_Switch |
| (Simple : String; |
| Sepa : String; |
| Param : String; |
| Index : Integer); |
| -- Add a new switch that has had all its aliases expanded, and switches |
| -- ungrouped. We know there are no more aliases in Switches. |
| |
| ----------------------- |
| -- Add_Simple_Switch -- |
| ----------------------- |
| |
| procedure Add_Simple_Switch |
| (Simple : String; |
| Sepa : String; |
| Param : String; |
| Index : Integer) |
| is |
| Sep : Character; |
| |
| begin |
| if Index = -1 |
| and then Cmd.Config /= null |
| and then not Cmd.Config.Star_Switch |
| then |
| raise Invalid_Switch |
| with "Invalid switch " & Simple; |
| end if; |
| |
| if Separator /= ASCII.NUL then |
| Sep := Separator; |
| |
| elsif Sepa = "" then |
| Sep := ASCII.NUL; |
| else |
| Sep := Sepa (Sepa'First); |
| end if; |
| |
| if Cmd.Expanded = null then |
| Cmd.Expanded := new Argument_List'(1 .. 1 => new String'(Simple)); |
| |
| if Param /= "" then |
| Cmd.Params := |
| new Argument_List'(1 .. 1 => new String'(Sep & Param)); |
| else |
| Cmd.Params := new Argument_List'(1 .. 1 => null); |
| end if; |
| |
| if Section = "" then |
| Cmd.Sections := new Argument_List'(1 .. 1 => null); |
| else |
| Cmd.Sections := |
| new Argument_List'(1 .. 1 => new String'(Section)); |
| end if; |
| |
| else |
| -- Do we already have this switch? |
| |
| for C in Cmd.Expanded'Range loop |
| if Cmd.Expanded (C).all = Simple |
| and then |
| ((Cmd.Params (C) = null and then Param = "") |
| or else |
| (Cmd.Params (C) /= null |
| and then Cmd.Params (C).all = Sep & Param)) |
| and then |
| ((Cmd.Sections (C) = null and then Section = "") |
| or else |
| (Cmd.Sections (C) /= null |
| and then Cmd.Sections (C).all = Section)) |
| then |
| return; |
| end if; |
| end loop; |
| |
| -- Inserting at least one switch |
| |
| Success := True; |
| Add (Cmd.Expanded, new String'(Simple), Add_Before); |
| |
| if Param /= "" then |
| Add |
| (Cmd.Params, |
| new String'(Sep & Param), |
| Add_Before); |
| else |
| Add |
| (Cmd.Params, |
| null, |
| Add_Before); |
| end if; |
| |
| if Section = "" then |
| Add |
| (Cmd.Sections, |
| null, |
| Add_Before); |
| else |
| Add |
| (Cmd.Sections, |
| new String'(Section), |
| Add_Before); |
| end if; |
| end if; |
| end Add_Simple_Switch; |
| |
| procedure Add_Simple_Switches is |
| new For_Each_Simple_Switch (Add_Simple_Switch); |
| |
| -- Local Variables |
| |
| Section_Valid : Boolean := False; |
| |
| -- Start of processing for Add_Switch |
| |
| begin |
| if Section /= "" and then Cmd.Config /= null then |
| for S in Cmd.Config.Sections'Range loop |
| if Section = Cmd.Config.Sections (S).all then |
| Section_Valid := True; |
| exit; |
| end if; |
| end loop; |
| |
| if not Section_Valid then |
| raise Invalid_Section; |
| end if; |
| end if; |
| |
| Success := False; |
| Add_Simple_Switches (Cmd.Config, Section, Switch, Parameter); |
| Free (Cmd.Coalesce); |
| end Add_Switch; |
| |
| ------------ |
| -- Remove -- |
| ------------ |
| |
| procedure Remove (Line : in out Argument_List_Access; Index : Integer) is |
| Tmp : Argument_List_Access := Line; |
| |
| begin |
| Line := new Argument_List (Tmp'First .. Tmp'Last - 1); |
| |
| if Index /= Tmp'First then |
| Line (Tmp'First .. Index - 1) := Tmp (Tmp'First .. Index - 1); |
| end if; |
| |
| Free (Tmp (Index)); |
| |
| if Index /= Tmp'Last then |
| Line (Index .. Tmp'Last - 1) := Tmp (Index + 1 .. Tmp'Last); |
| end if; |
| |
| Unchecked_Free (Tmp); |
| end Remove; |
| |
| --------- |
| -- Add -- |
| --------- |
| |
| procedure Add |
| (Line : in out Argument_List_Access; |
| Str : String_Access; |
| Before : Boolean := False) |
| is |
| Tmp : Argument_List_Access := Line; |
| |
| begin |
| if Tmp /= null then |
| Line := new Argument_List (Tmp'First .. Tmp'Last + 1); |
| |
| if Before then |
| Line (Tmp'First) := Str; |
| Line (Tmp'First + 1 .. Tmp'Last + 1) := Tmp.all; |
| else |
| Line (Tmp'Range) := Tmp.all; |
| Line (Tmp'Last + 1) := Str; |
| end if; |
| |
| Unchecked_Free (Tmp); |
| |
| else |
| Line := new Argument_List'(1 .. 1 => Str); |
| end if; |
| end Add; |
| |
| ------------------- |
| -- Remove_Switch -- |
| ------------------- |
| |
| procedure Remove_Switch |
| (Cmd : in out Command_Line; |
| Switch : String; |
| Remove_All : Boolean := False; |
| Has_Parameter : Boolean := False; |
| Section : String := "") |
| is |
| Success : Boolean; |
| begin |
| Remove_Switch (Cmd, Switch, Remove_All, Has_Parameter, Section, Success); |
| end Remove_Switch; |
| |
| ------------------- |
| -- Remove_Switch -- |
| ------------------- |
| |
| procedure Remove_Switch |
| (Cmd : in out Command_Line; |
| Switch : String; |
| Remove_All : Boolean := False; |
| Has_Parameter : Boolean := False; |
| Section : String := ""; |
| Success : out Boolean) |
| is |
| procedure Remove_Simple_Switch |
| (Simple, Separator, Param : String; Index : Integer); |
| -- Removes a simple switch, with no aliasing or grouping |
| |
| -------------------------- |
| -- Remove_Simple_Switch -- |
| -------------------------- |
| |
| procedure Remove_Simple_Switch |
| (Simple, Separator, Param : String; Index : Integer) |
| is |
| C : Integer; |
| pragma Unreferenced (Param, Separator, Index); |
| |
| begin |
| if Cmd.Expanded /= null then |
| C := Cmd.Expanded'First; |
| while C <= Cmd.Expanded'Last loop |
| if Cmd.Expanded (C).all = Simple |
| and then |
| (Remove_All |
| or else (Cmd.Sections (C) = null |
| and then Section = "") |
| or else (Cmd.Sections (C) /= null |
| and then Section = Cmd.Sections (C).all)) |
| and then (not Has_Parameter or else Cmd.Params (C) /= null) |
| then |
| Remove (Cmd.Expanded, C); |
| Remove (Cmd.Params, C); |
| Remove (Cmd.Sections, C); |
| Success := True; |
| |
| if not Remove_All then |
| return; |
| end if; |
| |
| else |
| C := C + 1; |
| end if; |
| end loop; |
| end if; |
| end Remove_Simple_Switch; |
| |
| procedure Remove_Simple_Switches is |
| new For_Each_Simple_Switch (Remove_Simple_Switch); |
| |
| -- Start of processing for Remove_Switch |
| |
| begin |
| Success := False; |
| Remove_Simple_Switches |
| (Cmd.Config, Section, Switch, "", Unalias => not Has_Parameter); |
| Free (Cmd.Coalesce); |
| end Remove_Switch; |
| |
| ------------------- |
| -- Remove_Switch -- |
| ------------------- |
| |
| procedure Remove_Switch |
| (Cmd : in out Command_Line; |
| Switch : String; |
| Parameter : String; |
| Section : String := "") |
| is |
| procedure Remove_Simple_Switch |
| (Simple, Separator, Param : String; Index : Integer); |
| -- Removes a simple switch, with no aliasing or grouping |
| |
| -------------------------- |
| -- Remove_Simple_Switch -- |
| -------------------------- |
| |
| procedure Remove_Simple_Switch |
| (Simple, Separator, Param : String; Index : Integer) |
| is |
| pragma Unreferenced (Separator, Index); |
| C : Integer; |
| |
| begin |
| if Cmd.Expanded /= null then |
| C := Cmd.Expanded'First; |
| while C <= Cmd.Expanded'Last loop |
| if Cmd.Expanded (C).all = Simple |
| and then |
| ((Cmd.Sections (C) = null |
| and then Section = "") |
| or else |
| (Cmd.Sections (C) /= null |
| and then Section = Cmd.Sections (C).all)) |
| and then |
| ((Cmd.Params (C) = null and then Param = "") |
| or else |
| (Cmd.Params (C) /= null |
| |
| -- Ignore the separator stored in Parameter |
| |
| and then |
| Cmd.Params (C) (Cmd.Params (C)'First + 1 |
| .. Cmd.Params (C)'Last) = Param)) |
| then |
| Remove (Cmd.Expanded, C); |
| Remove (Cmd.Params, C); |
| Remove (Cmd.Sections, C); |
| |
| -- The switch is necessarily unique by construction of |
| -- Add_Switch. |
| |
| return; |
| |
| else |
| C := C + 1; |
| end if; |
| end loop; |
| end if; |
| end Remove_Simple_Switch; |
| |
| procedure Remove_Simple_Switches is |
| new For_Each_Simple_Switch (Remove_Simple_Switch); |
| |
| -- Start of processing for Remove_Switch |
| |
| begin |
| Remove_Simple_Switches (Cmd.Config, Section, Switch, Parameter); |
| Free (Cmd.Coalesce); |
| end Remove_Switch; |
| |
| -------------------- |
| -- Group_Switches -- |
| -------------------- |
| |
| procedure Group_Switches |
| (Cmd : Command_Line; |
| Result : Argument_List_Access; |
| Sections : Argument_List_Access; |
| Params : Argument_List_Access) |
| is |
| function Compatible_Parameter (Param : String_Access) return Boolean; |
| -- True when the parameter can be part of a group |
| |
| -------------------------- |
| -- Compatible_Parameter -- |
| -------------------------- |
| |
| function Compatible_Parameter (Param : String_Access) return Boolean is |
| begin |
| -- No parameter OK |
| |
| if Param = null then |
| return True; |
| |
| -- We need parameters without separators |
| |
| elsif Param (Param'First) /= ASCII.NUL then |
| return False; |
| |
| -- Parameters must be all digits |
| |
| else |
| for J in Param'First + 1 .. Param'Last loop |
| if Param (J) not in '0' .. '9' then |
| return False; |
| end if; |
| end loop; |
| |
| return True; |
| end if; |
| end Compatible_Parameter; |
| |
| -- Local declarations |
| |
| Group : Ada.Strings.Unbounded.Unbounded_String; |
| First : Natural; |
| use type Ada.Strings.Unbounded.Unbounded_String; |
| |
| -- Start of processing for Group_Switches |
| |
| begin |
| if Cmd.Config = null or else Cmd.Config.Prefixes = null then |
| return; |
| end if; |
| |
| for P in Cmd.Config.Prefixes'Range loop |
| Group := Ada.Strings.Unbounded.Null_Unbounded_String; |
| First := 0; |
| |
| for C in Result'Range loop |
| if Result (C) /= null |
| and then Compatible_Parameter (Params (C)) |
| and then Looking_At |
| (Result (C).all, |
| Result (C)'First, |
| Cmd.Config.Prefixes (P).all) |
| then |
| -- If we are still in the same section, group the switches |
| |
| if First = 0 |
| or else |
| (Sections (C) = null |
| and then Sections (First) = null) |
| or else |
| (Sections (C) /= null |
| and then Sections (First) /= null |
| and then Sections (C).all = Sections (First).all) |
| then |
| Group := |
| Group & |
| Result (C) |
| (Result (C)'First + Cmd.Config.Prefixes (P)'Length .. |
| Result (C)'Last); |
| |
| if Params (C) /= null then |
| Group := |
| Group & |
| Params (C) (Params (C)'First + 1 .. Params (C)'Last); |
| Free (Params (C)); |
| end if; |
| |
| if First = 0 then |
| First := C; |
| end if; |
| |
| Free (Result (C)); |
| |
| -- We changed section: we put the grouped switches to the first |
| -- place, on continue with the new section. |
| |
| else |
| Result (First) := |
| new String' |
| (Cmd.Config.Prefixes (P).all & |
| Ada.Strings.Unbounded.To_String (Group)); |
| Group := |
| Ada.Strings.Unbounded.To_Unbounded_String |
| (Result (C) |
| (Result (C)'First + Cmd.Config.Prefixes (P)'Length .. |
| Result (C)'Last)); |
| First := C; |
| end if; |
| end if; |
| end loop; |
| |
| if First > 0 then |
| Result (First) := |
| new String' |
| (Cmd.Config.Prefixes (P).all & |
| Ada.Strings.Unbounded.To_String (Group)); |
| end if; |
| end loop; |
| end Group_Switches; |
| |
| -------------------- |
| -- Alias_Switches -- |
| -------------------- |
| |
| procedure Alias_Switches |
| (Cmd : Command_Line; |
| Result : Argument_List_Access; |
| Params : Argument_List_Access) |
| is |
| Found : Boolean; |
| First : Natural; |
| |
| procedure Check_Cb (Switch, Separator, Param : String; Index : Integer); |
| -- Checks whether the command line contains [Switch]. Sets the global |
| -- variable [Found] appropriately. This is called for each simple switch |
| -- that make up an alias, to know whether the alias should be applied. |
| |
| procedure Remove_Cb (Switch, Separator, Param : String; Index : Integer); |
| -- Remove the simple switch [Switch] from the command line, since it is |
| -- part of a simpler alias |
| |
| -------------- |
| -- Check_Cb -- |
| -------------- |
| |
| procedure Check_Cb |
| (Switch, Separator, Param : String; Index : Integer) |
| is |
| pragma Unreferenced (Separator, Index); |
| |
| begin |
| if Found then |
| for E in Result'Range loop |
| if Result (E) /= null |
| and then |
| (Params (E) = null |
| or else Params (E) (Params (E)'First + 1 .. |
| Params (E)'Last) = Param) |
| and then Result (E).all = Switch |
| then |
| return; |
| end if; |
| end loop; |
| |
| Found := False; |
| end if; |
| end Check_Cb; |
| |
| --------------- |
| -- Remove_Cb -- |
| --------------- |
| |
| procedure Remove_Cb (Switch, Separator, Param : String; Index : Integer) |
| is |
| pragma Unreferenced (Separator, Index); |
| |
| begin |
| for E in Result'Range loop |
| if Result (E) /= null |
| and then |
| (Params (E) = null |
| or else Params (E) (Params (E)'First + 1 |
| .. Params (E)'Last) = Param) |
| and then Result (E).all = Switch |
| then |
| if First > E then |
| First := E; |
| end if; |
| |
| Free (Result (E)); |
| Free (Params (E)); |
| return; |
| end if; |
| end loop; |
| end Remove_Cb; |
| |
| procedure Check_All is new For_Each_Simple_Switch (Check_Cb); |
| procedure Remove_All is new For_Each_Simple_Switch (Remove_Cb); |
| |
| -- Start of processing for Alias_Switches |
| |
| begin |
| if Cmd.Config = null or else Cmd.Config.Aliases = null then |
| return; |
| end if; |
| |
| for A in Cmd.Config.Aliases'Range loop |
| |
| -- Compute the various simple switches that make up the alias. We |
| -- split the expansion into as many simple switches as possible, and |
| -- then check whether the expanded command line has all of them. |
| |
| Found := True; |
| Check_All (Cmd.Config, |
| Switch => Cmd.Config.Aliases (A).Expansion.all, |
| Section => Cmd.Config.Aliases (A).Section.all); |
| |
| if Found then |
| First := Integer'Last; |
| Remove_All (Cmd.Config, |
| Switch => Cmd.Config.Aliases (A).Expansion.all, |
| Section => Cmd.Config.Aliases (A).Section.all); |
| Result (First) := new String'(Cmd.Config.Aliases (A).Alias.all); |
| end if; |
| end loop; |
| end Alias_Switches; |
| |
| ------------------- |
| -- Sort_Sections -- |
| ------------------- |
| |
| procedure Sort_Sections |
| (Line : not null GNAT.OS_Lib.Argument_List_Access; |
| Sections : GNAT.OS_Lib.Argument_List_Access; |
| Params : GNAT.OS_Lib.Argument_List_Access) |
| is |
| Sections_List : Argument_List_Access := |
| new Argument_List'(1 .. 1 => null); |
| Found : Boolean; |
| Old_Line : constant Argument_List := Line.all; |
| Old_Sections : constant Argument_List := Sections.all; |
| Old_Params : constant Argument_List := Params.all; |
| Index : Natural; |
| |
| begin |
| -- First construct a list of all sections |
| |
| for E in Line'Range loop |
| if Sections (E) /= null then |
| Found := False; |
| for S in Sections_List'Range loop |
| if (Sections_List (S) = null and then Sections (E) = null) |
| or else |
| (Sections_List (S) /= null |
| and then Sections (E) /= null |
| and then Sections_List (S).all = Sections (E).all) |
| then |
| Found := True; |
| exit; |
| end if; |
| end loop; |
| |
| if not Found then |
| Add (Sections_List, Sections (E)); |
| end if; |
| end if; |
| end loop; |
| |
| Index := Line'First; |
| |
| for S in Sections_List'Range loop |
| for E in Old_Line'Range loop |
| if (Sections_List (S) = null and then Old_Sections (E) = null) |
| or else |
| (Sections_List (S) /= null |
| and then Old_Sections (E) /= null |
| and then Sections_List (S).all = Old_Sections (E).all) |
| then |
| Line (Index) := Old_Line (E); |
| Sections (Index) := Old_Sections (E); |
| Params (Index) := Old_Params (E); |
| Index := Index + 1; |
| end if; |
| end loop; |
| end loop; |
| |
| Unchecked_Free (Sections_List); |
| end Sort_Sections; |
| |
| ----------- |
| -- Start -- |
| ----------- |
| |
| procedure Start |
| (Cmd : in out Command_Line; |
| Iter : in out Command_Line_Iterator; |
| Expanded : Boolean := False) |
| is |
| begin |
| if Cmd.Expanded = null then |
| Iter.List := null; |
| return; |
| end if; |
| |
| -- Reorder the expanded line so that sections are grouped |
| |
| Sort_Sections (Cmd.Expanded, Cmd.Sections, Cmd.Params); |
| |
| -- Coalesce the switches as much as possible |
| |
| if not Expanded |
| and then Cmd.Coalesce = null |
| then |
| Cmd.Coalesce := new Argument_List (Cmd.Expanded'Range); |
| for E in Cmd.Expanded'Range loop |
| Cmd.Coalesce (E) := new String'(Cmd.Expanded (E).all); |
| end loop; |
| |
| Free (Cmd.Coalesce_Sections); |
| Cmd.Coalesce_Sections := new Argument_List (Cmd.Sections'Range); |
| for E in Cmd.Sections'Range loop |
| Cmd.Coalesce_Sections (E) := |
| (if Cmd.Sections (E) = null then null |
| else new String'(Cmd.Sections (E).all)); |
| end loop; |
| |
| Free (Cmd.Coalesce_Params); |
| Cmd.Coalesce_Params := new Argument_List (Cmd.Params'Range); |
| for E in Cmd.Params'Range loop |
| Cmd.Coalesce_Params (E) := |
| (if Cmd.Params (E) = null then null |
| else new String'(Cmd.Params (E).all)); |
| end loop; |
| |
| -- Not a clone, since we will not modify the parameters anyway |
| |
| Alias_Switches (Cmd, Cmd.Coalesce, Cmd.Coalesce_Params); |
| Group_Switches |
| (Cmd, Cmd.Coalesce, Cmd.Coalesce_Sections, Cmd.Coalesce_Params); |
| end if; |
| |
| if Expanded then |
| Iter.List := Cmd.Expanded; |
| Iter.Params := Cmd.Params; |
| Iter.Sections := Cmd.Sections; |
| else |
| Iter.List := Cmd.Coalesce; |
| Iter.Params := Cmd.Coalesce_Params; |
| Iter.Sections := Cmd.Coalesce_Sections; |
| end if; |
| |
| if Iter.List = null then |
| Iter.Current := Integer'Last; |
| else |
| Iter.Current := Iter.List'First - 1; |
| Next (Iter); |
| end if; |
| end Start; |
| |
| -------------------- |
| -- Current_Switch -- |
| -------------------- |
| |
| function Current_Switch (Iter : Command_Line_Iterator) return String is |
| begin |
| return Iter.List (Iter.Current).all; |
| end Current_Switch; |
| |
| -------------------- |
| -- Is_New_Section -- |
| -------------------- |
| |
| function Is_New_Section (Iter : Command_Line_Iterator) return Boolean is |
| Section : constant String := Current_Section (Iter); |
| |
| begin |
| if Iter.Sections = null then |
| return False; |
| |
| elsif Iter.Current = Iter.Sections'First |
| or else Iter.Sections (Iter.Current - 1) = null |
| then |
| return Section /= ""; |
| |
| else |
| return Section /= Iter.Sections (Iter.Current - 1).all; |
| end if; |
| end Is_New_Section; |
| |
| --------------------- |
| -- Current_Section -- |
| --------------------- |
| |
| function Current_Section (Iter : Command_Line_Iterator) return String is |
| begin |
| if Iter.Sections = null |
| or else Iter.Current > Iter.Sections'Last |
| or else Iter.Sections (Iter.Current) = null |
| then |
| return ""; |
| end if; |
| |
| return Iter.Sections (Iter.Current).all; |
| end Current_Section; |
| |
| ----------------------- |
| -- Current_Separator -- |
| ----------------------- |
| |
| function Current_Separator (Iter : Command_Line_Iterator) return String is |
| begin |
| if Iter.Params = null |
| or else Iter.Current > Iter.Params'Last |
| or else Iter.Params (Iter.Current) = null |
| then |
| return ""; |
| |
| else |
| declare |
| Sep : constant Character := |
| Iter.Params (Iter.Current) (Iter.Params (Iter.Current)'First); |
| begin |
| if Sep = ASCII.NUL then |
| return ""; |
| else |
| return "" & Sep; |
| end if; |
| end; |
| end if; |
| end Current_Separator; |
| |
| ----------------------- |
| -- Current_Parameter -- |
| ----------------------- |
| |
| function Current_Parameter (Iter : Command_Line_Iterator) return String is |
| begin |
| if Iter.Params = null |
| or else Iter.Current > Iter.Params'Last |
| or else Iter.Params (Iter.Current) = null |
| then |
| return ""; |
| |
| else |
| -- Return result, skipping separator |
| |
| declare |
| P : constant String := Iter.Params (Iter.Current).all; |
| begin |
| return P (P'First + 1 .. P'Last); |
| end; |
| end if; |
| end Current_Parameter; |
| |
| -------------- |
| -- Has_More -- |
| -------------- |
| |
| function Has_More (Iter : Command_Line_Iterator) return Boolean is |
| begin |
| return Iter.List /= null and then Iter.Current <= Iter.List'Last; |
| end Has_More; |
| |
| ---------- |
| -- Next -- |
| ---------- |
| |
| procedure Next (Iter : in out Command_Line_Iterator) is |
| begin |
| Iter.Current := Iter.Current + 1; |
| while Iter.Current <= Iter.List'Last |
| and then Iter.List (Iter.Current) = null |
| loop |
| Iter.Current := Iter.Current + 1; |
| end loop; |
| end Next; |
| |
| ---------- |
| -- Free -- |
| ---------- |
| |
| procedure Free (Config : in out Command_Line_Configuration) is |
| procedure Unchecked_Free is new |
| Ada.Unchecked_Deallocation |
| (Switch_Definitions, Switch_Definitions_List); |
| |
| procedure Unchecked_Free is new |
| Ada.Unchecked_Deallocation |
| (Alias_Definitions, Alias_Definitions_List); |
| |
| begin |
| if Config /= null then |
| Free (Config.Prefixes); |
| Free (Config.Sections); |
| Free (Config.Usage); |
| Free (Config.Help); |
| Free (Config.Help_Msg); |
| |
| if Config.Aliases /= null then |
| for A in Config.Aliases'Range loop |
| Free (Config.Aliases (A).Alias); |
| Free (Config.Aliases (A).Expansion); |
| Free (Config.Aliases (A).Section); |
| end loop; |
| |
| Unchecked_Free (Config.Aliases); |
| end if; |
| |
| if Config.Switches /= null then |
| for S in Config.Switches'Range loop |
| Free (Config.Switches (S).Switch); |
| Free (Config.Switches (S).Long_Switch); |
| Free (Config.Switches (S).Help); |
| Free (Config.Switches (S).Section); |
| Free (Config.Switches (S).Argument); |
| end loop; |
| |
| Unchecked_Free (Config.Switches); |
| end if; |
| |
| Unchecked_Free (Config); |
| end if; |
| end Free; |
| |
| ---------- |
| -- Free -- |
| ---------- |
| |
| procedure Free (Cmd : in out Command_Line) is |
| begin |
| Free (Cmd.Expanded); |
| Free (Cmd.Coalesce); |
| Free (Cmd.Coalesce_Sections); |
| Free (Cmd.Coalesce_Params); |
| Free (Cmd.Params); |
| Free (Cmd.Sections); |
| end Free; |
| |
| --------------- |
| -- Set_Usage -- |
| --------------- |
| |
| procedure Set_Usage |
| (Config : in out Command_Line_Configuration; |
| Usage : String := "[switches] [arguments]"; |
| Help : String := ""; |
| Help_Msg : String := "") |
| is |
| begin |
| if Config = null then |
| Config := new Command_Line_Configuration_Record; |
| end if; |
| |
| Free (Config.Usage); |
| Free (Config.Help); |
| Free (Config.Help_Msg); |
| |
| Config.Usage := new String'(Usage); |
| Config.Help := new String'(Help); |
| Config.Help_Msg := new String'(Help_Msg); |
| end Set_Usage; |
| |
| ------------------ |
| -- Display_Help -- |
| ------------------ |
| |
| procedure Display_Help (Config : Command_Line_Configuration) is |
| function Switch_Name |
| (Def : Switch_Definition; |
| Section : String) return String; |
| -- Return the "-short, --long=ARG" string for Def. |
| -- Returns "" if the switch is not in the section. |
| |
| function Param_Name |
| (P : Switch_Parameter_Type; |
| Name : String := "ARG") return String; |
| -- Return the display for a switch parameter |
| |
| procedure Display_Section_Help (Section : String); |
| -- Display the help for a specific section ("" is the default section) |
| |
| -------------------------- |
| -- Display_Section_Help -- |
| -------------------------- |
| |
| procedure Display_Section_Help (Section : String) is |
| Max_Len : Natural := 0; |
| |
| begin |
| -- ??? Special display for "*" |
| |
| New_Line; |
| |
| if Section /= "" then |
| Put_Line ("Switches after " & Section); |
| end if; |
| |
| -- Compute size of the switches column |
| |
| for S in Config.Switches'Range loop |
| Max_Len := Natural'Max |
| (Max_Len, Switch_Name (Config.Switches (S), Section)'Length); |
| end loop; |
| |
| if Config.Aliases /= null then |
| for A in Config.Aliases'Range loop |
| if Config.Aliases (A).Section.all = Section then |
| Max_Len := Natural'Max |
| (Max_Len, Config.Aliases (A).Alias'Length); |
| end if; |
| end loop; |
| end if; |
| |
| -- Display the switches |
| |
| for S in Config.Switches'Range loop |
| declare |
| N : constant String := |
| Switch_Name (Config.Switches (S), Section); |
| |
| begin |
| if N /= "" then |
| Put (" "); |
| Put (N); |
| Put ([1 .. Max_Len - N'Length + 1 => ' ']); |
| |
| if Config.Switches (S).Help /= null then |
| Put (Config.Switches (S).Help.all); |
| end if; |
| |
| New_Line; |
| end if; |
| end; |
| end loop; |
| |
| -- Display the aliases |
| |
| if Config.Aliases /= null then |
| for A in Config.Aliases'Range loop |
| if Config.Aliases (A).Section.all = Section then |
| Put (" "); |
| Put (Config.Aliases (A).Alias.all); |
| Put ([1 .. Max_Len - Config.Aliases (A).Alias'Length + 1 |
| => ' ']); |
| Put ("Equivalent to " & Config.Aliases (A).Expansion.all); |
| New_Line; |
| end if; |
| end loop; |
| end if; |
| end Display_Section_Help; |
| |
| ---------------- |
| -- Param_Name -- |
| ---------------- |
| |
| function Param_Name |
| (P : Switch_Parameter_Type; |
| Name : String := "ARG") return String |
| is |
| begin |
| case P is |
| when Parameter_None => |
| return ""; |
| |
| when Parameter_With_Optional_Space => |
| return " " & To_Upper (Name); |
| |
| when Parameter_With_Space_Or_Equal => |
| return "=" & To_Upper (Name); |
| |
| when Parameter_No_Space => |
| return To_Upper (Name); |
| |
| when Parameter_Optional => |
| return '[' & To_Upper (Name) & ']'; |
| end case; |
| end Param_Name; |
| |
| ----------------- |
| -- Switch_Name -- |
| ----------------- |
| |
| function Switch_Name |
| (Def : Switch_Definition; |
| Section : String) return String |
| is |
| use Ada.Strings.Unbounded; |
| Result : Unbounded_String; |
| P1, P2 : Switch_Parameter_Type; |
| Last1, Last2 : Integer := 0; |
| |
| begin |
| if (Section = "" and then Def.Section = null) |
| or else (Def.Section /= null and then Def.Section.all = Section) |
| then |
| if Def.Switch /= null and then Def.Switch.all = "*" then |
| return "[any switch]"; |
| end if; |
| |
| if Def.Switch /= null then |
| Decompose_Switch (Def.Switch.all, P1, Last1); |
| Append (Result, Def.Switch (Def.Switch'First .. Last1)); |
| |
| if Def.Long_Switch /= null then |
| Decompose_Switch (Def.Long_Switch.all, P2, Last2); |
| Append (Result, ", " |
| & Def.Long_Switch (Def.Long_Switch'First .. Last2)); |
| |
| if Def.Argument = null then |
| Append (Result, Param_Name (P2, "ARG")); |
| else |
| Append (Result, Param_Name (P2, Def.Argument.all)); |
| end if; |
| |
| else |
| if Def.Argument = null then |
| Append (Result, Param_Name (P1, "ARG")); |
| else |
| Append (Result, Param_Name (P1, Def.Argument.all)); |
| end if; |
| end if; |
| |
| -- Def.Switch is null (Long_Switch must be non-null) |
| |
| else |
| Decompose_Switch (Def.Long_Switch.all, P2, Last2); |
| Append (Result, |
| Def.Long_Switch (Def.Long_Switch'First .. Last2)); |
| |
| if Def.Argument = null then |
| Append (Result, Param_Name (P2, "ARG")); |
| else |
| Append (Result, Param_Name (P2, Def.Argument.all)); |
| end if; |
| end if; |
| end if; |
| |
| return To_String (Result); |
| end Switch_Name; |
| |
| -- Start of processing for Display_Help |
| |
| begin |
| if Config = null then |
| return; |
| end if; |
| |
| if Config.Help /= null and then Config.Help.all /= "" then |
| Put_Line (Config.Help.all); |
| end if; |
| |
| if Config.Usage /= null then |
| Put_Line ("Usage: " |
| & Base_Name |
| (Ada.Command_Line.Command_Name) & " " & Config.Usage.all); |
| else |
| Put_Line ("Usage: " & Base_Name (Ada.Command_Line.Command_Name) |
| & " [switches] [arguments]"); |
| end if; |
| |
| if Config.Help_Msg /= null and then Config.Help_Msg.all /= "" then |
| Put_Line (Config.Help_Msg.all); |
| |
| else |
| Display_Section_Help (""); |
| |
| if Config.Sections /= null and then Config.Switches /= null then |
| for S in Config.Sections'Range loop |
| Display_Section_Help (Config.Sections (S).all); |
| end loop; |
| end if; |
| end if; |
| end Display_Help; |
| |
| ------------ |
| -- Getopt -- |
| ------------ |
| |
| procedure Getopt |
| (Config : Command_Line_Configuration; |
| Callback : Switch_Handler := null; |
| Parser : Opt_Parser := Command_Line_Parser; |
| Concatenate : Boolean := True; |
| Quiet : Boolean := False) |
| is |
| Local_Config : Command_Line_Configuration := Config; |
| Getopt_Switches : String_Access; |
| C : Character := ASCII.NUL; |
| |
| Empty_Name : aliased constant String := ""; |
| Current_Section : Integer := -1; |
| Section_Name : not null access constant String := Empty_Name'Access; |
| |
| procedure Simple_Callback |
| (Simple_Switch : String; |
| Separator : String; |
| Parameter : String; |
| Index : Integer); |
| -- Needs comments ??? |
| |
| procedure Do_Callback (Switch, Parameter : String; Index : Integer); |
| |
| ----------------- |
| -- Do_Callback -- |
| ----------------- |
| |
| procedure Do_Callback (Switch, Parameter : String; Index : Integer) is |
| begin |
| -- Do automatic handling when possible |
| |
| if Index /= -1 then |
| case Local_Config.Switches (Index).Typ is |
| when Switch_Untyped => |
| null; -- no automatic handling |
| |
| when Switch_Boolean => |
| Local_Config.Switches (Index).Boolean_Output.all := |
| Local_Config.Switches (Index).Boolean_Value; |
| return; |
| |
| when Switch_Integer => |
| begin |
| if Parameter = "" then |
| Local_Config.Switches (Index).Integer_Output.all := |
| Local_Config.Switches (Index).Integer_Default; |
| else |
| Local_Config.Switches (Index).Integer_Output.all := |
| Integer'Value (Parameter); |
| end if; |
| |
| exception |
| when Constraint_Error => |
| raise Invalid_Parameter |
| with "Expected integer parameter for '" |
| & Switch & "'"; |
| end; |
| |
| return; |
| |
| when Switch_String => |
| Free (Local_Config.Switches (Index).String_Output.all); |
| Local_Config.Switches (Index).String_Output.all := |
| new String'(Parameter); |
| return; |
| |
| when Switch_Callback => |
| Local_Config.Switches (Index).Callback (Switch, Parameter); |
| return; |
| end case; |
| end if; |
| |
| -- Otherwise calls the user callback if one was defined |
| |
| if Callback /= null then |
| Callback (Switch => Switch, |
| Parameter => Parameter, |
| Section => Section_Name.all); |
| end if; |
| end Do_Callback; |
| |
| procedure For_Each_Simple |
| is new For_Each_Simple_Switch (Simple_Callback); |
| |
| --------------------- |
| -- Simple_Callback -- |
| --------------------- |
| |
| procedure Simple_Callback |
| (Simple_Switch : String; |
| Separator : String; |
| Parameter : String; |
| Index : Integer) |
| is |
| pragma Unreferenced (Separator); |
| begin |
| Do_Callback (Switch => Simple_Switch, |
| Parameter => Parameter, |
| Index => Index); |
| end Simple_Callback; |
| |
| -- Start of processing for Getopt |
| |
| begin |
| -- We work with a local copy of Config, because Config can be null, for |
| -- example if Define_Switch was never called. We could modify Config |
| -- itself, but then we would have to make it into an 'in out' parameter, |
| -- which would be incompatible. |
| |
| if Local_Config = null then |
| Local_Config := new Command_Line_Configuration_Record; |
| end if; |
| |
| if Local_Config.Switches = null then |
| Local_Config.Switches := new Switch_Definitions (1 .. 0); |
| end if; |
| |
| -- Initialize sections |
| |
| if Local_Config.Sections = null then |
| Local_Config.Sections := new Argument_List'(1 .. 0 => null); |
| end if; |
| |
| Internal_Initialize_Option_Scan |
| (Parser => Parser, |
| Switch_Char => Parser.Switch_Character, |
| Stop_At_First_Non_Switch => Parser.Stop_At_First, |
| Section_Delimiters => Section_Delimiters (Local_Config)); |
| |
| Getopt_Switches := new String' |
| (Get_Switches (Local_Config, Parser.Switch_Character, Section_Name.all) |
| & " h -help"); |
| |
| -- Initialize output values for automatically handled switches |
| |
| for S in Local_Config.Switches'Range loop |
| case Local_Config.Switches (S).Typ is |
| when Switch_Untyped | Switch_Callback => |
| null; -- Nothing to do |
| |
| when Switch_Boolean => |
| Local_Config.Switches (S).Boolean_Output.all := |
| not Local_Config.Switches (S).Boolean_Value; |
| |
| when Switch_Integer => |
| Local_Config.Switches (S).Integer_Output.all := |
| Local_Config.Switches (S).Integer_Initial; |
| |
| when Switch_String => |
| if Local_Config.Switches (S).String_Output.all = null then |
| Local_Config.Switches (S).String_Output.all := |
| new String'(""); |
| end if; |
| end case; |
| end loop; |
| |
| -- For all sections, and all switches within those sections |
| |
| loop |
| C := Getopt (Switches => Getopt_Switches.all, |
| Concatenate => Concatenate, |
| Parser => Parser); |
| |
| if C = '*' then |
| -- Full_Switch already includes the leading '-' |
| |
| Do_Callback (Switch => Full_Switch (Parser), |
| Parameter => Parameter (Parser), |
| Index => -1); |
| |
| elsif C /= ASCII.NUL then |
| if Full_Switch (Parser) = "h" |
| or else |
| Full_Switch (Parser) = "-help" |
| then |
| Display_Help (Local_Config); |
| raise Exit_From_Command_Line; |
| end if; |
| |
| -- Do switch expansion if needed |
| |
| For_Each_Simple |
| (Local_Config, |
| Section => Section_Name.all, |
| Switch => Parser.Switch_Character & Full_Switch (Parser), |
| Parameter => Parameter (Parser)); |
| |
| else |
| if Current_Section = -1 then |
| Current_Section := Local_Config.Sections'First; |
| else |
| Current_Section := Current_Section + 1; |
| end if; |
| |
| exit when Current_Section > Local_Config.Sections'Last; |
| |
| Section_Name := Local_Config.Sections (Current_Section); |
| Goto_Section (Section_Name.all, Parser); |
| |
| Free (Getopt_Switches); |
| Getopt_Switches := new String' |
| (Get_Switches |
| (Local_Config, Parser.Switch_Character, Section_Name.all)); |
| end if; |
| end loop; |
| |
| Free (Getopt_Switches); |
| |
| exception |
| when Invalid_Switch => |
| Free (Getopt_Switches); |
| |
| -- Message inspired by "ls" on Unix |
| |
| if not Quiet then |
| Put_Line (Standard_Error, |
| Base_Name (Ada.Command_Line.Command_Name) |
| & ": unrecognized option '" |
| & Full_Switch (Parser) |
| & "'"); |
| Try_Help; |
| end if; |
| |
| raise; |
| |
| when others => |
| Free (Getopt_Switches); |
| raise; |
| end Getopt; |
| |
| ----------- |
| -- Build -- |
| ----------- |
| |
| procedure Build |
| (Line : in out Command_Line; |
| Args : out GNAT.OS_Lib.Argument_List_Access; |
| Expanded : Boolean := False; |
| Switch_Char : Character := '-') |
| is |
| Iter : Command_Line_Iterator; |
| Count : Natural := 0; |
| |
| begin |
| Start (Line, Iter, Expanded => Expanded); |
| while Has_More (Iter) loop |
| if Is_New_Section (Iter) then |
| Count := Count + 1; |
| end if; |
| |
| Count := Count + 1; |
| Next (Iter); |
| end loop; |
| |
| Args := new Argument_List (1 .. Count); |
| Count := Args'First; |
| |
| Start (Line, Iter, Expanded => Expanded); |
| while Has_More (Iter) loop |
| if Is_New_Section (Iter) then |
| Args (Count) := new String'(Switch_Char & Current_Section (Iter)); |
| Count := Count + 1; |
| end if; |
| |
| Args (Count) := new String'(Current_Switch (Iter) |
| & Current_Separator (Iter) |
| & Current_Parameter (Iter)); |
| Count := Count + 1; |
| Next (Iter); |
| end loop; |
| end Build; |
| |
| -------------- |
| -- Try_Help -- |
| -------------- |
| |
| -- Note: Any change to the message displayed should also be done in |
| -- gnatbind.adb that does not use this interface. |
| |
| procedure Try_Help is |
| begin |
| Put_Line |
| (Standard_Error, |
| "try """ & Base_Name (Ada.Command_Line.Command_Name, Suffix => ".exe") |
| & " --help"" for more information."); |
| end Try_Help; |
| |
| end GNAT.Command_Line; |