-- sarge.adb - Implementation file for the Sarge command line argument parser project. -- Revision 0 -- Features: -- - -- Notes: -- - -- 2019/04/10, Maya Posch with Ada.Command_Line; with Ada.Text_IO; use Ada.Text_IO; with Ada.Strings.Unbounded.Text_IO; use Ada.Strings.Unbounded.Text_IO; package body Sarge is --- SET ARGUMENT --- procedure setArgument(arg_short: in Unbounded_String; arg_long: in Unbounded_String; desc: in Unbounded_String; hasVal: in boolean) is arg: aliased Argument := (arg_short => arg_short, arg_long => arg_long, description => desc, hasValue => hasVal, value => +"", parsed => False); begin args.append(arg); -- Set up links. if length(arg_short) > 0 then argNames.include(arg_short, args.Last_Index); end if; if length(arg_long) > 0 then argNames.include(arg_long, args.Last_Index); end if; end setArgument; --- SET DESCRIPTION --- procedure setDescription(desc: in Unbounded_String) is begin description := desc; end setDescription; --- SET USAGE --- procedure setUsage(usage: in Unbounded_String) is begin usageStr := usage; end setUsage; --- PARSE ARGUMENTS --- function parseArguments return boolean is flag_it: argNames_map.Cursor; expectValue: boolean := False; arg: Unbounded_String; short_arg: Unbounded_String; begin -- execName := +Ada.Command_Line.command_name; for arg_i in 1..Ada.Command_Line.argument_count loop arg := +Ada.Command_Line.Argument(arg_i); -- Each flag will start with a '-' character. Multiple flags can be joined together in -- the same string if they're the short form flag type (one character per flag). if expectValue = True then -- Copy value. args.Reference(argNames_map.Element(flag_it)).value := arg; expectValue := False; elsif Ada.Strings.Unbounded.Slice(arg, 1, 1) = "-" then -- Parse flag. -- First check for the long form. if Ada.Strings.Unbounded.Slice(arg, 1, 2) = "--" then -- Long form of the flag. -- First delete the preceding dashes. arg := Ada.Strings.Unbounded.Delete(arg, 1, 2); if not argNames.contains(arg) then -- Flag wasn't found. Abort. Ada.Strings.Unbounded.Text_IO.put_line("Long flag " & arg & " wasn't found"); return False; end if; -- Mark as found. flag_it := argNames.find(arg); args(argNames_map.Element(flag_it)).parsed := True; flagCounter := flagCounter + 1; if args(argNames_map.Element(flag_it)).hasValue = True then expectValue := True; end if; else -- Parse short form flag. Parse all of them sequentially. Only the last one -- is allowed to have an additional value following it. -- First delete the preceding dash. arg := Ada.Strings.Unbounded.Delete(arg, 1, 1); for i in 1 .. Ada.Strings.Unbounded.Length(arg) loop Ada.Strings.Unbounded.Append(short_arg, Ada.Strings.Unbounded.Element(arg, i)); if argNames_map.Contains(argNames, short_arg) /= True then -- Flag wasn't found. Abort. put_line("Short flag " & short_arg & " wasn't found."); return False; end if; flag_it := argNames.find(short_arg); -- Mark as found. args(argNames_map.Element(flag_it)).parsed := True; flagCounter := flagCounter + 1; if args(argNames_map.Element(flag_it)).hasValue = True then if i /= (Ada.Strings.Unbounded.Length(arg)) then -- Flag isn't at end, thus cannot have value. put_line("Flag " & short_arg & " needs to be followed by a value string."); return False; else expectValue := True; end if; end if; Ada.Strings.Unbounded.Delete(short_arg, 1, 1); end loop; end if; else -- Add to text argument vector. textArguments.append(arg); end if; end loop; parsed := True; return True; end parseArguments; --- GET FLAG --- function getFlag(arg_flag: in Unbounded_String; arg_value: out Unbounded_String) return boolean is flag_it: argNames_map.Cursor; use argNames_map; begin if parsed /= True then return False; end if; flag_it := argNames.find(arg_flag); if flag_it = argNames_map.No_Element then return False; elsif args(argNames_map.Element(flag_it)).parsed /= True then return False; end if; if args(argNames_map.Element(flag_it)).hasValue = True then arg_value := args(argNames_map.Element(flag_it)).value; end if; return True; end getFlag; --- EXISTS --- function exists(arg_flag: in Unbounded_String) return boolean is flag_it: argNames_map.Cursor; use argNames_map; begin if parsed /= True then return False; end if; flag_it := argNames.find(arg_flag); if flag_it = argNames_map.No_Element then return False; elsif args(argNames_map.Element(flag_it)).parsed /= True then return False; end if; return True; end exists; --- GET TEXT ARGUMENT --- function getTextArgument(index: in Integer; value: out Unbounded_String) return boolean is begin if index < Integer(tArgVector.length(textArguments)) then value := textArguments(index); return True; end if; return False; end getTextArgument; --- PRINT HELP --- procedure printHelp is count: Integer := 1; spaceCnt: Integer; begin put_line(""); put_line(description); put_line("Usage:"); put_line(usageStr); put_line(""); put_line("Options:"); -- Determine whitespace needed between arg_long and description. for flag in args.Iterate loop if Integer(Ada.Strings.Unbounded.length(args(flag).arg_long)) > count then count := Integer(Ada.Strings.Unbounded.length(args(flag).arg_long)); end if; end loop; count := count + 3; -- Number of actual spaces between the longest arg_long and description. -- Print out the options. for opt in args.Iterate loop --spaceStr := Unbound_String(count - Ada.Strings.Unbounded.length(args(opt).arg_long) spaceCnt := (count - Integer(Ada.Strings.Unbounded.length(args(opt).arg_long))); if Ada.Strings.Unbounded.length(args(opt).arg_short) < 1 then Ada.Strings.Unbounded.Text_IO.put_line(" " & args(opt).arg_short & "--" & args(opt).arg_long & spaceCnt * " " & args(opt).description); else Ada.Strings.Unbounded.Text_IO.put_line("-" & args(opt).arg_short & ", --" & args(opt).arg_long & spaceCnt * " " & args(opt).description); end if; end loop; end printHelp; --- FLAG COUNT --- function flagCount return integer is begin return flagCounter; end flagCount; --- EXECUTABLE NAME --- function executableName return Unbounded_String is begin return execName; end executableName; end Sarge;