From ba1e620ad00400cebe2a63ac27ef74eea1e5d632 Mon Sep 17 00:00:00 2001 From: Regan-Koopmans Date: Sat, 28 Oct 2017 10:38:10 +0200 Subject: [PATCH 1/3] Added example.cpp, makefile and updated README.md --- .gitignore | 1 + README.md | 2 +- argparse.hpp | 11 +++++++++-- example.cpp | 22 ++++++++++++++++++++++ makefile | 5 +++++ 5 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 example.cpp create mode 100644 makefile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96236f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +example \ No newline at end of file diff --git a/README.md b/README.md index eb717d7..b7d3c9e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ArgumentParser ============== -A slimline C++ class for parsing command-line arguments, with an interface similar to python's class of the same name. +A slimline C++ class for parsing command-line arguments, with an interface similar to Python's class of the same name. Usage ----- diff --git a/argparse.hpp b/argparse.hpp index e62ea15..62796ee 100644 --- a/argparse.hpp +++ b/argparse.hpp @@ -1,6 +1,7 @@ #ifndef ARGPARSE_HPP_ #define ARGPARSE_HPP_ +#include #if __cplusplus >= 201103L #include typedef std::unordered_map IndexMap; @@ -8,7 +9,6 @@ typedef std::unordered_map IndexMap; #include typedef std::map IndexMap; #endif -#include #include #include #include @@ -86,7 +86,10 @@ class ArgumentParser { } template ValueType& castTo() { - if (!toPtr()) throw std::bad_cast(); + if (!toPtr()) { + std::cout << "oh no!" << std::endl; + throw std::bad_cast(); + } return *toPtr(); } template @@ -277,6 +280,10 @@ class ArgumentParser { in < argv.end() - nfinal; ++in) { String active_name = active.canonicalName(); String el = *in; + std::cout << el << std::endl; + if (el.compare("-h") == 0) { + usage(); + } // check if the element is a key if (index_.count(el) == 0) { // input diff --git a/example.cpp b/example.cpp new file mode 100644 index 0000000..5df414c --- /dev/null +++ b/example.cpp @@ -0,0 +1,22 @@ +#include "argparse.hpp" + +using namespace std; + +int main(int argc, const char** argv) { + + // make a new ArgumentParser + ArgumentParser parser; + parser.appName("Testing"); + + // add some arguments to search for + parser.addArgument("-c", "--cactus", 1); + + // parse the command-line arguments - throws if invalid format + parser.parse(argc, argv); + + // // if we get here, the configuration is valid + // string cactus = parser.retrieve("cactus"); + // std::cout << cactus << std::endl; + // cout << typeid(cactus).name() << endl; + return 0; +} \ No newline at end of file diff --git a/makefile b/makefile new file mode 100644 index 0000000..e296aa0 --- /dev/null +++ b/makefile @@ -0,0 +1,5 @@ +make: + g++ example.cpp -o example + +run: + ./example -h \ No newline at end of file From c9d07be40ffffc776878e141f76932c4261ddf16 Mon Sep 17 00:00:00 2001 From: Regan-Koopmans Date: Sat, 28 Oct 2017 10:49:57 +0200 Subject: [PATCH 2/3] Increased code readability and fixed argument error on -h input --- argparse.hpp | 63 ++++++++++++++++++++++++++++++++++++++++------------ example.cpp | 2 +- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/argparse.hpp b/argparse.hpp index 62796ee..53db99b 100644 --- a/argparse.hpp +++ b/argparse.hpp @@ -55,28 +55,31 @@ class ArgumentParser { // -------------------------------------------------------------------------- class Any { public: - // constructor Any() : content(0) {} - // destructor ~Any() { delete content; } + // INWARD CONVERSIONS Any(const Any& other) : content(other.content ? other.content->clone() : 0) {} template Any(const ValueType& other) : content(new Holder(other)) {} + Any& swap(Any& other) { std::swap(content, other.content); return *this; } + Any& operator=(const Any& rhs) { Any tmp(rhs); return swap(tmp); } + template Any& operator=(const ValueType& rhs) { Any tmp(rhs); return swap(tmp); } + // OUTWARD CONVERSIONS template ValueType* toPtr() const { @@ -84,6 +87,7 @@ class ArgumentParser { ? &static_cast*>(content)->held_ : 0; } + template ValueType& castTo() { if (!toPtr()) { @@ -92,6 +96,7 @@ class ArgumentParser { } return *toPtr(); } + template const ValueType& castTo() const { if (!toPtr()) throw std::bad_cast(); @@ -106,6 +111,7 @@ class ArgumentParser { virtual const std::type_info& type_info() const = 0; virtual PlaceHolder* clone() const = 0; }; + // Inner template concrete instantiation of PlaceHolder template class Holder : public PlaceHolder { @@ -197,9 +203,9 @@ class ArgumentParser { if (!arg.optional) required_++; } - // -------------------------------------------------------------------------- - // Error handling - // -------------------------------------------------------------------------- + /** + Function which prints a given error message, and optionally shows the usage. + */ void argumentError(const std::string& msg, bool show_usage = false) { if (use_exceptions_) throw std::invalid_argument(msg); std::cerr << "ArgumentParser error: " << msg << std::endl; @@ -225,6 +231,7 @@ class ArgumentParser { // addArgument // -------------------------------------------------------------------------- void appName(const String& name) { app_name_ = name; } + void addArgument(const String& name, char nargs = 0, bool optional = true) { if (name.size() > 2) { Argument arg("", verify(name), optional, nargs); @@ -234,16 +241,19 @@ class ArgumentParser { insertArgument(arg); } } + void addArgument(const String& short_name, const String& name, char nargs = 0, bool optional = true) { Argument arg(verify(short_name), verify(name), optional, nargs); insertArgument(arg); } + void addFinalArgument(const String& name, char nargs = 1, bool optional = false) { final_name_ = delimit(name); Argument arg("", final_name_, optional, nargs); insertArgument(arg); } + void ignoreFirstArgument(bool ignore_first) { ignore_first_ = ignore_first; } String verify(const String& name) { if (name.empty()) argumentError("argument names must be non-empty"); @@ -280,10 +290,13 @@ class ArgumentParser { in < argv.end() - nfinal; ++in) { String active_name = active.canonicalName(); String el = *in; - std::cout << el << std::endl; + + // If one of the parameters is "-h", print usage and return. if (el.compare("-h") == 0) { - usage(); + std::cout << usage() << std::endl; + return; } + // check if the element is a key if (index_.count(el) == 0) { // input @@ -343,8 +356,9 @@ class ArgumentParser { } // check that all of the required arguments have been encountered - if (nrequired > 0 || nfinal > 0) + if (nrequired > 0 || nfinal > 0) { argumentError(String("too few required arguments passed to ").append(app_name_), true); + } } // -------------------------------------------------------------------------- @@ -361,6 +375,7 @@ class ArgumentParser { // Properties // -------------------------------------------------------------------------- String usage() { + // premable app name std::ostringstream help; help << "Usage: " << escape(app_name_); @@ -386,8 +401,15 @@ class ArgumentParser { // get the optional arguments for (ArgumentVector::const_iterator it = arguments_.begin(); it != arguments_.end(); ++it) { Argument arg = *it; - if (!arg.optional) continue; - if (arg.name.compare(final_name_) == 0) continue; + + if (!arg.optional) { + continue; + } + + if (arg.name.compare(final_name_) == 0) { + continue; + } + help << " "; String argstr = arg.toString(); if (argstr.size() + linelength > 80) { @@ -411,11 +433,19 @@ class ArgumentParser { } help << argstr; } - return help.str(); } - void useExceptions(bool state) { use_exceptions_ = state; } - bool empty() const { return index_.empty(); } + + + void useExceptions(bool state) { + use_exceptions_ = state; + } + + // Function which returns whether the parsed set of arguments is empty. + bool empty() const { + return index_.empty(); + } + void clear() { ignore_first_ = true; required_ = 0; @@ -423,13 +453,17 @@ class ArgumentParser { arguments_.clear(); variables_.clear(); } + + // Function which returns whether the parsed set of arguments contains a certain key. bool exists(const String& name) const { return index_.count(delimit(name)) > 0; } size_t count(const String& name) { + // check if the name is an argument if (index_.count(delimit(name)) == 0) return 0; size_t N = index_[delimit(name)]; Argument arg = arguments_[N]; Any var = variables_[N]; + // check if the argument is a vector if (arg.fixed) { return !var.castTo().empty(); @@ -438,4 +472,5 @@ class ArgumentParser { } } }; -#endif + +#endif \ No newline at end of file diff --git a/example.cpp b/example.cpp index 5df414c..e6f8014 100644 --- a/example.cpp +++ b/example.cpp @@ -9,7 +9,7 @@ int main(int argc, const char** argv) { parser.appName("Testing"); // add some arguments to search for - parser.addArgument("-c", "--cactus", 1); + parser.addArgument("-c", "--cactus", 1, true); // parse the command-line arguments - throws if invalid format parser.parse(argc, argv); From 93ec1ae39dfc647719c3667a161932a82603cdef Mon Sep 17 00:00:00 2001 From: Regan-Koopmans Date: Sat, 28 Oct 2017 11:08:10 +0200 Subject: [PATCH 3/3] Updated README.md with tables and code-blocks --- README.md | 76 +++++++++++++++++++++++++++------------------------- argparse.hpp | 22 ++++++++++----- example.cpp | 2 ++ 3 files changed, 58 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index b7d3c9e..2f702b2 100644 --- a/README.md +++ b/README.md @@ -6,29 +6,31 @@ Usage ----- An example says it best: - int main(int argc, const char** argv) { - - // make a new ArgumentParser - ArgumentParser parser; - - // add some arguments to search for - parser.addArgument("-a"); - parser.addArgument("-b"); - parser.addArgument("-c", "--cactus", 1); - parser.addArgument("-o", "--optional"); - parser.addArgument("-r", "--required", 1, true); - parser.addArgument("--five", 5); - parser.addArgument("--atleast", '+'); - parser.addArgument("--any", '*'); - parser.addFinalArgument("output"); - - // parse the command-line arguments - throws if invalid format - parser.parse(argc, argv); - - // if we get here, the configuration is valid - int cactus = parser.retrieve("cactus"); - return cactus; - } +```c++ +int main(int argc, const char** argv) { + + // make a new ArgumentParser + ArgumentParser parser; + + // add some arguments to search for + parser.addArgument("-a"); + parser.addArgument("-b"); + parser.addArgument("-c", "--cactus", 1); + parser.addArgument("-o", "--optional"); + parser.addArgument("-r", "--required", 1, true); + parser.addArgument("--five", 5); + parser.addArgument("--atleast", '+'); + parser.addArgument("--any", '*'); + parser.addFinalArgument("output"); + + // parse the command-line arguments - throws if invalid format + parser.parse(argc, argv); + + // if we get here, the configuration is valid + int cactus = parser.retrieve("cactus"); + return cactus; +} +``` If the supplied format is incorrect or we explicitly call `parser.usage()`, a usage string is printed to the terminal: @@ -93,17 +95,19 @@ or convert the required argument to a float: Method Summary -------------- - ArgumentParser() default constructor - useExceptions() if true, parsing errors throw exceptions rather than printing to stderr and exiting - appName() set the name of the application - addArgument() specify an argument to search for - addFinalArgument() specify a final un-named argument - ignoreFirstArgument() don't parse the first argument (usually the caller name on UNIX) - parse() invoke the parser on a `char**` array - retrieve() retrieve a set of inputs for an argument - usage() return a formatted usage string - empty() check if the set of specified arguments is empty - clear() clear all specified arguments - exists() check if an argument has been found - count() count the number of inputs for an argument +| Method | Description | +|------------------------| --------------------------------------------------------------------------------------| +| ArgumentParser() | default constructor | +| useExceptions() | if true, parsing errors throw exceptions rather than printing to stderr and exiting| +| appName() | set the name of the application| +| addArgument() | specify an argument to search for| +| addFinalArgument() | specify a final un-named argument| +| ignoreFirstArgument() | don't parse the first argument (usually the caller name on UNIX)| +| parse() | invoke the parser on a `char**` array| +| retrieve() | retrieve a set of inputs for an argument| +| usage() | return a formatted usage string| +| empty() | check if the set of specified arguments is empty| +| clear() | clear all specified arguments| +| exists() | check if an argument has been found| +| count() | count the number of inputs for an argument| diff --git a/argparse.hpp b/argparse.hpp index 53db99b..da0cfdb 100644 --- a/argparse.hpp +++ b/argparse.hpp @@ -167,7 +167,11 @@ class ArgumentParser { char variable_nargs; }; bool fixed; - String canonicalName() const { return (name.empty()) ? short_name : name; } + + String canonicalName() const { + return (name.empty()) ? short_name : name; + } + String toString(bool named = true) const { std::ostringstream s; String uname = name.empty() ? upper(strip(short_name)) : upper(strip(name)); @@ -271,7 +275,9 @@ class ArgumentParser { // -------------------------------------------------------------------------- // Parse // -------------------------------------------------------------------------- - void parse(size_t argc, const char** argv) { parse(StringVector(argv, argv + argc)); } + void parse(size_t argc, const char** argv) { + parse(StringVector(argv, argv + argc)); + } void parse(const StringVector& argv) { // check if the app is named @@ -299,6 +305,7 @@ class ArgumentParser { // check if the element is a key if (index_.count(el) == 0) { + // input // is the current active argument expecting more inputs? if (active.fixed && active.fixed_nargs <= consumed) @@ -311,6 +318,7 @@ class ArgumentParser { } consumed++; } else { + // new key! // has the active argument consumed enough elements? if ((active.fixed && active.fixed_nargs != consumed) || @@ -321,12 +329,14 @@ class ArgumentParser { .append(active_name), true); active = arguments_[index_[el]]; + // check if we've satisfied the required arguments if (active.optional && nrequired > 0) argumentError(String("encountered optional argument ") .append(el) .append(" when expecting more required arguments"), true); + // are there enough arguments for the new argument to consume? if ((active.fixed && active.fixed_nargs > (argv.end() - in - nfinal - 1)) || (!active.fixed && active.variable_nargs == '+' && @@ -341,6 +351,7 @@ class ArgumentParser { std::max(argv.begin() + ignore_first_, argv.end() - nfinal); in != argv.end(); ++in) { String el = *in; + // check if we accidentally find an argument specifier if (index_.count(el)) argumentError(String("encountered argument specifier ") @@ -371,9 +382,7 @@ class ArgumentParser { return variables_[N].castTo(); } - // -------------------------------------------------------------------------- - // Properties - // -------------------------------------------------------------------------- + // Function that constructs a usage string for the program. String usage() { // premable app name @@ -409,7 +418,7 @@ class ArgumentParser { if (arg.name.compare(final_name_) == 0) { continue; } - + help << " "; String argstr = arg.toString(); if (argstr.size() + linelength > 80) { @@ -446,6 +455,7 @@ class ArgumentParser { return index_.empty(); } + // Function that clears all of the captured arguments. void clear() { ignore_first_ = true; required_ = 0; diff --git a/example.cpp b/example.cpp index e6f8014..fd489e9 100644 --- a/example.cpp +++ b/example.cpp @@ -9,6 +9,8 @@ int main(int argc, const char** argv) { parser.appName("Testing"); // add some arguments to search for + parser.addArgument("-a"); + parser.addArgument("-b"); parser.addArgument("-c", "--cactus", 1, true); // parse the command-line arguments - throws if invalid format