]> git.armaanb.net Git - gen-shell.git/commitdiff
added argument parsing with Sarge
authorArmaan Bhojwani <3fb650a9-b47e-4604-a282-1dd91953b2ee@anonaddy.me>
Mon, 2 Nov 2020 17:35:40 +0000 (12:35 -0500)
committerArmaan Bhojwani <3fb650a9-b47e-4604-a282-1dd91953b2ee@anonaddy.me>
Mon, 2 Nov 2020 17:35:40 +0000 (12:35 -0500)
17 files changed:
.gitmodules [new file with mode: 0644]
README.md
cmake.h
src/CMakeLists.txt
src/Sarge/.gitignore [new file with mode: 0644]
src/Sarge/LICENSE [new file with mode: 0644]
src/Sarge/README.md [new file with mode: 0644]
src/Sarge/ada/gps/sarge_test.gpr [new file with mode: 0644]
src/Sarge/ada/gps/src/sarge_test.adb [new file with mode: 0644]
src/Sarge/ada/src/sarge.adb [new file with mode: 0644]
src/Sarge/ada/src/sarge.ads [new file with mode: 0644]
src/Sarge/ada/test/sarge_test.adb [new file with mode: 0644]
src/Sarge/src/sarge.cpp [new file with mode: 0644]
src/Sarge/src/sarge.h [new file with mode: 0644]
src/Sarge/test/sarge_test.cpp [new file with mode: 0644]
src/main.cpp
src/prompt.cpp

diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..0b2eeb7
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "Sarge"]
+       path = src/Sarge
+       url = https://github.com/MayaPosch/Sarge
index 99eaebc2fcc2da53c82e422e08ee0af1786b115b..a7d8e301c5152da1e988e80c36ed00c6a21d2281 100644 (file)
--- a/README.md
+++ b/README.md
@@ -41,3 +41,5 @@ docker run -it -e CMD=<command-to-run> armaanb/gen-shell
 ```
 ## License
 Following suit from tasksh, gen-shell is MIT licensed by Armaan Bhojwani, 2020. Gen-shell is forked from tasksh, from which its codebase has been greatly reduced, although the majority of code remaining was written by the tasksh developers [(listed here)](https://github.com/GothenburgBitFactory/taskshell/blob/master/AUTHORS).
+
+Gen-shell uses [Sarge](https://github.com/MayaPosch/Sarge) for parsing command-line arguments. Sarge was written by Maya Posch and is BSD 3-Clause licensed
diff --git a/cmake.h b/cmake.h
index ac1bb92398f35eb4dca12c81bf0591054fa955d7..4873a94dfd634b3d4b7921c7765014b7048dd6ee 100644 (file)
--- a/cmake.h
+++ b/cmake.h
@@ -4,11 +4,11 @@
 #define PRODUCT_TASKSH 1
 
 /* Package information */
-#define PACKAGE           "gen-shell"
+#define PACKAGE           ""
 #define VERSION           ""
 #define PACKAGE_BUGREPORT ""
-#define PACKAGE_NAME      "gen-shell"
-#define PACKAGE_TARNAME   "gen-shell"
+#define PACKAGE_NAME      ""
+#define PACKAGE_TARNAME   ""
 #define PACKAGE_VERSION   ""
 #define PACKAGE_STRING    ""
 
index 2d983d9fed6c318be67ed0cb6a69ec09678af130..94584e780130bdcbcbba000f5d60c73f65b06eab 100644 (file)
@@ -4,7 +4,9 @@ include_directories (${CMAKE_SOURCE_DIR}
                      ${CMAKE_SOURCE_DIR}/src/libshared/src
                      ${GEN-SHELL_INCLUDE_DIRS})
 
-set (gen-shell_SRCS prompt.cpp)
+set (gen-shell_SRCS prompt.cpp
+                    Sarge/src/sarge.cpp)
+
 add_library (gen-shell STATIC ${gen-shell_SRCS})
 
 add_executable (gen-shell_executable main.cpp)
diff --git a/src/Sarge/.gitignore b/src/Sarge/.gitignore
new file mode 100644 (file)
index 0000000..f83e57b
--- /dev/null
@@ -0,0 +1,4 @@
+bin/
+log/
+obj/
+
diff --git a/src/Sarge/LICENSE b/src/Sarge/LICENSE
new file mode 100644 (file)
index 0000000..c57d17f
--- /dev/null
@@ -0,0 +1,24 @@
+Copyright (c) 2019, Maya Posch
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the <organization> nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/Sarge/README.md b/src/Sarge/README.md
new file mode 100644 (file)
index 0000000..7d69fc7
--- /dev/null
@@ -0,0 +1,207 @@
+# Sarge #\r
+\r
+Sarge is a simple and powerful command line argument parser, with the C++ version consisting out of <200 lines of well-commented C++ code, contained in a single class:\r
+\r
+\r
+       -------------------------------------------------------------------------------\r
+       Language                     files          blank        comment           code\r
+       -------------------------------------------------------------------------------\r
+       C++                              1             42             41            116\r
+       C/C++ Header                     1             12              7             35\r
+       -------------------------------------------------------------------------------\r
+       SUM:                             2             54             48            151\r
+       -------------------------------------------------------------------------------\r
+\r
\r
+\r
+Simply add the header file and source file to one's C++ project and use the class as in the project's test code:\r
+\r
+    #include "../src/sarge.h"\r
+       \r
+       #include <iostream>\r
+       \r
+       \r
+       int main(int argc, char** argv) {\r
+               Sarge sarge;\r
+               \r
+               sarge.setArgument("h", "help", "Get help.", false);\r
+               sarge.setArgument("k", "kittens", "K is for kittens. Everyone needs kittens in their life.", true);\r
+               sarge.setArgument("n", "number", "Gimme a number. Any number.", true);\r
+               sarge.setArgument("a", "apple", "Just an apple.", false);\r
+               sarge.setArgument("b", "bear", "Look, it's a bear.", false);\r
+               sarge.setArgument("", "snake", "Snakes only come in long form, there are no short snakes.", false);\r
+               sarge.setDescription("Sarge command line argument parsing testing app. For demonstration purposes and testing.");\r
+               sarge.setUsage("sarge_test <options>");\r
+               \r
+               if (!sarge.parseArguments(argc, argv)) {\r
+                       std::cerr << "Couldn't parse arguments..." << std::endl;\r
+                       return 1;\r
+               }\r
+               \r
+               std::cout << "Number of flags found: " << sarge.flagCount() << std::endl;\r
+               \r
+               if (sarge.exists("help")) {\r
+                       sarge.printHelp();\r
+               }\r
+               else {\r
+                       std::cout << "No help requested..." << std::endl;\r
+               }\r
+               \r
+               std::string kittens;\r
+               if (sarge.getFlag("kittens", kittens)) {\r
+                       std::cout << "Got kittens: " << kittens << std::endl;\r
+               }\r
+\r
+               std::string textarg;\r
+               if (sarge.getTextArgument(0, textarg)) {\r
+                       std::cout << "Got text argument: " << textarg << std::endl;\r
+               }\r
+               \r
+               return 0;\r
+       }\r
+\r
+Only dependencies are a reasonably modern C++ compiler, capable of supporting at least C++11 (STL datastructure improvements).\r
+\r
+## API ##\r
+\r
+       void setArgument(std::string arg_short, std::string arg_long, std::string desc, bool hasVal);\r
+       void setArguments(std::vector<Argument> args);\r
+       void setDescription(std::string desc);\r
+       void setUsage(std::string use);\r
+       bool parseArguments(int argc, char** argv);\r
+       bool getFlag(std::string arg_flag, std::string &arg_value);\r
+       bool exists(std::string arg_flag);\r
+       bool getTextArgument(uint32_t index, std::string &value);\r
+       void printHelp();\r
+       int flagCount();\r
+       std::string executableName();\r
+\r
+## Supported flag types ##\r
+\r
+Sarge supports both short and long options, prefixed by one or two dashes ('-') respectively. The short option can be left empty, which will only enable the long option.\r
+\r
+Short option: `-h`.\r
+\r
+Long option: `--help`.\r
+\r
+Options can optionally be followed by a value string. This has to be noted when registering the flag with Sarge in one's code. \r
+\r
+It's also supported to supply multiple short options combined to Sarge, e.g.: `-hnklm`. Important here is that options which require a value to follow them have to always be at the end of such a chain.\r
+\r
+String without flag associated with them are made available using the `getTextArgument()` method after parsing. These arguments are only allowed to exist after the flags section.\r
+\r
+## Compiling the test application ##\r
+\r
+A Makefile has been added to the root of the project. Simply execute `make` in the folder to compile the test binary into the `bin/` folder. Execute `bin/sarge_test` to get the following output:\r
+\r
+       # ./bin/sarge_test.exe -hk Mew\r
+       Number of flags found: 2\r
+       \r
+       Sarge command line argument parsing testing app. For demonstration purposes and testing.\r
+       \r
+       Usage:\r
+               sarge_test <options>\r
+       \r
+       Options:\r
+       -h, --help      Get help.\r
+       -k, --kittens   K is for kittens. Everyone needs kittens in their life.\r
+       -n, --number    Gimme a number. Any number.\r
+       -a, --apple     Just an apple.\r
+       -b, --bear      Look, it's a bear.\r
+           --snake     Snakes only come in long form, there are no short snakes.\r
+\r
+\r
+As you can see, no kittens were harmed in the production of this code :)\r
+\r
+# Ada version #\r
+\r
+The Ada version of Sarge (found in the `ada/` folder) is pretty much a straight port of the C++ version. It consists out of a single package (Sarge), with <200 lines of code. \r
+\r
+       -------------------------------------------------------------------------------\r
+       Language                     files          blank        comment           code\r
+       -------------------------------------------------------------------------------\r
+       Ada                              2             58             44            197\r
+       -------------------------------------------------------------------------------\r
+       SUM:                             2             58             44            197\r
+       -------------------------------------------------------------------------------\r
+\r
+\r
+Its biggest limitation compared to the C++ version at this point is that one cannot use multiple instances of Sarge since the relevant data structures are part of the package. This should not pose any issues in the average usage scenario, however.\r
+\r
+\r
+## API ##\r
+\r
+       procedure setArgument(arg_short: in Unbounded_String; arg_long: in Unbounded_String; desc: in Unbounded_String; hasVal: in boolean);\r
+       procedure setDescription(desc: in Unbounded_String);\r
+       procedure setUsage(usage: in Unbounded_String);\r
+       function parseArguments return boolean;\r
+       function getFlag(arg_flag: in Unbounded_String; arg_value: out Unbounded_String) return boolean;\r
+       function exists(arg_flag: in Unbounded_String) return boolean;\r
+       function getTextArgument(index: in Integer; value: out Unbounded_String) return boolean;\r
+       procedure printHelp;\r
+       function flagCount return integer;\r
+       function executableName return Unbounded_String;\r
+\r
+## Example ##\r
+\r
+The test application has also been ported from the C++ version, showing the use of the package:\r
+\r
+       with Sarge;\r
+       with Ada.Text_IO;\r
+       use Ada.Text_IO;\r
+       with Ada.Strings.Unbounded;\r
+       use Ada.Strings.Unbounded;\r
+       with Ada.Strings.Unbounded.Text_IO;\r
+       use Ada.Strings.Unbounded.Text_IO;\r
+\r
+\r
+       procedure Sarge_Test is\r
+\r
+       function "+"(S : in String) return Unbounded_String renames Ada.Strings.Unbounded.To_Unbounded_String;\r
+\r
+       kittens: Unbounded_String;\r
+       number: Unbounded_String;\r
+\r
+       begin\r
+          Sarge.setArgument(+"h", +"help", +"Get help.", False);\r
+               Sarge.setArgument(+"k", +"kittens", +"K is for kittens. Everyone needs kittens in their life.", True);\r
+               Sarge.setArgument(+"n", +"number", +"Gimme a number. Any number.", True);\r
+               Sarge.setArgument(+"a", +"apple", +"Just an apple.", False);\r
+               Sarge.setArgument(+"b", +"bear", +"Look, it's a bear.", False);\r
+               Sarge.setArgument(+"", +"snake", +"Snakes only come in long form, there are no short snakes.", False);\r
+                       Sarge.setDescription(+"Sarge command line argument parsing testing app. For demonstration purposes and testing.");\r
+               Sarge.setUsage(+"sarge_test <options>");\r
+\r
+               if Sarge.parseArguments /= True then\r
+                       put_line("Couldn't parse arguments...");\r
+                       return;\r
+               end if;\r
+\r
+               put_line("Number of flags found: " & Sarge.flagCount'Image);\r
+\r
+               if Sarge.exists(+"help") /= False then\r
+                       Sarge.printHelp;\r
+               else\r
+                       put_line("No help requested...");\r
+               end if;\r
+\r
+               -- Read out Kittens and Number.\r
+               if Sarge.getFlag(+"kittens", kittens) = True then\r
+                       put_line("Got kittens: " & kittens);\r
+               end if;\r
+\r
+               if Sarge.getFlag(+"number", number) = True then\r
+                       put_line("Got number: " & number);\r
+               end if;\r
+\r
+               if Sarge.getTextArgument(0, textarg) = True then\r
+                       put_line("Got text argument: " & textarg);\r
+               end if;\r
+\r
+       end Sarge_Test; \r
+\r
+The Makefile in the `ada` folder should be used when compiling the test application. The Gnat Programming Studio project file is currently not maintained. \r
+\r
+The Sarge package is found in the `ada/src` folder. One can use it directly from there by including it one's project, or copying it into the project's source tree, depending on one's requirements.\r
+\r
+\r
diff --git a/src/Sarge/ada/gps/sarge_test.gpr b/src/Sarge/ada/gps/sarge_test.gpr
new file mode 100644 (file)
index 0000000..3943f3f
--- /dev/null
@@ -0,0 +1,8 @@
+project Sarge_Test is\r
+\r
+   for Source_Dirs use ("src", "../src");\r
+   for Object_Dir use "obj";\r
+   for Main use ("sarge_test.adb");\r
+\r
+end Sarge_Test;\r
+\r
diff --git a/src/Sarge/ada/gps/src/sarge_test.adb b/src/Sarge/ada/gps/src/sarge_test.adb
new file mode 100644 (file)
index 0000000..022ad0a
--- /dev/null
@@ -0,0 +1,62 @@
+--     sarge_test.adb - Implementation file for the Sarge command line argument parser test.
+
+--     Revision 0
+
+--     Features:
+--                     -
+
+--     Notes:
+--                     -
+
+--     2019/04/10, Maya Posch
+
+
+with Sarge;
+with Ada.Text_IO;
+use Ada.Text_IO;
+with Ada.Strings.Unbounded;
+use Ada.Strings.Unbounded;
+with Ada.Strings.Unbounded.Text_IO;
+use Ada.Strings.Unbounded.Text_IO;
+
+
+procedure Sarge_Test is
+
+function "+"(S : in String) return Unbounded_String renames Ada.Strings.Unbounded.To_Unbounded_String;
+
+kittens: Unbounded_String;
+number: Unbounded_String;
+
+begin
+   -- Create Sarge instance, set stuff, parse stuff.
+   Sarge.setArgument(+"h", +"help", +"Get help.", False);
+   Sarge.setArgument(+"k", +"kittens", +"K is for kittens. Everyone needs kittens in their life.", True);
+   Sarge.setArgument(+"n", +"number", +"Gimme a number. Any number.", True);
+   Sarge.setArgument(+"a", +"apple", +"Just an apple.", False);
+   Sarge.setArgument(+"b", +"bear", +"Look, it's a bear.", False);
+   Sarge.setDescription(+"Sarge command line argument parsing testing app. For demonstration purposes and testing.");
+   Sarge.setUsage(+"sarge_test <options>");
+
+   if Sarge.parseArguments /= True then
+      put_line("Couldn't parse arguments...");
+      return;
+   end if;
+
+   put_line("Number of flags found: " & Sarge.flagCount'Image);
+
+   if Sarge.exists(+"help") /= False then
+      Sarge.printHelp;
+   else
+      put_line("No help requested...");
+   end if;
+
+   -- Read out Kittens and Number.
+   if Sarge.getFlag(+"kittens", kittens) = True then
+       put_line("Got kittens: " & kittens);
+   end if;
+
+   if Sarge.getFlag(+"number", number) = True then
+       put_line("Got number: " & number);
+   end if;
+
+end Sarge_Test;
diff --git a/src/Sarge/ada/src/sarge.adb b/src/Sarge/ada/src/sarge.adb
new file mode 100644 (file)
index 0000000..32728a1
--- /dev/null
@@ -0,0 +1,244 @@
+--     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;
+
diff --git a/src/Sarge/ada/src/sarge.ads b/src/Sarge/ada/src/sarge.ads
new file mode 100644 (file)
index 0000000..8ecb484
--- /dev/null
@@ -0,0 +1,55 @@
+--     sarge.ads - Specification file for the Sarge command line argument parser project.
+       
+--     Revision 0
+       
+--     Notes:
+--                     -
+                        
+--     2019/04/10, Maya Posch
+
+
+with Ada.Strings.Unbounded;
+use Ada.Strings.Unbounded;
+with Ada.Containers.Vectors;
+with Ada.Containers.Indefinite_Ordered_Maps;
+use Ada.Containers;
+
+
+package Sarge is 
+       type Argument is record
+               arg_short: aliased Unbounded_String;
+               arg_long: aliased Unbounded_String;
+               description: aliased Unbounded_String;
+               hasValue: aliased boolean := False;
+               value: aliased Unbounded_String;
+               parsed: aliased boolean := False;
+       end record;
+       
+       type Argument_Access is access all Argument;
+       
+       procedure setArgument(arg_short: in Unbounded_String; arg_long: in Unbounded_String; desc: in Unbounded_String; hasVal: in boolean);
+       procedure setDescription(desc: in Unbounded_String);
+       procedure setUsage(usage: in Unbounded_String);
+       function parseArguments return boolean;
+       function getFlag(arg_flag: in Unbounded_String; arg_value: out Unbounded_String) return boolean;
+       function exists(arg_flag: in Unbounded_String) return boolean;
+       function getTextArgument(index: in Integer; value: out Unbounded_String) return boolean;
+       procedure printHelp;
+       function flagCount return integer;
+       function executableName return Unbounded_String;
+       
+private
+        function "+"(S : in String) return Unbounded_String renames Ada.Strings.Unbounded.To_Unbounded_String;
+       package arg_vector is new Vectors(Natural, Argument);
+       args: arg_vector.vector;
+       --package argNames_map is new Indefinite_Ordered_Maps(Unbounded_String, Argument_Access);
+       package argNames_map is new Indefinite_Ordered_Maps(Unbounded_String, Natural);
+       argNames: argNames_map.map;
+       parsed: boolean;
+       flagCounter: Integer := 0;
+       execName: Unbounded_String;
+       description: Unbounded_String;
+       usageStr: Unbounded_String;
+       package tArgVector is new Vectors(Natural, Unbounded_String);
+       textArguments: tArgVector.vector;
+end Sarge;
diff --git a/src/Sarge/ada/test/sarge_test.adb b/src/Sarge/ada/test/sarge_test.adb
new file mode 100644 (file)
index 0000000..5a9251d
--- /dev/null
@@ -0,0 +1,68 @@
+--     sarge_test.adb - Implementation file for the Sarge command line argument parser test.
+
+--     Revision 0
+
+--     Features:
+--                     -
+
+--     Notes:
+--                     -
+
+--     2019/04/10, Maya Posch
+
+
+with Sarge;
+with Ada.Text_IO;
+use Ada.Text_IO;
+with Ada.Strings.Unbounded;
+use Ada.Strings.Unbounded;
+with Ada.Strings.Unbounded.Text_IO;
+use Ada.Strings.Unbounded.Text_IO;
+
+
+procedure Sarge_Test is
+
+function "+"(S : in String) return Unbounded_String renames Ada.Strings.Unbounded.To_Unbounded_String;
+
+kittens: Unbounded_String;
+number: Unbounded_String;
+textarg: Unbounded_String;
+
+begin
+       -- Create Sarge instance, set stuff, parse stuff.
+       Sarge.setArgument(+"h", +"help", +"Get help.", False);
+       Sarge.setArgument(+"k", +"kittens", +"K is for kittens. Everyone needs kittens in their life.", True);
+       Sarge.setArgument(+"n", +"number", +"Gimme a number. Any number.", True);
+       Sarge.setArgument(+"a", +"apple", +"Just an apple.", False);
+       Sarge.setArgument(+"b", +"bear", +"Look, it's a bear.", False);
+       Sarge.setArgument(+"", +"snake", +"Snakes only come in long form, there are no short snakes.", False);
+       Sarge.setDescription(+"Sarge command line argument parsing testing app. For demonstration purposes and testing.");
+       Sarge.setUsage(+"sarge_test <options>");
+
+       if Sarge.parseArguments /= True then
+               put_line("Couldn't parse arguments...");
+               return;
+       end if;
+
+       put_line("Number of flags found: " & Sarge.flagCount'Image);
+
+       if Sarge.exists(+"help") /= False then
+               Sarge.printHelp;
+       else
+               put_line("No help requested...");
+       end if;
+
+       -- Read out Kittens and Number.
+       if Sarge.getFlag(+"kittens", kittens) = True then
+               put_line("Got kittens: " & kittens);
+       end if;
+
+       if Sarge.getFlag(+"number", number) = True then
+               put_line("Got number: " & number);
+       end if;
+
+       if Sarge.getTextArgument(0, textarg) = True then
+               put_line("Got text argument: " & textarg);
+       end if;
+
+end Sarge_Test;
diff --git a/src/Sarge/src/sarge.cpp b/src/Sarge/src/sarge.cpp
new file mode 100644 (file)
index 0000000..eb7d14f
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+       sarge.cpp - Implementation file for the Sarge command line argument parser project.
+       
+       Revision 0
+       
+       Features:
+                       - 
+       
+       Notes:
+                       -
+                        
+       2019/03/16, Maya Posch
+       
+*/
+
+
+#include "sarge.h"
+
+#include <iostream>
+
+
+// --- SET ARGUMENT ---
+void Sarge::setArgument(std::string arg_short, std::string arg_long, std::string desc, bool hasVal) {
+       std::unique_ptr<Argument> arg(new Argument);
+       arg->arg_short = arg_short;
+       arg->arg_long = arg_long;
+       arg->description = desc;
+       arg->hasValue = hasVal;
+       args.push_back(std::move(arg));
+       
+       // Set up links.
+       if (!arg_short.empty()) {
+               argNames.insert(std::pair<std::string, Argument*>(arg_short, args.back().get()));
+       }
+       
+       if (!arg_long.empty()) {
+               argNames.insert(std::pair<std::string, Argument*>(arg_long, args.back().get()));
+       }
+}
+       
+
+// --- SET ARGUMENTS ---
+void Sarge::setArguments(std::vector<Argument> args) {
+       for (Argument a : args) {
+               setArgument(a.arg_short, a.arg_long, a.description, a.hasValue);
+       }
+}
+
+
+// --- PARSE ARGUMENTS ---
+bool Sarge::parseArguments(int argc, char** argv) {
+       // The first argument is the name of the executable. After it we loop through the remaining
+       // arguments, linking flags and values.
+       execName = std::string(argv[0]);
+       bool expectValue = false;
+       std::map<std::string, Argument*>::const_iterator flag_it;
+       for (int i = 1; i < argc; ++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).
+               std::string entry(argv[i]);
+               
+               if (expectValue) {
+                       // Copy value.
+                       flag_it->second->value = entry;                 
+                       expectValue = false;
+               }
+               else if (entry.compare(0, 1, "-") == 0) {
+                       if (textArguments.size() > 0) { 
+                               std::cerr << "Flags not allowed after text arguments." << std::endl; 
+                       }
+                       
+                       // Parse flag.
+                       // First check for the long form.
+                       if (entry.compare(0, 2, "--") == 0) {
+                               // Long form of flag.
+                               entry.erase(0, 2); // Erase the double dash since we no longer need it.
+                       
+                               flag_it = argNames.find(entry);
+                               if (flag_it == argNames.end()) {
+                                       // Flag wasn't found. Abort.
+                                       std::cerr << "Long flag " << entry << " wasn't found." << std::endl;
+                                       return false;
+                               }
+                               
+                               // Mark as found.
+                               flag_it->second->parsed = true;
+                               ++flagCounter;
+                               
+                               if (flag_it->second->hasValue) {
+                                       expectValue = true; // Next argument has to be a value string.
+                               }
+                       }
+                       else {
+                               // Parse short form flag. Parse all of them sequentially. Only the last one
+                               // is allowed to have an additional value following it.
+                               entry.erase(0, 1); // Erase the dash.                           
+                               for (int i = 0; i < entry.length(); ++i) {
+                                       std::string k(&(entry[i]), 1);
+                                       flag_it = argNames.find(k);
+                                       if (flag_it == argNames.end()) {
+                                               // Flag wasn't found. Abort.
+                                               std::cerr << "Short flag " << k << " wasn't found." << std::endl;
+                                               return false;
+                                       }
+                                       
+                                       // Mark as found.
+                                       flag_it->second->parsed = true;
+                                       ++flagCounter;
+                                       
+                                       if (flag_it->second->hasValue) {
+                                               if (i != (entry.length() - 1)) {
+                                                       // Flag isn't at end, thus cannot have value.
+                                                       std::cerr << "Flag " << k << " needs to be followed by a value string."
+                                                               << std::endl;
+                                                       return false;
+                                               } else {
+                                                       expectValue = true; // Next argument has to be a value string.
+                                               }
+                                       }
+                               }
+                       }
+               }
+               else {
+                       // Add to text argument vector.
+                       textArguments.push_back(entry);
+               }
+       }
+       
+       parsed = true;
+       
+       return true;
+}
+
+
+// --- GET FLAG ---
+// Returns whether the flag was found, along with the value if relevant.
+bool Sarge::getFlag(std::string arg_flag, std::string &arg_value) {
+       if (!parsed) { return false; }
+       
+       std::map<std::string, Argument*>::const_iterator it = argNames.find(arg_flag);
+       if (it == argNames.end()) { return false; }
+       if (!it->second->parsed) { return false; }
+       
+       if (it->second->hasValue) { arg_value = it->second->value; }
+       
+       return true;
+}
+
+
+// --- EXISTS ---
+// Returns whether the flag was found.
+bool Sarge::exists(std::string arg_flag) {
+       if (!parsed) { return false; }
+       
+       std::map<std::string, Argument*>::const_iterator it = argNames.find(arg_flag);
+       if (it == argNames.end()) { return false; }     
+       if (!it->second->parsed) { return false; }
+       
+       return true;
+}
+
+
+// --- GET TEXT ARGUMENT ---
+// Updates the value parameter with the text argument (unbound value) if found.
+// Index starts at 0.
+// Returns true if found, else false.
+bool Sarge::getTextArgument(uint32_t index, std::string &value) {
+       if (index < textArguments.size()) { value = textArguments.at(index); return true; }
+       
+       return false;
+}
+
+
+// --- PRINT HELP ---
+// Prints out the application description, usage and available options.
+void Sarge::printHelp() {
+       std::cout << std::endl << description << std::endl;
+       std::cout << std::endl << "Usage:" << std::endl;
+       std::cout << "\t" << usage << std::endl;
+       std::cout << std::endl;
+       std::cout << "Options: " << std::endl;
+       
+       // Determine whitespace needed between arg_long and description.
+       int count = 1; 
+       std::vector<std::unique_ptr<Argument> >::const_iterator it;
+       for (it = args.cbegin(); it != args.cend(); ++it) {
+               if ((*it)->arg_long.size() > count) { 
+                       count = (*it)->arg_long.size();
+               }
+       }
+       
+       count += 3; // Number of actual spaces between the longest arg_long and description.
+
+       // Print out the options.
+       for (it = args.cbegin(); it != args.cend(); ++it) {
+               std::cout << ((*it)->arg_short.empty() ? "    " : "-" + (*it)->arg_short + ", ") 
+                                                                                                                                       << "--" << (*it)->arg_long;
+               std::cout << std::string(count - (*it)->arg_long.size(), ' ') << (*it)->description 
+                                                                                                                                       << std::endl;
+       }
+}
diff --git a/src/Sarge/src/sarge.h b/src/Sarge/src/sarge.h
new file mode 100644 (file)
index 0000000..ab24c72
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+       sarge.h - Header file for the Sarge command line argument parser project.
+       
+       Revision 0
+       
+       Notes:
+                       -
+                        
+       2019/03/16, Maya Posch
+       
+*/
+
+
+#include <string>
+#include <vector>
+#include <map>
+#include <memory>
+
+
+struct Argument {
+       Argument() : hasValue(false), parsed(false) { /* */ }
+       std::string arg_short;
+       std::string arg_long;
+       std::string description;
+       bool hasValue;
+       std::string value;
+       bool parsed;
+};
+
+
+
+class Sarge {
+       std::vector<std::unique_ptr<Argument> > args;
+       std::map<std::string, Argument*> argNames;
+       bool parsed = false;
+       int flagCounter = 0;
+       std::string execName;
+       std::string description;
+       std::string usage;
+       std::vector<std::string> textArguments;
+       
+public:
+       void setArgument(std::string arg_short, std::string arg_long, std::string desc, bool hasVal);
+       void setArguments(std::vector<Argument> args);
+       void setDescription(std::string desc) { this->description = desc; }
+       void setUsage(std::string use) { this->usage = use; }
+       bool parseArguments(int argc, char** argv);
+       bool getFlag(std::string arg_flag, std::string &arg_value);
+       bool exists(std::string arg_flag);
+       bool getTextArgument(uint32_t index, std::string &value);
+       void printHelp();
+       int flagCount() { return flagCounter; }
+       std::string executableName() { return execName; }
+};
diff --git a/src/Sarge/test/sarge_test.cpp b/src/Sarge/test/sarge_test.cpp
new file mode 100644 (file)
index 0000000..c1d6989
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+       sarge_test.cpp - Implementation file for the Sarge command line argument parser test.
+       
+       Revision 0
+       
+       Features:
+                       - 
+       
+       Notes:
+                       -
+                        
+       2019/03/16, Maya Posch
+       
+*/
+
+
+#include "../src/sarge.h"
+
+#include <iostream>
+
+
+int main(int argc, char** argv) {
+       // Create Sarge instance, set stuff, parse stuff.
+       Sarge sarge;
+       
+       sarge.setArgument("h", "help", "Get help.", false);
+       sarge.setArgument("k", "kittens", "K is for kittens. Everyone needs kittens in their life.", true);
+       sarge.setArgument("n", "number", "Gimme a number. Any number.", true);
+       sarge.setArgument("a", "apple", "Just an apple.", false);
+       sarge.setArgument("b", "bear", "Look, it's a bear.", false);
+       sarge.setArgument("", "snake", "Snakes only come in long form, there are no short snakes.", false);
+       sarge.setDescription("Sarge command line argument parsing testing app. For demonstration purposes and testing.");
+       sarge.setUsage("sarge_test <options>");
+       
+       if (!sarge.parseArguments(argc, argv)) {
+               std::cerr << "Couldn't parse arguments..." << std::endl;
+               return 1;
+       }
+       
+       std::cout << "Number of flags found: " << sarge.flagCount() << std::endl;
+       
+       if (sarge.exists("help")) {
+               sarge.printHelp();
+       }
+       else {
+               std::cout << "No help requested..." << std::endl;
+       }
+       
+       std::string kittens;
+       if (sarge.getFlag("kittens", kittens)) {
+               std::cout << "Got kittens: " << kittens << std::endl;
+       }
+       
+       std::string number;
+       if (sarge.getFlag("number", number)) {
+               std::cout << "Got number: " << number << std::endl;
+       }
+       
+       std::string textarg;
+       if (sarge.getTextArgument(0, textarg)) {
+               std::cout << "Got text argument: " << textarg << std::endl;
+       }
+       
+       return 0;
+}
+
index 703f46ace9cfc527e844feb5023a160beacbfb9a..a7e95fe751bff9337248f4a3762fde2933c175f0 100644 (file)
@@ -26,6 +26,7 @@
 
 #include <cmake.h>
 #include <iostream>
+#include <Sarge/src/sarge.h>
 #include <stdlib.h>
 
 #ifdef HAVE_READLINE
@@ -33,6 +34,8 @@
 #include <readline/history.h>
 #endif
 
+////////////////////////////////////////////////////////////////////////////////
+
 using namespace std;
 std::string promptCompose();
 
@@ -67,44 +70,54 @@ const std::string getResponse(const std::string & prompt) {
   return response;
 }
 
+////////////////////////////////////////////////////////////////////////////////
+
 int main(int argc, char** argv)
 {
-  int status = 0;
 
-  string root_cmd;
-  for (int i = 1; i < argc; ++i) {
-    root_cmd +=argv[i];
-    root_cmd += " ";
-  }
+  // Command line arguments
+  Sarge sarge;
+
+       sarge.setArgument("h", "help", "Get help.", false);
+       sarge.setArgument("c", "cmd", "Command to execute before entering the shell", true);
+       sarge.setDescription("Make a shell from any command");
+       sarge.setUsage("gen-shell <options> <command>");
 
-  while (status == 0) {
+       if (!sarge.parseArguments(argc, argv)) {
+               std::cerr << "Couldn't parse arguments..." << std::endl;
+               return 1;
+       }
+
+       if (sarge.exists("help")) {
+               sarge.printHelp();
+    return 0;
+       }
+
+  string arg_cmd;
+  sarge.getFlag("cmd", arg_cmd);
+  arg_cmd += " ";
+
+  // Main program
+  while (true) {
     // Compose the prompt.
     auto prompt = promptCompose();
 
     // Display prompt, get input.
     auto command = getResponse(prompt);
 
-    int status = 0;
     if (command != "")
     {
       // Dispatch command.
-      if (command == "<EOF>")                      status = 1;
+      if (command == "<EOF>")
+      {
+        return 0;
+      }
       else if (command != "")
       {
-        if (argc == 0) {
-          string whole_command=command;
-          std::cout << "[" << command << "]\n";
-          system (command.c_str ());
-          }
-        else {
-          string whole_command = root_cmd + " " + command;
-          // std::cout << "[" << whole_command << "]\n";
-          system (whole_command.c_str ());
-        }
+        string whole_command = arg_cmd + command;
+        system (whole_command.c_str ());
       }
     }
-    if (status == 1)
-      return 0;
   }
 }
 
index 5b2e0487ae0284f95d7beccfcabbd2504c7e35e7..493425a9a6b03854932b0f7f2b23f67a0dad0b41 100644 (file)
 #include <vector>
 #include <string>
 
-static std::vector <std::string> contexts;
+////////////////////////////////////////////////////////////////////////////////
 
+static std::vector <std::string> contexts;
 std::string composeContexts (bool pretty = false);
 
-////////////////////////////////////////////////////////////////////////////////
 int promptClear ()
 {
   contexts.clear ();
   return 0;
 }
 
-////////////////////////////////////////////////////////////////////////////////
 int promptRemove ()
 {
   if (contexts.size ())
@@ -49,6 +48,7 @@ int promptRemove ()
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+
 int promptAdd (const std::string& context)
 {
   contexts.push_back (context);
@@ -56,6 +56,7 @@ int promptAdd (const std::string& context)
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+
 std::string promptCompose ()
 {
   return "% ";