From 3b2f0fa4cf8eaf14299f1a8d66a06f747083055c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Sun, 23 Jun 2019 20:43:57 +0200 Subject: [PATCH 1/4] Add argument parsing for eeprom stuff --- simulation/include/EEPROM.h | 2 +- simulation/src/argparse.hpp | 468 ++++++++++++++++++++++++++++++++++++ simulation/src/nuevisim.cpp | 29 ++- 3 files changed, 495 insertions(+), 4 deletions(-) create mode 100644 simulation/src/argparse.hpp diff --git a/simulation/include/EEPROM.h b/simulation/include/EEPROM.h index 39fa35e..5631e64 100644 --- a/simulation/include/EEPROM.h +++ b/simulation/include/EEPROM.h @@ -37,7 +37,7 @@ struct EEPROMClass // } private: - char someFakeEEPROM_memory[4096]; + char someFakeEEPROM_memory[2048]; }; diff --git a/simulation/src/argparse.hpp b/simulation/src/argparse.hpp new file mode 100644 index 0000000..f63b7e8 --- /dev/null +++ b/simulation/src/argparse.hpp @@ -0,0 +1,468 @@ +/* + +This library comes from https://github.com/hbristow/argparse + +Copyright (c) 2017, Hilton Bristow +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 copyright holder 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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. + +*/ + +#ifndef ARGPARSE_HPP_ +#define ARGPARSE_HPP_ + +#if __cplusplus >= 201103L +#include +typedef std::unordered_map IndexMap; +#else +#include +typedef std::map IndexMap; +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +/*! @class ArgumentParser + * @brief A simple command-line argument parser based on the design of + * python's parser of the same name. + * + * ArgumentParser is a simple C++ class that can parse arguments from + * the command-line or any array of strings. The syntax is familiar to + * anyone who has used python's ArgumentParser: + * \code + * // create a parser and add the options + * ArgumentParser parser; + * parser.addArgument("-n", "--name"); + * parser.addArgument("--inputs", '+'); + * + * // parse the command-line arguments + * parser.parse(argc, argv); + * + * // get the inputs and iterate over them + * string name = parser.retrieve("name"); + * vector inputs = parser.retrieve>("inputs"); + * \endcode + * + */ +class ArgumentParser { +private: + class Any; + struct Argument; + class PlaceHolder; + class Holder; + typedef std::string String; + typedef std::vector AnyVector; + typedef std::vector StringVector; + typedef std::vector ArgumentVector; + + // -------------------------------------------------------------------------- + // Type-erasure internal storage + // -------------------------------------------------------------------------- + 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 { + return content->type_info() == typeid(ValueType) + ? &static_cast*>(content)->held_ + : 0; + } + template + ValueType& castTo() { + if (!toPtr()) throw std::bad_cast(); + return *toPtr(); + } + template + const ValueType& castTo() const { + if (!toPtr()) throw std::bad_cast(); + return *toPtr(); + } + + private: + // Inner placeholder interface + class PlaceHolder { + public: + virtual ~PlaceHolder() {} + 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 { + public: + ValueType held_; + Holder(const ValueType& value) : held_(value) {} + virtual const std::type_info& type_info() const { return typeid(ValueType); } + virtual PlaceHolder* clone() const { return new Holder(held_); } + }; + PlaceHolder* content; + }; + + // -------------------------------------------------------------------------- + // Argument + // -------------------------------------------------------------------------- + static String delimit(const String& name) { + return String(std::min(name.size(), (size_t)2), '-').append(name); + } + static String strip(const String& name) { + size_t begin = 0; + begin += name.size() > 0 ? name[0] == '-' : 0; + begin += name.size() > 3 ? name[1] == '-' : 0; + return name.substr(begin); + } + static String upper(const String& in) { + String out(in); + std::transform(out.begin(), out.end(), out.begin(), ::toupper); + return out; + } + static String escape(const String& in) { + String out(in); + if (in.find(' ') != std::string::npos) out = String("\"").append(out).append("\""); + return out; + } + + struct Argument { + Argument() : short_name(""), name(""), optional(true), fixed_nargs(0), fixed(true) {} + Argument(const String& _short_name, const String& _name, bool _optional, char nargs) + : short_name(_short_name), name(_name), optional(_optional) { + if (nargs == '+' || nargs == '*') { + variable_nargs = nargs; + fixed = false; + } else { + fixed_nargs = nargs; + fixed = true; + } + } + String short_name; + String name; + bool optional; + union { + size_t fixed_nargs; + char variable_nargs; + }; + bool fixed; + 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)); + if (named && optional) s << "["; + if (named) s << canonicalName(); + if (fixed) { + size_t N = std::min((size_t)3, fixed_nargs); + for (size_t n = 0; n < N; ++n) s << " " << uname; + if (N < fixed_nargs) s << " ..."; + } + if (!fixed) { + s << " "; + if (variable_nargs == '*') s << "["; + s << uname << " "; + if (variable_nargs == '+') s << "["; + s << uname << "...]"; + } + if (named && optional) s << "]"; + return s.str(); + } + }; + + void insertArgument(const Argument& arg) { + size_t N = arguments_.size(); + arguments_.push_back(arg); + if (arg.fixed && arg.fixed_nargs <= 1) { + variables_.push_back(String()); + } else { + variables_.push_back(StringVector()); + } + if (!arg.short_name.empty()) index_[arg.short_name] = N; + if (!arg.name.empty()) index_[arg.name] = N; + if (!arg.optional) required_++; + } + + // -------------------------------------------------------------------------- + // Error handling + // -------------------------------------------------------------------------- + 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; + if (show_usage) std::cerr << usage() << std::endl; + exit(-5); + } + + // -------------------------------------------------------------------------- + // Member variables + // -------------------------------------------------------------------------- + IndexMap index_; + bool ignore_first_; + bool use_exceptions_; + size_t required_; + String app_name_; + String final_name_; + ArgumentVector arguments_; + AnyVector variables_; + +public: + ArgumentParser() : ignore_first_(true), use_exceptions_(false), required_(0) {} + // -------------------------------------------------------------------------- + // 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); + insertArgument(arg); + } else { + Argument arg(verify(name), "", optional, nargs); + 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"); + if ((name.size() == 2 && name[0] != '-') || name.size() == 3) + argumentError(String("invalid argument '") + .append(name) + .append("'. Short names must begin with '-'")); + if (name.size() > 3 && (name[0] != '-' || name[1] != '-')) + argumentError(String("invalid argument '") + .append(name) + .append("'. Multi-character names must begin with '--'")); + return name; + } + + // -------------------------------------------------------------------------- + // Parse + // -------------------------------------------------------------------------- + void parse(size_t argc, const char** argv) { parse(StringVector(argv, argv + argc)); } + + void parse(const StringVector& argv) { + // check if the app is named + if (app_name_.empty() && ignore_first_ && !argv.empty()) app_name_ = argv[0]; + + // set up the working set + Argument active; + Argument final = final_name_.empty() ? Argument() : arguments_[index_[final_name_]]; + size_t consumed = 0; + size_t nrequired = final.optional ? required_ : required_ - 1; + size_t nfinal = final.optional ? 0 : (final.fixed ? final.fixed_nargs + : (final.variable_nargs == '+' ? 1 : 0)); + + // iterate over each element of the array + for (StringVector::const_iterator in = argv.begin() + ignore_first_; + in < argv.end() - nfinal; ++in) { + String active_name = active.canonicalName(); + String el = *in; + // 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) + argumentError(String("attempt to pass too many inputs to ").append(active_name), + true); + if (active.fixed && active.fixed_nargs == 1) { + variables_[index_[active_name]].castTo() = el; + } else { + variables_[index_[active_name]].castTo().push_back(el); + } + consumed++; + } else { + // new key! + // has the active argument consumed enough elements? + if ((active.fixed && active.fixed_nargs != consumed) || + (!active.fixed && active.variable_nargs == '+' && consumed < 1)) + argumentError(String("encountered argument ") + .append(el) + .append(" when expecting more inputs to ") + .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 == '+' && + !(argv.end() - in - nfinal - 1))) + argumentError(String("too few inputs passed to argument ").append(el), true); + if (!active.optional) nrequired--; + consumed = 0; + } + } + + for (StringVector::const_iterator in = + 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 ") + .append(el) + .append(" while parsing final required inputs"), + true); + if (final.fixed && final.fixed_nargs == 1) { + variables_[index_[final_name_]].castTo() = el; + } else { + variables_[index_[final_name_]].castTo().push_back(el); + } + nfinal--; + } + + // check that all of the required arguments have been encountered + if (nrequired > 0 || nfinal > 0) + argumentError(String("too few required arguments passed to ").append(app_name_), true); + } + + // -------------------------------------------------------------------------- + // Retrieve + // -------------------------------------------------------------------------- + template + T& retrieve(const String& name) { + if (index_.count(delimit(name)) == 0) throw std::out_of_range("Key not found"); + size_t N = index_[delimit(name)]; + return variables_[N].castTo(); + } + + // -------------------------------------------------------------------------- + // Properties + // -------------------------------------------------------------------------- + String usage() { + // premable app name + std::ostringstream help; + help << "Usage: " << escape(app_name_); + size_t indent = help.str().size(); + size_t linelength = 0; + + // get the required 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; + help << " "; + String argstr = arg.toString(); + if (argstr.size() + linelength > 80) { + help << "\n" << String(indent, ' '); + linelength = 0; + } else { + linelength += argstr.size(); + } + help << argstr; + } + + // 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; + help << " "; + String argstr = arg.toString(); + if (argstr.size() + linelength > 80) { + help << "\n" << String(indent, ' '); + linelength = 0; + } else { + linelength += argstr.size(); + } + help << argstr; + } + + // get the final argument + if (!final_name_.empty()) { + Argument arg = arguments_[index_[final_name_]]; + String argstr = arg.toString(false); + if (argstr.size() + linelength > 80) { + help << "\n" << String(indent, ' '); + linelength = 0; + } else { + linelength += argstr.size(); + } + help << argstr; + } + + return help.str(); + } + void useExceptions(bool state) { use_exceptions_ = state; } + bool empty() const { return index_.empty(); } + void clear() { + ignore_first_ = true; + required_ = 0; + index_.clear(); + arguments_.clear(); + variables_.clear(); + } + 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(); + } else { + return var.castTo().size(); + } + } +}; +#endif diff --git a/simulation/src/nuevisim.cpp b/simulation/src/nuevisim.cpp index f00cf69..d17d216 100644 --- a/simulation/src/nuevisim.cpp +++ b/simulation/src/nuevisim.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -14,6 +15,8 @@ #include +#include "argparse.hpp" + // Forward declarations static void SimQuit(void); static int SimInit(void); @@ -503,7 +506,7 @@ static void SimLoop(std::function continue_predicate, std::function("eeprom-file"); + eepromWrite = parser.exists("eeprom-write"); + factoryReset = parser.exists("factory-reset"); + + printf("%d: %s\n",eepromWrite, eepromFile.c_str()); + return 0; + + return SimRun(eepromFile, eepromWrite, factoryReset); } From 829c08c0315739810a9ca8f622f7b13506a25d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Mon, 24 Jun 2019 11:56:57 +0200 Subject: [PATCH 2/4] EEPROM file storage, and argparsing that actually works --- simulation/include/EEPROM.h | 12 +- simulation/src/argparse.hpp | 468 ---- simulation/src/args.hxx | 4265 ++++++++++++++++++++++++++++++++++ simulation/src/nuevisim.cpp | 45 +- simulation/src/simeeprom.cpp | 87 +- 5 files changed, 4385 insertions(+), 492 deletions(-) delete mode 100644 simulation/src/argparse.hpp create mode 100644 simulation/src/args.hxx diff --git a/simulation/include/EEPROM.h b/simulation/include/EEPROM.h index 5631e64..4faa955 100644 --- a/simulation/include/EEPROM.h +++ b/simulation/include/EEPROM.h @@ -2,6 +2,7 @@ #define __EEPROM_H #include +#include struct EEPROMClass { @@ -36,12 +37,19 @@ struct EEPROMClass // return t; // } + //Make EEPROM persistent by storing to a file + int16_t setStorage(const char* filename, bool write); + void closeStorage(); + private: - char someFakeEEPROM_memory[2048]; + uint8_t someFakeEEPROM_memory[2048]; //Teensy 3.2 size + FILE *storage; + bool autoUpdate; + }; -static EEPROMClass EEPROM __attribute__ ((unused)); +extern EEPROMClass EEPROM __unused; #endif diff --git a/simulation/src/argparse.hpp b/simulation/src/argparse.hpp deleted file mode 100644 index f63b7e8..0000000 --- a/simulation/src/argparse.hpp +++ /dev/null @@ -1,468 +0,0 @@ -/* - -This library comes from https://github.com/hbristow/argparse - -Copyright (c) 2017, Hilton Bristow -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 copyright holder 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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. - -*/ - -#ifndef ARGPARSE_HPP_ -#define ARGPARSE_HPP_ - -#if __cplusplus >= 201103L -#include -typedef std::unordered_map IndexMap; -#else -#include -typedef std::map IndexMap; -#endif -#include -#include -#include -#include -#include -#include -#include -#include - -/*! @class ArgumentParser - * @brief A simple command-line argument parser based on the design of - * python's parser of the same name. - * - * ArgumentParser is a simple C++ class that can parse arguments from - * the command-line or any array of strings. The syntax is familiar to - * anyone who has used python's ArgumentParser: - * \code - * // create a parser and add the options - * ArgumentParser parser; - * parser.addArgument("-n", "--name"); - * parser.addArgument("--inputs", '+'); - * - * // parse the command-line arguments - * parser.parse(argc, argv); - * - * // get the inputs and iterate over them - * string name = parser.retrieve("name"); - * vector inputs = parser.retrieve>("inputs"); - * \endcode - * - */ -class ArgumentParser { -private: - class Any; - struct Argument; - class PlaceHolder; - class Holder; - typedef std::string String; - typedef std::vector AnyVector; - typedef std::vector StringVector; - typedef std::vector ArgumentVector; - - // -------------------------------------------------------------------------- - // Type-erasure internal storage - // -------------------------------------------------------------------------- - 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 { - return content->type_info() == typeid(ValueType) - ? &static_cast*>(content)->held_ - : 0; - } - template - ValueType& castTo() { - if (!toPtr()) throw std::bad_cast(); - return *toPtr(); - } - template - const ValueType& castTo() const { - if (!toPtr()) throw std::bad_cast(); - return *toPtr(); - } - - private: - // Inner placeholder interface - class PlaceHolder { - public: - virtual ~PlaceHolder() {} - 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 { - public: - ValueType held_; - Holder(const ValueType& value) : held_(value) {} - virtual const std::type_info& type_info() const { return typeid(ValueType); } - virtual PlaceHolder* clone() const { return new Holder(held_); } - }; - PlaceHolder* content; - }; - - // -------------------------------------------------------------------------- - // Argument - // -------------------------------------------------------------------------- - static String delimit(const String& name) { - return String(std::min(name.size(), (size_t)2), '-').append(name); - } - static String strip(const String& name) { - size_t begin = 0; - begin += name.size() > 0 ? name[0] == '-' : 0; - begin += name.size() > 3 ? name[1] == '-' : 0; - return name.substr(begin); - } - static String upper(const String& in) { - String out(in); - std::transform(out.begin(), out.end(), out.begin(), ::toupper); - return out; - } - static String escape(const String& in) { - String out(in); - if (in.find(' ') != std::string::npos) out = String("\"").append(out).append("\""); - return out; - } - - struct Argument { - Argument() : short_name(""), name(""), optional(true), fixed_nargs(0), fixed(true) {} - Argument(const String& _short_name, const String& _name, bool _optional, char nargs) - : short_name(_short_name), name(_name), optional(_optional) { - if (nargs == '+' || nargs == '*') { - variable_nargs = nargs; - fixed = false; - } else { - fixed_nargs = nargs; - fixed = true; - } - } - String short_name; - String name; - bool optional; - union { - size_t fixed_nargs; - char variable_nargs; - }; - bool fixed; - 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)); - if (named && optional) s << "["; - if (named) s << canonicalName(); - if (fixed) { - size_t N = std::min((size_t)3, fixed_nargs); - for (size_t n = 0; n < N; ++n) s << " " << uname; - if (N < fixed_nargs) s << " ..."; - } - if (!fixed) { - s << " "; - if (variable_nargs == '*') s << "["; - s << uname << " "; - if (variable_nargs == '+') s << "["; - s << uname << "...]"; - } - if (named && optional) s << "]"; - return s.str(); - } - }; - - void insertArgument(const Argument& arg) { - size_t N = arguments_.size(); - arguments_.push_back(arg); - if (arg.fixed && arg.fixed_nargs <= 1) { - variables_.push_back(String()); - } else { - variables_.push_back(StringVector()); - } - if (!arg.short_name.empty()) index_[arg.short_name] = N; - if (!arg.name.empty()) index_[arg.name] = N; - if (!arg.optional) required_++; - } - - // -------------------------------------------------------------------------- - // Error handling - // -------------------------------------------------------------------------- - 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; - if (show_usage) std::cerr << usage() << std::endl; - exit(-5); - } - - // -------------------------------------------------------------------------- - // Member variables - // -------------------------------------------------------------------------- - IndexMap index_; - bool ignore_first_; - bool use_exceptions_; - size_t required_; - String app_name_; - String final_name_; - ArgumentVector arguments_; - AnyVector variables_; - -public: - ArgumentParser() : ignore_first_(true), use_exceptions_(false), required_(0) {} - // -------------------------------------------------------------------------- - // 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); - insertArgument(arg); - } else { - Argument arg(verify(name), "", optional, nargs); - 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"); - if ((name.size() == 2 && name[0] != '-') || name.size() == 3) - argumentError(String("invalid argument '") - .append(name) - .append("'. Short names must begin with '-'")); - if (name.size() > 3 && (name[0] != '-' || name[1] != '-')) - argumentError(String("invalid argument '") - .append(name) - .append("'. Multi-character names must begin with '--'")); - return name; - } - - // -------------------------------------------------------------------------- - // Parse - // -------------------------------------------------------------------------- - void parse(size_t argc, const char** argv) { parse(StringVector(argv, argv + argc)); } - - void parse(const StringVector& argv) { - // check if the app is named - if (app_name_.empty() && ignore_first_ && !argv.empty()) app_name_ = argv[0]; - - // set up the working set - Argument active; - Argument final = final_name_.empty() ? Argument() : arguments_[index_[final_name_]]; - size_t consumed = 0; - size_t nrequired = final.optional ? required_ : required_ - 1; - size_t nfinal = final.optional ? 0 : (final.fixed ? final.fixed_nargs - : (final.variable_nargs == '+' ? 1 : 0)); - - // iterate over each element of the array - for (StringVector::const_iterator in = argv.begin() + ignore_first_; - in < argv.end() - nfinal; ++in) { - String active_name = active.canonicalName(); - String el = *in; - // 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) - argumentError(String("attempt to pass too many inputs to ").append(active_name), - true); - if (active.fixed && active.fixed_nargs == 1) { - variables_[index_[active_name]].castTo() = el; - } else { - variables_[index_[active_name]].castTo().push_back(el); - } - consumed++; - } else { - // new key! - // has the active argument consumed enough elements? - if ((active.fixed && active.fixed_nargs != consumed) || - (!active.fixed && active.variable_nargs == '+' && consumed < 1)) - argumentError(String("encountered argument ") - .append(el) - .append(" when expecting more inputs to ") - .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 == '+' && - !(argv.end() - in - nfinal - 1))) - argumentError(String("too few inputs passed to argument ").append(el), true); - if (!active.optional) nrequired--; - consumed = 0; - } - } - - for (StringVector::const_iterator in = - 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 ") - .append(el) - .append(" while parsing final required inputs"), - true); - if (final.fixed && final.fixed_nargs == 1) { - variables_[index_[final_name_]].castTo() = el; - } else { - variables_[index_[final_name_]].castTo().push_back(el); - } - nfinal--; - } - - // check that all of the required arguments have been encountered - if (nrequired > 0 || nfinal > 0) - argumentError(String("too few required arguments passed to ").append(app_name_), true); - } - - // -------------------------------------------------------------------------- - // Retrieve - // -------------------------------------------------------------------------- - template - T& retrieve(const String& name) { - if (index_.count(delimit(name)) == 0) throw std::out_of_range("Key not found"); - size_t N = index_[delimit(name)]; - return variables_[N].castTo(); - } - - // -------------------------------------------------------------------------- - // Properties - // -------------------------------------------------------------------------- - String usage() { - // premable app name - std::ostringstream help; - help << "Usage: " << escape(app_name_); - size_t indent = help.str().size(); - size_t linelength = 0; - - // get the required 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; - help << " "; - String argstr = arg.toString(); - if (argstr.size() + linelength > 80) { - help << "\n" << String(indent, ' '); - linelength = 0; - } else { - linelength += argstr.size(); - } - help << argstr; - } - - // 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; - help << " "; - String argstr = arg.toString(); - if (argstr.size() + linelength > 80) { - help << "\n" << String(indent, ' '); - linelength = 0; - } else { - linelength += argstr.size(); - } - help << argstr; - } - - // get the final argument - if (!final_name_.empty()) { - Argument arg = arguments_[index_[final_name_]]; - String argstr = arg.toString(false); - if (argstr.size() + linelength > 80) { - help << "\n" << String(indent, ' '); - linelength = 0; - } else { - linelength += argstr.size(); - } - help << argstr; - } - - return help.str(); - } - void useExceptions(bool state) { use_exceptions_ = state; } - bool empty() const { return index_.empty(); } - void clear() { - ignore_first_ = true; - required_ = 0; - index_.clear(); - arguments_.clear(); - variables_.clear(); - } - 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(); - } else { - return var.castTo().size(); - } - } -}; -#endif diff --git a/simulation/src/args.hxx b/simulation/src/args.hxx new file mode 100644 index 0000000..09c1919 --- /dev/null +++ b/simulation/src/args.hxx @@ -0,0 +1,4265 @@ +/* Copyright (c) 2016-2017 Taylor C. Richberger and Pavel + * Belikov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/** \file args.hxx + * \brief this single-header lets you use all of the args functionality + * + * The important stuff is done inside the args namespace + */ + +#ifndef ARGS_HXX +#define ARGS_HXX + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef ARGS_TESTNAMESPACE +namespace argstest +{ +#else + +/** \namespace args + * \brief contains all the functionality of the args library + */ +namespace args +{ +#endif + /** Getter to grab the value from the argument type. + * + * If the Get() function of the type returns a reference, so does this, and + * the value will be modifiable. + */ + template + auto get(Option &option_) -> decltype(option_.Get()) + { + return option_.Get(); + } + + /** (INTERNAL) Count UTF-8 glyphs + * + * This is not reliable, and will fail for combinatory glyphs, but it's + * good enough here for now. + * + * \param string The string to count glyphs from + * \return The UTF-8 glyphs in the string + */ + inline std::string::size_type Glyphs(const std::string &string_) + { + std::string::size_type length = 0; + for (const char c: string_) + { + if ((c & 0xc0) != 0x80) + { + ++length; + } + } + return length; + } + + /** (INTERNAL) Wrap a vector of words into a vector of lines + * + * Empty words are skipped. Word "\n" forces wrapping. + * + * \param begin The begin iterator + * \param end The end iterator + * \param width The width of the body + * \param firstlinewidth the width of the first line, defaults to the width of the body + * \param firstlineindent the indent of the first line, defaults to 0 + * \return the vector of lines + */ + template + inline std::vector Wrap(It begin, + It end, + const std::string::size_type width, + std::string::size_type firstlinewidth = 0, + std::string::size_type firstlineindent = 0) + { + std::vector output; + std::string line(firstlineindent, ' '); + bool empty = true; + + if (firstlinewidth == 0) + { + firstlinewidth = width; + } + + auto currentwidth = firstlinewidth; + + for (auto it = begin; it != end; ++it) + { + if (it->empty()) + { + continue; + } + + if (*it == "\n") + { + if (!empty) + { + output.push_back(line); + line.clear(); + empty = true; + currentwidth = width; + } + + continue; + } + + auto itemsize = Glyphs(*it); + if ((line.length() + 1 + itemsize) > currentwidth) + { + if (!empty) + { + output.push_back(line); + line.clear(); + empty = true; + currentwidth = width; + } + } + + if (itemsize > 0) + { + if (!empty) + { + line += ' '; + } + + line += *it; + empty = false; + } + } + + if (!empty) + { + output.push_back(line); + } + + return output; + } + + namespace detail + { + template + std::string Join(const T& array, const std::string &delimiter) + { + std::string res; + for (auto &element : array) + { + if (!res.empty()) + { + res += delimiter; + } + + res += element; + } + + return res; + } + } + + /** (INTERNAL) Wrap a string into a vector of lines + * + * This is quick and hacky, but works well enough. You can specify a + * different width for the first line + * + * \param width The width of the body + * \param firstlinewid the width of the first line, defaults to the width of the body + * \return the vector of lines + */ + inline std::vector Wrap(const std::string &in, const std::string::size_type width, std::string::size_type firstlinewidth = 0) + { + // Preserve existing line breaks + const auto newlineloc = in.find('\n'); + if (newlineloc != in.npos) + { + auto first = Wrap(std::string(in, 0, newlineloc), width); + auto second = Wrap(std::string(in, newlineloc + 1), width); + first.insert( + std::end(first), + std::make_move_iterator(std::begin(second)), + std::make_move_iterator(std::end(second))); + return first; + } + + std::istringstream stream(in); + std::string::size_type indent = 0; + + for (char c : in) + { + if (!isspace(c)) + { + break; + } + ++indent; + } + + return Wrap(std::istream_iterator(stream), std::istream_iterator(), + width, firstlinewidth, indent); + } + +#ifdef ARGS_NOEXCEPT + /// Error class, for when ARGS_NOEXCEPT is defined + enum class Error + { + None, + Usage, + Parse, + Validation, + Required, + Map, + Extra, + Help, + Subparser, + Completion, + }; +#else + /** Base error class + */ + class Error : public std::runtime_error + { + public: + Error(const std::string &problem) : std::runtime_error(problem) {} + virtual ~Error() {} + }; + + /** Errors that occur during usage + */ + class UsageError : public Error + { + public: + UsageError(const std::string &problem) : Error(problem) {} + virtual ~UsageError() {} + }; + + /** Errors that occur during regular parsing + */ + class ParseError : public Error + { + public: + ParseError(const std::string &problem) : Error(problem) {} + virtual ~ParseError() {} + }; + + /** Errors that are detected from group validation after parsing finishes + */ + class ValidationError : public Error + { + public: + ValidationError(const std::string &problem) : Error(problem) {} + virtual ~ValidationError() {} + }; + + /** Errors that when a required flag is omitted + */ + class RequiredError : public ValidationError + { + public: + RequiredError(const std::string &problem) : ValidationError(problem) {} + virtual ~RequiredError() {} + }; + + /** Errors in map lookups + */ + class MapError : public ParseError + { + public: + MapError(const std::string &problem) : ParseError(problem) {} + virtual ~MapError() {} + }; + + /** Error that occurs when a singular flag is specified multiple times + */ + class ExtraError : public ParseError + { + public: + ExtraError(const std::string &problem) : ParseError(problem) {} + virtual ~ExtraError() {} + }; + + /** An exception that indicates that the user has requested help + */ + class Help : public Error + { + public: + Help(const std::string &flag) : Error(flag) {} + virtual ~Help() {} + }; + + /** (INTERNAL) An exception that emulates coroutine-like control flow for subparsers. + */ + class SubparserError : public Error + { + public: + SubparserError() : Error("") {} + virtual ~SubparserError() {} + }; + + /** An exception that contains autocompletion reply + */ + class Completion : public Error + { + public: + Completion(const std::string &flag) : Error(flag) {} + virtual ~Completion() {} + }; +#endif + + /** A simple unified option type for unified initializer lists for the Matcher class. + */ + struct EitherFlag + { + const bool isShort; + const char shortFlag; + const std::string longFlag; + EitherFlag(const std::string &flag) : isShort(false), shortFlag(), longFlag(flag) {} + EitherFlag(const char *flag) : isShort(false), shortFlag(), longFlag(flag) {} + EitherFlag(const char flag) : isShort(true), shortFlag(flag), longFlag() {} + + /** Get just the long flags from an initializer list of EitherFlags + */ + static std::unordered_set GetLong(std::initializer_list flags) + { + std::unordered_set longFlags; + for (const EitherFlag &flag: flags) + { + if (!flag.isShort) + { + longFlags.insert(flag.longFlag); + } + } + return longFlags; + } + + /** Get just the short flags from an initializer list of EitherFlags + */ + static std::unordered_set GetShort(std::initializer_list flags) + { + std::unordered_set shortFlags; + for (const EitherFlag &flag: flags) + { + if (flag.isShort) + { + shortFlags.insert(flag.shortFlag); + } + } + return shortFlags; + } + + std::string str() const + { + return isShort ? std::string(1, shortFlag) : longFlag; + } + + std::string str(const std::string &shortPrefix, const std::string &longPrefix) const + { + return isShort ? shortPrefix + std::string(1, shortFlag) : longPrefix + longFlag; + } + }; + + + + /** A class of "matchers", specifying short and flags that can possibly be + * matched. + * + * This is supposed to be constructed and then passed in, not used directly + * from user code. + */ + class Matcher + { + private: + const std::unordered_set shortFlags; + const std::unordered_set longFlags; + + public: + /** Specify short and long flags separately as iterators + * + * ex: `args::Matcher(shortFlags.begin(), shortFlags.end(), longFlags.begin(), longFlags.end())` + */ + template + Matcher(ShortIt shortFlagsStart, ShortIt shortFlagsEnd, LongIt longFlagsStart, LongIt longFlagsEnd) : + shortFlags(shortFlagsStart, shortFlagsEnd), + longFlags(longFlagsStart, longFlagsEnd) + { + if (shortFlags.empty() && longFlags.empty()) + { +#ifndef ARGS_NOEXCEPT + throw UsageError("empty Matcher"); +#endif + } + } + +#ifdef ARGS_NOEXCEPT + /// Only for ARGS_NOEXCEPT + Error GetError() const noexcept + { + return shortFlags.empty() && longFlags.empty() ? Error::Usage : Error::None; + } +#endif + + /** Specify short and long flags separately as iterables + * + * ex: `args::Matcher(shortFlags, longFlags)` + */ + template + Matcher(Short &&shortIn, Long &&longIn) : + Matcher(std::begin(shortIn), std::end(shortIn), std::begin(longIn), std::end(longIn)) + {} + + /** Specify a mixed single initializer-list of both short and long flags + * + * This is the fancy one. It takes a single initializer list of + * any number of any mixed kinds of flags. Chars are + * automatically interpreted as short flags, and strings are + * automatically interpreted as long flags: + * + * args::Matcher{'a'} + * args::Matcher{"foo"} + * args::Matcher{'h', "help"} + * args::Matcher{"foo", 'f', 'F', "FoO"} + */ + Matcher(std::initializer_list in) : + Matcher(EitherFlag::GetShort(in), EitherFlag::GetLong(in)) {} + + Matcher(Matcher &&other) : shortFlags(std::move(other.shortFlags)), longFlags(std::move(other.longFlags)) + {} + + ~Matcher() {} + + /** (INTERNAL) Check if there is a match of a short flag + */ + bool Match(const char flag) const + { + return shortFlags.find(flag) != shortFlags.end(); + } + + /** (INTERNAL) Check if there is a match of a long flag + */ + bool Match(const std::string &flag) const + { + return longFlags.find(flag) != longFlags.end(); + } + + /** (INTERNAL) Check if there is a match of a flag + */ + bool Match(const EitherFlag &flag) const + { + return flag.isShort ? Match(flag.shortFlag) : Match(flag.longFlag); + } + + /** (INTERNAL) Get all flag strings as a vector, with the prefixes embedded + */ + std::vector GetFlagStrings() const + { + std::vector flagStrings; + flagStrings.reserve(shortFlags.size() + longFlags.size()); + for (const char flag: shortFlags) + { + flagStrings.emplace_back(flag); + } + for (const std::string &flag: longFlags) + { + flagStrings.emplace_back(flag); + } + return flagStrings; + } + + /** (INTERNAL) Get long flag if it exists or any short flag + */ + EitherFlag GetLongOrAny() const + { + if (!longFlags.empty()) + { + return *longFlags.begin(); + } + + if (!shortFlags.empty()) + { + return *shortFlags.begin(); + } + + // should be unreachable + return ' '; + } + + /** (INTERNAL) Get short flag if it exists or any long flag + */ + EitherFlag GetShortOrAny() const + { + if (!shortFlags.empty()) + { + return *shortFlags.begin(); + } + + if (!longFlags.empty()) + { + return *longFlags.begin(); + } + + // should be unreachable + return ' '; + } + }; + + /** Attributes for flags. + */ + enum class Options + { + /** Default options. + */ + None = 0x0, + + /** Flag can't be passed multiple times. + */ + Single = 0x01, + + /** Flag can't be omitted. + */ + Required = 0x02, + + /** Flag is excluded from usage line. + */ + HiddenFromUsage = 0x04, + + /** Flag is excluded from options help. + */ + HiddenFromDescription = 0x08, + + /** Flag is global and can be used in any subcommand. + */ + Global = 0x10, + + /** Flag stops a parser. + */ + KickOut = 0x20, + + /** Flag is excluded from auto completion. + */ + HiddenFromCompletion = 0x40, + + /** Flag is excluded from options help and usage line + */ + Hidden = HiddenFromUsage | HiddenFromDescription | HiddenFromCompletion, + }; + + inline Options operator | (Options lhs, Options rhs) + { + return static_cast(static_cast(lhs) | static_cast(rhs)); + } + + inline Options operator & (Options lhs, Options rhs) + { + return static_cast(static_cast(lhs) & static_cast(rhs)); + } + + class FlagBase; + class PositionalBase; + class Command; + class ArgumentParser; + + /** A simple structure of parameters for easy user-modifyable help menus + */ + struct HelpParams + { + /** The width of the help menu + */ + unsigned int width = 80; + /** The indent of the program line + */ + unsigned int progindent = 2; + /** The indent of the program trailing lines for long parameters + */ + unsigned int progtailindent = 4; + /** The indent of the description and epilogs + */ + unsigned int descriptionindent = 4; + /** The indent of the flags + */ + unsigned int flagindent = 6; + /** The indent of the flag descriptions + */ + unsigned int helpindent = 40; + /** The additional indent each group adds + */ + unsigned int eachgroupindent = 2; + + /** The minimum gutter between each flag and its help + */ + unsigned int gutter = 1; + + /** Show the terminator when both options and positional parameters are present + */ + bool showTerminator = true; + + /** Show the {OPTIONS} on the prog line when this is true + */ + bool showProglineOptions = true; + + /** Show the positionals on the prog line when this is true + */ + bool showProglinePositionals = true; + + /** The prefix for short flags + */ + std::string shortPrefix; + + /** The prefix for long flags + */ + std::string longPrefix; + + /** The separator for short flags + */ + std::string shortSeparator; + + /** The separator for long flags + */ + std::string longSeparator; + + /** The program name for help generation + */ + std::string programName; + + /** Show command's flags + */ + bool showCommandChildren = false; + + /** Show command's descriptions and epilog + */ + bool showCommandFullHelp = false; + + /** The postfix for progline when showProglineOptions is true and command has any flags + */ + std::string proglineOptions = "{OPTIONS}"; + + /** The prefix for progline when command has any subcommands + */ + std::string proglineCommand = "COMMAND"; + + /** The prefix for progline value + */ + std::string proglineValueOpen = " <"; + + /** The postfix for progline value + */ + std::string proglineValueClose = ">"; + + /** The prefix for progline required argument + */ + std::string proglineRequiredOpen = ""; + + /** The postfix for progline required argument + */ + std::string proglineRequiredClose = ""; + + /** The prefix for progline non-required argument + */ + std::string proglineNonrequiredOpen = "["; + + /** The postfix for progline non-required argument + */ + std::string proglineNonrequiredClose = "]"; + + /** Show flags in program line + */ + bool proglineShowFlags = false; + + /** Use short flags in program lines when possible + */ + bool proglinePreferShortFlags = false; + + /** Program line prefix + */ + std::string usageString; + + /** String shown in help before flags descriptions + */ + std::string optionsString = "OPTIONS:"; + + /** Display value name after all the long and short flags + */ + bool useValueNameOnce = false; + + /** Show value name + */ + bool showValueName = true; + + /** Add newline before flag description + */ + bool addNewlineBeforeDescription = false; + + /** The prefix for option value + */ + std::string valueOpen = "["; + + /** The postfix for option value + */ + std::string valueClose = "]"; + + /** Add choices to argument description + */ + bool addChoices = false; + + /** The prefix for choices + */ + std::string choiceString = "\nOne of: "; + + /** Add default values to argument description + */ + bool addDefault = false; + + /** The prefix for default values + */ + std::string defaultString = "\nDefault: "; + }; + + /** A number of arguments which can be consumed by an option. + * + * Represents a closed interval [min, max]. + */ + struct Nargs + { + const size_t min; + const size_t max; + + Nargs(size_t min_, size_t max_) : min{min_}, max{max_} + { +#ifndef ARGS_NOEXCEPT + if (max < min) + { + throw UsageError("Nargs: max > min"); + } +#endif + } + + Nargs(size_t num_) : min{num_}, max{num_} + { + } + + friend bool operator == (const Nargs &lhs, const Nargs &rhs) + { + return lhs.min == rhs.min && lhs.max == rhs.max; + } + + friend bool operator != (const Nargs &lhs, const Nargs &rhs) + { + return !(lhs == rhs); + } + }; + + /** Base class for all match types + */ + class Base + { + private: + Options options = {}; + + protected: + bool matched = false; + const std::string help; +#ifdef ARGS_NOEXCEPT + /// Only for ARGS_NOEXCEPT + mutable Error error = Error::None; + mutable std::string errorMsg; +#endif + + public: + Base(const std::string &help_, Options options_ = {}) : options(options_), help(help_) {} + virtual ~Base() {} + + Options GetOptions() const noexcept + { + return options; + } + + bool IsRequired() const noexcept + { + return (GetOptions() & Options::Required) != Options::None; + } + + virtual bool Matched() const noexcept + { + return matched; + } + + virtual void Validate(const std::string &, const std::string &) const + { + } + + operator bool() const noexcept + { + return Matched(); + } + + virtual std::vector> GetDescription(const HelpParams &, const unsigned indentLevel) const + { + std::tuple description; + std::get<1>(description) = help; + std::get<2>(description) = indentLevel; + return { std::move(description) }; + } + + virtual std::vector GetCommands() + { + return {}; + } + + virtual bool IsGroup() const + { + return false; + } + + virtual FlagBase *Match(const EitherFlag &) + { + return nullptr; + } + + virtual PositionalBase *GetNextPositional() + { + return nullptr; + } + + virtual std::vector GetAllFlags() + { + return {}; + } + + virtual bool HasFlag() const + { + return false; + } + + virtual bool HasPositional() const + { + return false; + } + + virtual bool HasCommand() const + { + return false; + } + + virtual std::vector GetProgramLine(const HelpParams &) const + { + return {}; + } + + /// Sets a kick-out value for building subparsers + void KickOut(bool kickout_) noexcept + { + if (kickout_) + { + options = options | Options::KickOut; + } + else + { + options = static_cast(static_cast(options) & ~static_cast(Options::KickOut)); + } + } + + /// Gets the kick-out value for building subparsers + bool KickOut() const noexcept + { + return (options & Options::KickOut) != Options::None; + } + + virtual void Reset() noexcept + { + matched = false; +#ifdef ARGS_NOEXCEPT + error = Error::None; + errorMsg.clear(); +#endif + } + +#ifdef ARGS_NOEXCEPT + /// Only for ARGS_NOEXCEPT + virtual Error GetError() const + { + return error; + } + + /// Only for ARGS_NOEXCEPT + std::string GetErrorMsg() const + { + return errorMsg; + } +#endif + }; + + /** Base class for all match types that have a name + */ + class NamedBase : public Base + { + protected: + const std::string name; + bool kickout = false; + std::string defaultString; + bool defaultStringManual = false; + std::vector choicesStrings; + bool choicesStringManual = false; + + virtual std::string GetDefaultString(const HelpParams&) const { return {}; } + + virtual std::vector GetChoicesStrings(const HelpParams&) const { return {}; } + + virtual std::string GetNameString(const HelpParams&) const { return Name(); } + + void AddDescriptionPostfix(std::string &dest, const bool isManual, const std::string &manual, bool isGenerated, const std::string &generated, const std::string &str) const + { + if (isManual && !manual.empty()) + { + dest += str; + dest += manual; + } + else if (!isManual && isGenerated && !generated.empty()) + { + dest += str; + dest += generated; + } + } + + public: + NamedBase(const std::string &name_, const std::string &help_, Options options_ = {}) : Base(help_, options_), name(name_) {} + virtual ~NamedBase() {} + + /** Sets default value string that will be added to argument description. + * Use empty string to disable it for this argument. + */ + void HelpDefault(const std::string &str) + { + defaultStringManual = true; + defaultString = str; + } + + /** Gets default value string that will be added to argument description. + */ + std::string HelpDefault(const HelpParams ¶ms) const + { + return defaultStringManual ? defaultString : GetDefaultString(params); + } + + /** Sets choices strings that will be added to argument description. + * Use empty vector to disable it for this argument. + */ + void HelpChoices(const std::vector &array) + { + choicesStringManual = true; + choicesStrings = array; + } + + /** Gets choices strings that will be added to argument description. + */ + std::vector HelpChoices(const HelpParams ¶ms) const + { + return choicesStringManual ? choicesStrings : GetChoicesStrings(params); + } + + virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned indentLevel) const override + { + std::tuple description; + std::get<0>(description) = GetNameString(params); + std::get<1>(description) = help; + std::get<2>(description) = indentLevel; + + AddDescriptionPostfix(std::get<1>(description), choicesStringManual, detail::Join(choicesStrings, ", "), params.addChoices, detail::Join(GetChoicesStrings(params), ", "), params.choiceString); + AddDescriptionPostfix(std::get<1>(description), defaultStringManual, defaultString, params.addDefault, GetDefaultString(params), params.defaultString); + + return { std::move(description) }; + } + + virtual std::string Name() const + { + return name; + } + }; + + namespace detail + { + template + struct IsConvertableToString : std::false_type {}; + + template + struct IsConvertableToString() << std::declval(), int())> : std::true_type {}; + + template + typename std::enable_if::value, std::string>::type + ToString(const T &value) + { + std::ostringstream s; + s << value; + return s.str(); + } + + template + typename std::enable_if::value, std::string>::type + ToString(const T &) + { + return {}; + } + + template + std::vector MapKeysToStrings(const T &map) + { + std::vector res; + using K = typename std::decayfirst)>::type; + if (IsConvertableToString::value) + { + for (const auto &p : map) + { + res.push_back(detail::ToString(p.first)); + } + + std::sort(res.begin(), res.end()); + } + return res; + } + } + + /** Base class for all flag options + */ + class FlagBase : public NamedBase + { + protected: + const Matcher matcher; + + virtual std::string GetNameString(const HelpParams ¶ms) const override + { + const std::string postfix = !params.showValueName || NumberOfArguments() == 0 ? std::string() : Name(); + std::string flags; + const auto flagStrings = matcher.GetFlagStrings(); + const bool useValueNameOnce = flagStrings.size() == 1 ? false : params.useValueNameOnce; + for (auto it = flagStrings.begin(); it != flagStrings.end(); ++it) + { + auto &flag = *it; + if (it != flagStrings.begin()) + { + flags += ", "; + } + + flags += flag.isShort ? params.shortPrefix : params.longPrefix; + flags += flag.str(); + + if (!postfix.empty() && (!useValueNameOnce || it + 1 == flagStrings.end())) + { + flags += flag.isShort ? params.shortSeparator : params.longSeparator; + flags += params.valueOpen + postfix + params.valueClose; + } + } + + return flags; + } + + public: + FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false) : NamedBase(name_, help_, extraError_ ? Options::Single : Options()), matcher(std::move(matcher_)) {} + + FlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : NamedBase(name_, help_, options_), matcher(std::move(matcher_)) {} + + virtual ~FlagBase() {} + + virtual FlagBase *Match(const EitherFlag &flag) override + { + if (matcher.Match(flag)) + { + if ((GetOptions() & Options::Single) != Options::None && matched) + { + std::ostringstream problem; + problem << "Flag '" << flag.str() << "' was passed multiple times, but is only allowed to be passed once"; +#ifdef ARGS_NOEXCEPT + error = Error::Extra; + errorMsg = problem.str(); +#else + throw ExtraError(problem.str()); +#endif + } + matched = true; + return this; + } + return nullptr; + } + + virtual std::vector GetAllFlags() override + { + return { this }; + } + + const Matcher &GetMatcher() const + { + return matcher; + } + + virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) const override + { + if (!Matched() && IsRequired()) + { + std::ostringstream problem; + problem << "Flag '" << matcher.GetLongOrAny().str(shortPrefix, longPrefix) << "' is required"; +#ifdef ARGS_NOEXCEPT + error = Error::Required; + errorMsg = problem.str(); +#else + throw RequiredError(problem.str()); +#endif + } + } + + virtual std::vector GetProgramLine(const HelpParams ¶ms) const override + { + if (!params.proglineShowFlags) + { + return {}; + } + + const std::string postfix = NumberOfArguments() == 0 ? std::string() : Name(); + const EitherFlag flag = params.proglinePreferShortFlags ? matcher.GetShortOrAny() : matcher.GetLongOrAny(); + std::string res = flag.str(params.shortPrefix, params.longPrefix); + if (!postfix.empty()) + { + res += params.proglineValueOpen + postfix + params.proglineValueClose; + } + + return { IsRequired() ? params.proglineRequiredOpen + res + params.proglineRequiredClose + : params.proglineNonrequiredOpen + res + params.proglineNonrequiredClose }; + } + + virtual bool HasFlag() const override + { + return true; + } + +#ifdef ARGS_NOEXCEPT + /// Only for ARGS_NOEXCEPT + virtual Error GetError() const override + { + const auto nargs = NumberOfArguments(); + if (nargs.min > nargs.max) + { + return Error::Usage; + } + + const auto matcherError = matcher.GetError(); + if (matcherError != Error::None) + { + return matcherError; + } + + return error; + } +#endif + + /** Defines how many values can be consumed by this option. + * + * \return closed interval [min, max] + */ + virtual Nargs NumberOfArguments() const noexcept = 0; + + /** Parse values of this option. + * + * \param value Vector of values. It's size must be in NumberOfArguments() interval. + */ + virtual void ParseValue(const std::vector &value) = 0; + }; + + /** Base class for value-accepting flag options + */ + class ValueFlagBase : public FlagBase + { + public: + ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false) : FlagBase(name_, help_, std::move(matcher_), extraError_) {} + ValueFlagBase(const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) : FlagBase(name_, help_, std::move(matcher_), options_) {} + virtual ~ValueFlagBase() {} + + virtual Nargs NumberOfArguments() const noexcept override + { + return 1; + } + }; + + class CompletionFlag : public ValueFlagBase + { + public: + std::vector reply; + size_t cword = 0; + std::string syntax; + + template + CompletionFlag(GroupClass &group_, Matcher &&matcher_): ValueFlagBase("completion", "completion flag", std::move(matcher_), Options::Hidden) + { + group_.AddCompletion(*this); + } + + virtual ~CompletionFlag() {} + + virtual Nargs NumberOfArguments() const noexcept override + { + return 2; + } + + virtual void ParseValue(const std::vector &value_) override + { + syntax = value_.at(0); + std::istringstream(value_.at(1)) >> cword; + } + + /** Get the completion reply + */ + std::string Get() noexcept + { + return detail::Join(reply, "\n"); + } + + virtual void Reset() noexcept override + { + ValueFlagBase::Reset(); + cword = 0; + syntax.clear(); + reply.clear(); + } + }; + + + /** Base class for positional options + */ + class PositionalBase : public NamedBase + { + protected: + bool ready; + + public: + PositionalBase(const std::string &name_, const std::string &help_, Options options_ = {}) : NamedBase(name_, help_, options_), ready(true) {} + virtual ~PositionalBase() {} + + bool Ready() + { + return ready; + } + + virtual void ParseValue(const std::string &value_) = 0; + + virtual void Reset() noexcept override + { + matched = false; + ready = true; +#ifdef ARGS_NOEXCEPT + error = Error::None; + errorMsg.clear(); +#endif + } + + virtual PositionalBase *GetNextPositional() override + { + return Ready() ? this : nullptr; + } + + virtual bool HasPositional() const override + { + return true; + } + + virtual std::vector GetProgramLine(const HelpParams ¶ms) const override + { + return { IsRequired() ? params.proglineRequiredOpen + Name() + params.proglineRequiredClose + : params.proglineNonrequiredOpen + Name() + params.proglineNonrequiredClose }; + } + + virtual void Validate(const std::string &, const std::string &) const override + { + if (IsRequired() && !Matched()) + { + std::ostringstream problem; + problem << "Option '" << Name() << "' is required"; +#ifdef ARGS_NOEXCEPT + error = Error::Required; + errorMsg = problem.str(); +#else + throw RequiredError(problem.str()); +#endif + } + } + }; + + /** Class for all kinds of validating groups, including ArgumentParser + */ + class Group : public Base + { + private: + std::vector children; + std::function validator; + + public: + /** Default validators + */ + struct Validators + { + static bool Xor(const Group &group) + { + return group.MatchedChildren() == 1; + } + + static bool AtLeastOne(const Group &group) + { + return group.MatchedChildren() >= 1; + } + + static bool AtMostOne(const Group &group) + { + return group.MatchedChildren() <= 1; + } + + static bool All(const Group &group) + { + return group.Children().size() == group.MatchedChildren(); + } + + static bool AllOrNone(const Group &group) + { + return (All(group) || None(group)); + } + + static bool AllChildGroups(const Group &group) + { + return std::none_of(std::begin(group.Children()), std::end(group.Children()), [](const Base* child) -> bool { + return child->IsGroup() && !child->Matched(); + }); + } + + static bool DontCare(const Group &) + { + return true; + } + + static bool CareTooMuch(const Group &) + { + return false; + } + + static bool None(const Group &group) + { + return group.MatchedChildren() == 0; + } + }; + /// If help is empty, this group will not be printed in help output + Group(const std::string &help_ = std::string(), const std::function &validator_ = Validators::DontCare, Options options_ = {}) : Base(help_, options_), validator(validator_) {} + /// If help is empty, this group will not be printed in help output + Group(Group &group_, const std::string &help_ = std::string(), const std::function &validator_ = Validators::DontCare, Options options_ = {}) : Base(help_, options_), validator(validator_) + { + group_.Add(*this); + } + virtual ~Group() {} + + /** Append a child to this Group. + */ + void Add(Base &child) + { + children.emplace_back(&child); + } + + /** Get all this group's children + */ + const std::vector &Children() const + { + return children; + } + + /** Return the first FlagBase that matches flag, or nullptr + * + * \param flag The flag with prefixes stripped + * \return the first matching FlagBase pointer, or nullptr if there is no match + */ + virtual FlagBase *Match(const EitherFlag &flag) override + { + for (Base *child: Children()) + { + if (FlagBase *match = child->Match(flag)) + { + return match; + } + } + return nullptr; + } + + virtual std::vector GetAllFlags() override + { + std::vector res; + for (Base *child: Children()) + { + auto childRes = child->GetAllFlags(); + res.insert(res.end(), childRes.begin(), childRes.end()); + } + return res; + } + + virtual void Validate(const std::string &shortPrefix, const std::string &longPrefix) const override + { + for (Base *child: Children()) + { + child->Validate(shortPrefix, longPrefix); + } + } + + /** Get the next ready positional, or nullptr if there is none + * + * \return the first ready PositionalBase pointer, or nullptr if there is no match + */ + virtual PositionalBase *GetNextPositional() override + { + for (Base *child: Children()) + { + if (auto next = child->GetNextPositional()) + { + return next; + } + } + return nullptr; + } + + /** Get whether this has any FlagBase children + * + * \return Whether or not there are any FlagBase children + */ + virtual bool HasFlag() const override + { + return std::any_of(Children().begin(), Children().end(), [](Base *child) { return child->HasFlag(); }); + } + + /** Get whether this has any PositionalBase children + * + * \return Whether or not there are any PositionalBase children + */ + virtual bool HasPositional() const override + { + return std::any_of(Children().begin(), Children().end(), [](Base *child) { return child->HasPositional(); }); + } + + /** Get whether this has any Command children + * + * \return Whether or not there are any Command children + */ + virtual bool HasCommand() const override + { + return std::any_of(Children().begin(), Children().end(), [](Base *child) { return child->HasCommand(); }); + } + + /** Count the number of matched children this group has + */ + std::vector::size_type MatchedChildren() const + { + // Cast to avoid warnings from -Wsign-conversion + return static_cast::size_type>( + std::count_if(std::begin(Children()), std::end(Children()), [](const Base *child){return child->Matched();})); + } + + /** Whether or not this group matches validation + */ + virtual bool Matched() const noexcept override + { + return validator(*this); + } + + /** Get validation + */ + bool Get() const + { + return Matched(); + } + + /** Get all the child descriptions for help generation + */ + virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned int indent) const override + { + std::vector> descriptions; + + // Push that group description on the back if not empty + unsigned addindent = 0; + if (!help.empty()) + { + descriptions.emplace_back(help, "", indent); + addindent = 1; + } + + for (Base *child: Children()) + { + if ((child->GetOptions() & Options::HiddenFromDescription) != Options::None) + { + continue; + } + + auto groupDescriptions = child->GetDescription(params, indent + addindent); + descriptions.insert( + std::end(descriptions), + std::make_move_iterator(std::begin(groupDescriptions)), + std::make_move_iterator(std::end(groupDescriptions))); + } + return descriptions; + } + + /** Get the names of positional parameters + */ + virtual std::vector GetProgramLine(const HelpParams ¶ms) const override + { + std::vector names; + for (Base *child: Children()) + { + if ((child->GetOptions() & Options::HiddenFromUsage) != Options::None) + { + continue; + } + + auto groupNames = child->GetProgramLine(params); + names.insert( + std::end(names), + std::make_move_iterator(std::begin(groupNames)), + std::make_move_iterator(std::end(groupNames))); + } + return names; + } + + virtual std::vector GetCommands() override + { + std::vector res; + for (const auto &child : Children()) + { + auto subparsers = child->GetCommands(); + res.insert(std::end(res), std::begin(subparsers), std::end(subparsers)); + } + return res; + } + + virtual bool IsGroup() const override + { + return true; + } + + virtual void Reset() noexcept override + { + Base::Reset(); + + for (auto &child: Children()) + { + child->Reset(); + } +#ifdef ARGS_NOEXCEPT + error = Error::None; + errorMsg.clear(); +#endif + } + +#ifdef ARGS_NOEXCEPT + /// Only for ARGS_NOEXCEPT + virtual Error GetError() const override + { + if (error != Error::None) + { + return error; + } + + auto it = std::find_if(Children().begin(), Children().end(), [](const Base *child){return child->GetError() != Error::None;}); + if (it == Children().end()) + { + return Error::None; + } else + { + return (*it)->GetError(); + } + } +#endif + + }; + + /** Class for using global options in ArgumentParser. + */ + class GlobalOptions : public Group + { + public: + GlobalOptions(Group &base, Base &options_) : Group(base, {}, Group::Validators::DontCare, Options::Global) + { + Add(options_); + } + }; + + /** Utility class for building subparsers with coroutines/callbacks. + * + * Brief example: + * \code + * Command command(argumentParser, "command", "my command", [](args::Subparser &s) + * { + * // your command flags/positionals + * s.Parse(); //required + * //your command code + * }); + * \endcode + * + * For ARGS_NOEXCEPT mode don't forget to check `s.GetError()` after `s.Parse()` + * and return if it isn't equals to args::Error::None. + * + * \sa Command + */ + class Subparser : public Group + { + private: + std::vector args; + std::vector kicked; + ArgumentParser *parser = nullptr; + const HelpParams &helpParams; + const Command &command; + bool isParsed = false; + + public: + Subparser(std::vector args_, ArgumentParser &parser_, const Command &command_, const HelpParams &helpParams_) + : args(std::move(args_)), parser(&parser_), helpParams(helpParams_), command(command_) + { + } + + Subparser(const Command &command_, const HelpParams &helpParams_) : helpParams(helpParams_), command(command_) + { + } + + Subparser(const Subparser&) = delete; + Subparser(Subparser&&) = delete; + Subparser &operator = (const Subparser&) = delete; + Subparser &operator = (Subparser&&) = delete; + + const Command &GetCommand() + { + return command; + } + + /** (INTERNAL) Determines whether Parse was called or not. + */ + bool IsParsed() const + { + return isParsed; + } + + /** Continue parsing arguments for new command. + */ + void Parse(); + + /** Returns a vector of kicked out arguments. + * + * \sa Base::KickOut + */ + const std::vector &KickedOut() const noexcept + { + return kicked; + } + }; + + /** Main class for building subparsers. + * + * /sa Subparser + */ + class Command : public Group + { + private: + friend class Subparser; + + std::string name; + std::string help; + std::string description; + std::string epilog; + std::string proglinePostfix; + + std::function parserCoroutine; + bool commandIsRequired = true; + Command *selectedCommand = nullptr; + + mutable std::vector> subparserDescription; + mutable std::vector subparserProgramLine; + mutable bool subparserHasFlag = false; + mutable bool subparserHasPositional = false; + mutable bool subparserHasCommand = false; +#ifdef ARGS_NOEXCEPT + mutable Error subparserError = Error::None; +#endif + mutable Subparser *subparser = nullptr; + + protected: + + class RaiiSubparser + { + public: + RaiiSubparser(ArgumentParser &parser_, std::vector args_); + RaiiSubparser(const Command &command_, const HelpParams ¶ms_); + + ~RaiiSubparser() + { + command.subparser = oldSubparser; + } + + Subparser &Parser() + { + return parser; + } + + private: + const Command &command; + Subparser parser; + Subparser *oldSubparser; + }; + + Command() = default; + + std::function &GetCoroutine() + { + return selectedCommand != nullptr ? selectedCommand->GetCoroutine() : parserCoroutine; + } + + Command &SelectedCommand() + { + Command *res = this; + while (res->selectedCommand != nullptr) + { + res = res->selectedCommand; + } + + return *res; + } + + const Command &SelectedCommand() const + { + const Command *res = this; + while (res->selectedCommand != nullptr) + { + res = res->selectedCommand; + } + + return *res; + } + + void UpdateSubparserHelp(const HelpParams ¶ms) const + { + if (parserCoroutine) + { + RaiiSubparser coro(*this, params); +#ifndef ARGS_NOEXCEPT + try + { + parserCoroutine(coro.Parser()); + } + catch (args::SubparserError&) + { + } +#else + parserCoroutine(coro.Parser()); +#endif + } + } + + public: + Command(Group &base_, std::string name_, std::string help_, std::function coroutine_ = {}) + : name(std::move(name_)), help(std::move(help_)), parserCoroutine(std::move(coroutine_)) + { + base_.Add(*this); + } + + /** The description that appears on the prog line after options + */ + const std::string &ProglinePostfix() const + { return proglinePostfix; } + + /** The description that appears on the prog line after options + */ + void ProglinePostfix(const std::string &proglinePostfix_) + { this->proglinePostfix = proglinePostfix_; } + + /** The description that appears above options + */ + const std::string &Description() const + { return description; } + /** The description that appears above options + */ + + void Description(const std::string &description_) + { this->description = description_; } + + /** The description that appears below options + */ + const std::string &Epilog() const + { return epilog; } + + /** The description that appears below options + */ + void Epilog(const std::string &epilog_) + { this->epilog = epilog_; } + + /** The name of command + */ + const std::string &Name() const + { return name; } + + /** The description of command + */ + const std::string &Help() const + { return help; } + + /** If value is true, parser will fail if no command was parsed. + * + * Default: true. + */ + void RequireCommand(bool value) + { commandIsRequired = value; } + + virtual bool IsGroup() const override + { return false; } + + virtual bool Matched() const noexcept override + { return Base::Matched(); } + + operator bool() const noexcept + { return Matched(); } + + void Match() noexcept + { matched = true; } + + void SelectCommand(Command *c) noexcept + { + selectedCommand = c; + + if (c != nullptr) + { + c->Match(); + } + } + + virtual FlagBase *Match(const EitherFlag &flag) override + { + if (selectedCommand != nullptr) + { + if (auto *res = selectedCommand->Match(flag)) + { + return res; + } + + for (auto *child: Children()) + { + if ((child->GetOptions() & Options::Global) != Options::None) + { + if (auto *res = child->Match(flag)) + { + return res; + } + } + } + + return nullptr; + } + + if (subparser != nullptr) + { + return subparser->Match(flag); + } + + return Matched() ? Group::Match(flag) : nullptr; + } + + virtual std::vector GetAllFlags() override + { + std::vector res; + + if (!Matched()) + { + return res; + } + + for (auto *child: Children()) + { + if (selectedCommand == nullptr || (child->GetOptions() & Options::Global) != Options::None) + { + auto childFlags = child->GetAllFlags(); + res.insert(res.end(), childFlags.begin(), childFlags.end()); + } + } + + if (selectedCommand != nullptr) + { + auto childFlags = selectedCommand->GetAllFlags(); + res.insert(res.end(), childFlags.begin(), childFlags.end()); + } + + if (subparser != nullptr) + { + auto childFlags = subparser->GetAllFlags(); + res.insert(res.end(), childFlags.begin(), childFlags.end()); + } + + return res; + } + + virtual PositionalBase *GetNextPositional() override + { + if (selectedCommand != nullptr) + { + if (auto *res = selectedCommand->GetNextPositional()) + { + return res; + } + + for (auto *child: Children()) + { + if ((child->GetOptions() & Options::Global) != Options::None) + { + if (auto *res = child->GetNextPositional()) + { + return res; + } + } + } + + return nullptr; + } + + if (subparser != nullptr) + { + return subparser->GetNextPositional(); + } + + return Matched() ? Group::GetNextPositional() : nullptr; + } + + virtual bool HasFlag() const override + { + return subparserHasFlag || Group::HasFlag(); + } + + virtual bool HasPositional() const override + { + return subparserHasPositional || Group::HasPositional(); + } + + virtual bool HasCommand() const override + { + return true; + } + + std::vector GetCommandProgramLine(const HelpParams ¶ms) const + { + UpdateSubparserHelp(params); + + auto res = Group::GetProgramLine(params); + res.insert(res.end(), subparserProgramLine.begin(), subparserProgramLine.end()); + + if (!params.proglineCommand.empty() && (Group::HasCommand() || subparserHasCommand)) + { + res.insert(res.begin(), commandIsRequired ? params.proglineCommand : "[" + params.proglineCommand + "]"); + } + + if (!Name().empty()) + { + res.insert(res.begin(), Name()); + } + + if ((subparserHasFlag || Group::HasFlag()) && params.showProglineOptions && !params.proglineShowFlags) + { + res.push_back(params.proglineOptions); + } + + if (!ProglinePostfix().empty()) + { + std::string line; + for (char c : ProglinePostfix()) + { + if (isspace(c)) + { + if (!line.empty()) + { + res.push_back(line); + line.clear(); + } + + if (c == '\n') + { + res.push_back("\n"); + } + } + else + { + line += c; + } + } + + if (!line.empty()) + { + res.push_back(line); + } + } + + return res; + } + + virtual std::vector GetProgramLine(const HelpParams ¶ms) const override + { + if (!Matched()) + { + return {}; + } + + return GetCommandProgramLine(params); + } + + virtual std::vector GetCommands() override + { + if (selectedCommand != nullptr) + { + return selectedCommand->GetCommands(); + } + + if (Matched()) + { + return Group::GetCommands(); + } + + return { this }; + } + + virtual std::vector> GetDescription(const HelpParams ¶ms, const unsigned int indent) const override + { + std::vector> descriptions; + unsigned addindent = 0; + + UpdateSubparserHelp(params); + + if (!Matched()) + { + if (params.showCommandFullHelp) + { + std::ostringstream s; + bool empty = true; + for (const auto &progline: GetCommandProgramLine(params)) + { + if (!empty) + { + s << ' '; + } + else + { + empty = false; + } + + s << progline; + } + + descriptions.emplace_back(s.str(), "", indent); + } + else + { + descriptions.emplace_back(Name(), help, indent); + } + + if (!params.showCommandChildren && !params.showCommandFullHelp) + { + return descriptions; + } + + addindent = 1; + } + + if (params.showCommandFullHelp && !Matched()) + { + descriptions.emplace_back("", "", indent + addindent); + descriptions.emplace_back(Description().empty() ? Help() : Description(), "", indent + addindent); + descriptions.emplace_back("", "", indent + addindent); + } + + for (Base *child: Children()) + { + if ((child->GetOptions() & Options::HiddenFromDescription) != Options::None) + { + continue; + } + + auto groupDescriptions = child->GetDescription(params, indent + addindent); + descriptions.insert( + std::end(descriptions), + std::make_move_iterator(std::begin(groupDescriptions)), + std::make_move_iterator(std::end(groupDescriptions))); + } + + for (auto childDescription: subparserDescription) + { + std::get<2>(childDescription) += indent + addindent; + descriptions.push_back(std::move(childDescription)); + } + + if (params.showCommandFullHelp && !Matched()) + { + descriptions.emplace_back("", "", indent + addindent); + if (!Epilog().empty()) + { + descriptions.emplace_back(Epilog(), "", indent + addindent); + descriptions.emplace_back("", "", indent + addindent); + } + } + + return descriptions; + } + + virtual void Validate(const std::string &shortprefix, const std::string &longprefix) const override + { + if (!Matched()) + { + return; + } + + for (Base *child: Children()) + { + if (child->IsGroup() && !child->Matched()) + { + std::ostringstream problem; + problem << "Group validation failed somewhere!"; +#ifdef ARGS_NOEXCEPT + error = Error::Validation; + errorMsg = problem.str(); +#else + throw ValidationError(problem.str()); +#endif + } + + child->Validate(shortprefix, longprefix); + } + + if (subparser != nullptr) + { + subparser->Validate(shortprefix, longprefix); + } + + if (selectedCommand == nullptr && commandIsRequired && (Group::HasCommand() || subparserHasCommand)) + { + std::ostringstream problem; + problem << "Command is required"; +#ifdef ARGS_NOEXCEPT + error = Error::Validation; + errorMsg = problem.str(); +#else + throw ValidationError(problem.str()); +#endif + } + } + + virtual void Reset() noexcept override + { + Group::Reset(); + selectedCommand = nullptr; + subparserProgramLine.clear(); + subparserDescription.clear(); + subparserHasFlag = false; + subparserHasPositional = false; + subparserHasCommand = false; +#ifdef ARGS_NOEXCEPT + subparserError = Error::None; +#endif + } + +#ifdef ARGS_NOEXCEPT + /// Only for ARGS_NOEXCEPT + virtual Error GetError() const override + { + if (!Matched()) + { + return Error::None; + } + + if (error != Error::None) + { + return error; + } + + if (subparserError != Error::None) + { + return subparserError; + } + + return Group::GetError(); + } +#endif + }; + + /** The main user facing command line argument parser class + */ + class ArgumentParser : public Command + { + friend class Subparser; + + private: + std::string longprefix; + std::string shortprefix; + + std::string longseparator; + + std::string terminator; + + bool allowJoinedShortValue = true; + bool allowJoinedLongValue = true; + bool allowSeparateShortValue = true; + bool allowSeparateLongValue = true; + + CompletionFlag *completion = nullptr; + bool readCompletion = false; + + protected: + enum class OptionType + { + LongFlag, + ShortFlag, + Positional + }; + + OptionType ParseOption(const std::string &s, bool allowEmpty = false) + { + if (s.find(longprefix) == 0 && (allowEmpty || s.length() > longprefix.length())) + { + return OptionType::LongFlag; + } + + if (s.find(shortprefix) == 0 && (allowEmpty || s.length() > shortprefix.length())) + { + return OptionType::ShortFlag; + } + + return OptionType::Positional; + } + + template + bool Complete(FlagBase &flag, It it, It end) + { + auto nextIt = it; + if (!readCompletion || (++nextIt != end)) + { + return false; + } + + const auto &chunk = *it; + for (auto &choice : flag.HelpChoices(helpParams)) + { + AddCompletionReply(chunk, choice); + } + +#ifndef ARGS_NOEXCEPT + throw Completion(completion->Get()); +#else + return true; +#endif + } + + /** (INTERNAL) Parse flag's values + * + * \param arg The string to display in error message as a flag name + * \param[in, out] it The iterator to first value. It will point to the last value + * \param end The end iterator + * \param joinedArg Joined value (e.g. bar in --foo=bar) + * \param canDiscardJoined If true joined value can be parsed as flag not as a value (as in -abcd) + * \param[out] values The vector to store parsed arg's values + */ + template + std::string ParseArgsValues(FlagBase &flag, const std::string &arg, It &it, It end, + const bool allowSeparate, const bool allowJoined, + const bool hasJoined, const std::string &joinedArg, + const bool canDiscardJoined, std::vector &values) + { + values.clear(); + + Nargs nargs = flag.NumberOfArguments(); + + if (hasJoined && !allowJoined && nargs.min != 0) + { + return "Flag '" + arg + "' was passed a joined argument, but these are disallowed"; + } + + if (hasJoined) + { + if (!canDiscardJoined || nargs.max != 0) + { + values.push_back(joinedArg); + } + } else if (!allowSeparate) + { + if (nargs.min != 0) + { + return "Flag '" + arg + "' was passed a separate argument, but these are disallowed"; + } + } else + { + auto valueIt = it; + ++valueIt; + + while (valueIt != end && + values.size() < nargs.max && + (nargs.min == nargs.max || ParseOption(*valueIt) == OptionType::Positional)) + { + if (Complete(flag, valueIt, end)) + { + it = end; + return ""; + } + + values.push_back(*valueIt); + ++it; + ++valueIt; + } + } + + if (values.size() > nargs.max) + { + return "Passed an argument into a non-argument flag: " + arg; + } else if (values.size() < nargs.min) + { + if (nargs.min == 1 && nargs.max == 1) + { + return "Flag '" + arg + "' requires an argument but received none"; + } else if (nargs.min == 1) + { + return "Flag '" + arg + "' requires at least one argument but received none"; + } else if (nargs.min != nargs.max) + { + return "Flag '" + arg + "' requires at least " + std::to_string(nargs.min) + + " arguments but received " + std::to_string(values.size()); + } else + { + return "Flag '" + arg + "' requires " + std::to_string(nargs.min) + + " arguments but received " + std::to_string(values.size()); + } + } + + return {}; + } + + template + bool ParseLong(It &it, It end) + { + const auto &chunk = *it; + const auto argchunk = chunk.substr(longprefix.size()); + // Try to separate it, in case of a separator: + const auto separator = longseparator.empty() ? argchunk.npos : argchunk.find(longseparator); + // If the separator is in the argument, separate it. + const auto arg = (separator != argchunk.npos ? + std::string(argchunk, 0, separator) + : argchunk); + const auto joined = (separator != argchunk.npos ? + argchunk.substr(separator + longseparator.size()) + : std::string()); + + if (auto flag = Match(arg)) + { + std::vector values; + const std::string errorMessage = ParseArgsValues(*flag, arg, it, end, allowSeparateLongValue, allowJoinedLongValue, + separator != argchunk.npos, joined, false, values); + if (!errorMessage.empty()) + { +#ifndef ARGS_NOEXCEPT + throw ParseError(errorMessage); +#else + error = Error::Parse; + errorMsg = errorMessage; + return false; +#endif + } + + if (!readCompletion) + { + flag->ParseValue(values); + } + + if (flag->KickOut()) + { + ++it; + return false; + } + } else + { + const std::string errorMessage("Flag could not be matched: " + arg); +#ifndef ARGS_NOEXCEPT + throw ParseError(errorMessage); +#else + error = Error::Parse; + errorMsg = errorMessage; + return false; +#endif + } + + return true; + } + + template + bool ParseShort(It &it, It end) + { + const auto &chunk = *it; + const auto argchunk = chunk.substr(shortprefix.size()); + for (auto argit = std::begin(argchunk); argit != std::end(argchunk); ++argit) + { + const auto arg = *argit; + + if (auto flag = Match(arg)) + { + const std::string value(argit + 1, std::end(argchunk)); + std::vector values; + const std::string errorMessage = ParseArgsValues(*flag, std::string(1, arg), it, end, + allowSeparateShortValue, allowJoinedShortValue, + !value.empty(), value, !value.empty(), values); + + if (!errorMessage.empty()) + { +#ifndef ARGS_NOEXCEPT + throw ParseError(errorMessage); +#else + error = Error::Parse; + errorMsg = errorMessage; + return false; +#endif + } + + if (!readCompletion) + { + flag->ParseValue(values); + } + + if (flag->KickOut()) + { + ++it; + return false; + } + + if (!values.empty()) + { + break; + } + } else + { + const std::string errorMessage("Flag could not be matched: '" + std::string(1, arg) + "'"); +#ifndef ARGS_NOEXCEPT + throw ParseError(errorMessage); +#else + error = Error::Parse; + errorMsg = errorMessage; + return false; +#endif + } + } + + return true; + } + + bool AddCompletionReply(const std::string &cur, const std::string &choice) + { + if (cur.empty() || choice.find(cur) == 0) + { + if (completion->syntax == "bash" && ParseOption(choice) == OptionType::LongFlag && choice.find(longseparator) != std::string::npos) + { + completion->reply.push_back(choice.substr(choice.find(longseparator) + 1)); + } else + { + completion->reply.push_back(choice); + } + return true; + } + + return false; + } + + template + bool Complete(It it, It end) + { + auto nextIt = it; + if (!readCompletion || (++nextIt != end)) + { + return false; + } + + const auto &chunk = *it; + auto pos = GetNextPositional(); + std::vector commands = GetCommands(); + const auto optionType = ParseOption(chunk, true); + + if (!commands.empty() && (chunk.empty() || optionType == OptionType::Positional)) + { + for (auto &cmd : commands) + { + if ((cmd->GetOptions() & Options::HiddenFromCompletion) == Options::None) + { + AddCompletionReply(chunk, cmd->Name()); + } + } + } else + { + bool hasPositionalCompletion = true; + + if (!commands.empty()) + { + for (auto &cmd : commands) + { + if ((cmd->GetOptions() & Options::HiddenFromCompletion) == Options::None) + { + AddCompletionReply(chunk, cmd->Name()); + } + } + } else if (pos) + { + if ((pos->GetOptions() & Options::HiddenFromCompletion) == Options::None) + { + auto choices = pos->HelpChoices(helpParams); + hasPositionalCompletion = !choices.empty() || optionType != OptionType::Positional; + for (auto &choice : choices) + { + AddCompletionReply(chunk, choice); + } + } + } + + if (hasPositionalCompletion) + { + auto flags = GetAllFlags(); + for (auto flag : flags) + { + if ((flag->GetOptions() & Options::HiddenFromCompletion) != Options::None) + { + continue; + } + + auto &matcher = flag->GetMatcher(); + if (!AddCompletionReply(chunk, matcher.GetShortOrAny().str(shortprefix, longprefix))) + { + for (auto &flagName : matcher.GetFlagStrings()) + { + if (AddCompletionReply(chunk, flagName.str(shortprefix, longprefix))) + { + break; + } + } + } + } + + if (optionType == OptionType::LongFlag && allowJoinedLongValue) + { + const auto separator = longseparator.empty() ? chunk.npos : chunk.find(longseparator); + if (separator != chunk.npos) + { + std::string arg(chunk, 0, separator); + if (auto flag = this->Match(arg.substr(longprefix.size()))) + { + for (auto &choice : flag->HelpChoices(helpParams)) + { + AddCompletionReply(chunk, arg + longseparator + choice); + } + } + } + } else if (optionType == OptionType::ShortFlag && allowJoinedShortValue) + { + if (chunk.size() > shortprefix.size() + 1) + { + auto arg = chunk.at(shortprefix.size()); + //TODO: support -abcVALUE where a and b take no value + if (auto flag = this->Match(arg)) + { + for (auto &choice : flag->HelpChoices(helpParams)) + { + AddCompletionReply(chunk, shortprefix + arg + choice); + } + } + } + } + } + } + +#ifndef ARGS_NOEXCEPT + throw Completion(completion->Get()); +#else + return true; +#endif + } + + template + It Parse(It begin, It end) + { + bool terminated = false; + std::vector commands = GetCommands(); + + // Check all arg chunks + for (auto it = begin; it != end; ++it) + { + if (Complete(it, end)) + { + return end; + } + + const auto &chunk = *it; + + if (!terminated && chunk == terminator) + { + terminated = true; + } else if (!terminated && ParseOption(chunk) == OptionType::LongFlag) + { + if (!ParseLong(it, end)) + { + return it; + } + } else if (!terminated && ParseOption(chunk) == OptionType::ShortFlag) + { + if (!ParseShort(it, end)) + { + return it; + } + } else if (!terminated && !commands.empty()) + { + auto itCommand = std::find_if(commands.begin(), commands.end(), [&chunk](Command *c) { return c->Name() == chunk; }); + if (itCommand == commands.end()) + { + const std::string errorMessage("Unknown command: " + chunk); +#ifndef ARGS_NOEXCEPT + throw ParseError(errorMessage); +#else + error = Error::Parse; + errorMsg = errorMessage; + return it; +#endif + } + + SelectCommand(*itCommand); + + if (const auto &coroutine = GetCoroutine()) + { + ++it; + RaiiSubparser coro(*this, std::vector(it, end)); + coroutine(coro.Parser()); +#ifdef ARGS_NOEXCEPT + error = GetError(); + if (error != Error::None) + { + return end; + } + + if (!coro.Parser().IsParsed()) + { + error = Error::Usage; + return end; + } +#else + if (!coro.Parser().IsParsed()) + { + throw UsageError("Subparser::Parse was not called"); + } +#endif + + break; + } + + commands = GetCommands(); + } else + { + auto pos = GetNextPositional(); + if (pos) + { + pos->ParseValue(chunk); + + if (pos->KickOut()) + { + return ++it; + } + } else + { + const std::string errorMessage("Passed in argument, but no positional arguments were ready to receive it: " + chunk); +#ifndef ARGS_NOEXCEPT + throw ParseError(errorMessage); +#else + error = Error::Parse; + errorMsg = errorMessage; + return it; +#endif + } + } + + if (!readCompletion && completion != nullptr && completion->Matched()) + { +#ifdef ARGS_NOEXCEPT + error = Error::Completion; +#endif + readCompletion = true; + ++it; + const auto argsLeft = static_cast(std::distance(it, end)); + if (completion->cword == 0 || argsLeft <= 1 || completion->cword >= argsLeft) + { +#ifndef ARGS_NOEXCEPT + throw Completion(""); +#endif + } + + std::vector curArgs(++it, end); + curArgs.resize(completion->cword); + + if (completion->syntax == "bash") + { + // bash tokenizes --flag=value as --flag=value + for (size_t idx = 0; idx < curArgs.size(); ) + { + if (idx > 0 && curArgs[idx] == "=") + { + curArgs[idx - 1] += "="; + // Avoid warnings from -Wsign-conversion + const auto signedIdx = static_cast(idx); + if (idx + 1 < curArgs.size()) + { + curArgs[idx - 1] += curArgs[idx + 1]; + curArgs.erase(curArgs.begin() + signedIdx, curArgs.begin() + signedIdx + 2); + } else + { + curArgs.erase(curArgs.begin() + signedIdx); + } + } else + { + ++idx; + } + } + + } +#ifndef ARGS_NOEXCEPT + try + { + Parse(curArgs.begin(), curArgs.end()); + throw Completion(""); + } + catch (Completion &) + { + throw; + } + catch (args::Error&) + { + throw Completion(""); + } +#else + return Parse(curArgs.begin(), curArgs.end()); +#endif + } + } + + Validate(shortprefix, longprefix); + return end; + } + + public: + HelpParams helpParams; + + ArgumentParser(const std::string &description_, const std::string &epilog_ = std::string()) + { + Description(description_); + Epilog(epilog_); + LongPrefix("--"); + ShortPrefix("-"); + LongSeparator("="); + Terminator("--"); + SetArgumentSeparations(true, true, true, true); + matched = true; + } + + void AddCompletion(CompletionFlag &completionFlag) + { + completion = &completionFlag; + Add(completionFlag); + } + + /** The program name for help generation + */ + const std::string &Prog() const + { return helpParams.programName; } + /** The program name for help generation + */ + void Prog(const std::string &prog_) + { this->helpParams.programName = prog_; } + + /** The prefix for long flags + */ + const std::string &LongPrefix() const + { return longprefix; } + /** The prefix for long flags + */ + void LongPrefix(const std::string &longprefix_) + { + this->longprefix = longprefix_; + this->helpParams.longPrefix = longprefix_; + } + + /** The prefix for short flags + */ + const std::string &ShortPrefix() const + { return shortprefix; } + /** The prefix for short flags + */ + void ShortPrefix(const std::string &shortprefix_) + { + this->shortprefix = shortprefix_; + this->helpParams.shortPrefix = shortprefix_; + } + + /** The separator for long flags + */ + const std::string &LongSeparator() const + { return longseparator; } + /** The separator for long flags + */ + void LongSeparator(const std::string &longseparator_) + { + if (longseparator_.empty()) + { + const std::string errorMessage("longseparator can not be set to empty"); +#ifdef ARGS_NOEXCEPT + error = Error::Usage; + errorMsg = errorMessage; +#else + throw UsageError(errorMessage); +#endif + } else + { + this->longseparator = longseparator_; + this->helpParams.longSeparator = allowJoinedLongValue ? longseparator_ : " "; + } + } + + /** The terminator that forcibly separates flags from positionals + */ + const std::string &Terminator() const + { return terminator; } + /** The terminator that forcibly separates flags from positionals + */ + void Terminator(const std::string &terminator_) + { this->terminator = terminator_; } + + /** Get the current argument separation parameters. + * + * See SetArgumentSeparations for details on what each one means. + */ + void GetArgumentSeparations( + bool &allowJoinedShortValue_, + bool &allowJoinedLongValue_, + bool &allowSeparateShortValue_, + bool &allowSeparateLongValue_) const + { + allowJoinedShortValue_ = this->allowJoinedShortValue; + allowJoinedLongValue_ = this->allowJoinedLongValue; + allowSeparateShortValue_ = this->allowSeparateShortValue; + allowSeparateLongValue_ = this->allowSeparateLongValue; + } + + /** Change allowed option separation. + * + * \param allowJoinedShortValue_ Allow a short flag that accepts an argument to be passed its argument immediately next to it (ie. in the same argv field) + * \param allowJoinedLongValue_ Allow a long flag that accepts an argument to be passed its argument separated by the longseparator (ie. in the same argv field) + * \param allowSeparateShortValue_ Allow a short flag that accepts an argument to be passed its argument separated by whitespace (ie. in the next argv field) + * \param allowSeparateLongValue_ Allow a long flag that accepts an argument to be passed its argument separated by whitespace (ie. in the next argv field) + */ + void SetArgumentSeparations( + const bool allowJoinedShortValue_, + const bool allowJoinedLongValue_, + const bool allowSeparateShortValue_, + const bool allowSeparateLongValue_) + { + this->allowJoinedShortValue = allowJoinedShortValue_; + this->allowJoinedLongValue = allowJoinedLongValue_; + this->allowSeparateShortValue = allowSeparateShortValue_; + this->allowSeparateLongValue = allowSeparateLongValue_; + + this->helpParams.longSeparator = allowJoinedLongValue ? longseparator : " "; + this->helpParams.shortSeparator = allowJoinedShortValue ? "" : " "; + } + + /** Pass the help menu into an ostream + */ + void Help(std::ostream &help_) const + { + auto &command = SelectedCommand(); + const auto &commandDescription = command.Description().empty() ? command.Help() : command.Description(); + const auto description_text = Wrap(commandDescription, helpParams.width - helpParams.descriptionindent); + const auto epilog_text = Wrap(command.Epilog(), helpParams.width - helpParams.descriptionindent); + + const bool hasoptions = command.HasFlag(); + const bool hasarguments = command.HasPositional(); + + std::vector prognameline; + prognameline.push_back(helpParams.usageString); + prognameline.push_back(Prog()); + auto commandProgLine = command.GetProgramLine(helpParams); + prognameline.insert(prognameline.end(), commandProgLine.begin(), commandProgLine.end()); + + const auto proglines = Wrap(prognameline.begin(), prognameline.end(), + helpParams.width - (helpParams.progindent + helpParams.progtailindent), + helpParams.width - helpParams.progindent); + auto progit = std::begin(proglines); + if (progit != std::end(proglines)) + { + help_ << std::string(helpParams.progindent, ' ') << *progit << '\n'; + ++progit; + } + for (; progit != std::end(proglines); ++progit) + { + help_ << std::string(helpParams.progtailindent, ' ') << *progit << '\n'; + } + + help_ << '\n'; + + if (!description_text.empty()) + { + for (const auto &line: description_text) + { + help_ << std::string(helpParams.descriptionindent, ' ') << line << "\n"; + } + help_ << "\n"; + } + + bool lastDescriptionIsNewline = false; + + if (!helpParams.optionsString.empty()) + { + help_ << std::string(helpParams.progindent, ' ') << helpParams.optionsString << "\n\n"; + } + + for (const auto &desc: command.GetDescription(helpParams, 0)) + { + lastDescriptionIsNewline = std::get<0>(desc).empty() && std::get<1>(desc).empty(); + const auto groupindent = std::get<2>(desc) * helpParams.eachgroupindent; + const auto flags = Wrap(std::get<0>(desc), helpParams.width - (helpParams.flagindent + helpParams.helpindent + helpParams.gutter)); + const auto info = Wrap(std::get<1>(desc), helpParams.width - (helpParams.helpindent + groupindent)); + + std::string::size_type flagssize = 0; + for (auto flagsit = std::begin(flags); flagsit != std::end(flags); ++flagsit) + { + if (flagsit != std::begin(flags)) + { + help_ << '\n'; + } + help_ << std::string(groupindent + helpParams.flagindent, ' ') << *flagsit; + flagssize = Glyphs(*flagsit); + } + + auto infoit = std::begin(info); + // groupindent is on both sides of this inequality, and therefore can be removed + if ((helpParams.flagindent + flagssize + helpParams.gutter) > helpParams.helpindent || infoit == std::end(info) || helpParams.addNewlineBeforeDescription) + { + help_ << '\n'; + } else + { + // groupindent is on both sides of the minus sign, and therefore doesn't actually need to be in here + help_ << std::string(helpParams.helpindent - (helpParams.flagindent + flagssize), ' ') << *infoit << '\n'; + ++infoit; + } + for (; infoit != std::end(info); ++infoit) + { + help_ << std::string(groupindent + helpParams.helpindent, ' ') << *infoit << '\n'; + } + } + if (hasoptions && hasarguments && helpParams.showTerminator) + { + lastDescriptionIsNewline = false; + for (const auto &item: Wrap(std::string("\"") + terminator + "\" can be used to terminate flag options and force all following arguments to be treated as positional options", helpParams.width - helpParams.flagindent)) + { + help_ << std::string(helpParams.flagindent, ' ') << item << '\n'; + } + } + + if (!lastDescriptionIsNewline) + { + help_ << "\n"; + } + + for (const auto &line: epilog_text) + { + help_ << std::string(helpParams.descriptionindent, ' ') << line << "\n"; + } + } + + /** Generate a help menu as a string. + * + * \return the help text as a single string + */ + std::string Help() const + { + std::ostringstream help_; + Help(help_); + return help_.str(); + } + + virtual void Reset() noexcept override + { + Command::Reset(); + matched = true; + readCompletion = false; + } + + /** Parse all arguments. + * + * \param begin an iterator to the beginning of the argument list + * \param end an iterator to the past-the-end element of the argument list + * \return the iterator after the last parsed value. Only useful for kick-out + */ + template + It ParseArgs(It begin, It end) + { + // Reset all Matched statuses and errors + Reset(); +#ifdef ARGS_NOEXCEPT + error = GetError(); + if (error != Error::None) + { + return end; + } +#endif + return Parse(begin, end); + } + + /** Parse all arguments. + * + * \param args an iterable of the arguments + * \return the iterator after the last parsed value. Only useful for kick-out + */ + template + auto ParseArgs(const T &args) -> decltype(std::begin(args)) + { + return ParseArgs(std::begin(args), std::end(args)); + } + + /** Convenience function to parse the CLI from argc and argv + * + * Just assigns the program name and vectorizes arguments for passing into ParseArgs() + * + * \return whether or not all arguments were parsed. This works for detecting kick-out, but is generally useless as it can't do anything with it. + */ + bool ParseCLI(const int argc, const char * const * argv) + { + if (Prog().empty()) + { + Prog(argv[0]); + } + const std::vector args(argv + 1, argv + argc); + return ParseArgs(args) == std::end(args); + } + + template + bool ParseCLI(const T &args) + { + return ParseArgs(args) == std::end(args); + } + }; + + inline Command::RaiiSubparser::RaiiSubparser(ArgumentParser &parser_, std::vector args_) + : command(parser_.SelectedCommand()), parser(std::move(args_), parser_, command, parser_.helpParams), oldSubparser(command.subparser) + { + command.subparser = &parser; + } + + inline Command::RaiiSubparser::RaiiSubparser(const Command &command_, const HelpParams ¶ms_): command(command_), parser(command, params_), oldSubparser(command.subparser) + { + command.subparser = &parser; + } + + inline void Subparser::Parse() + { + isParsed = true; + Reset(); + command.subparserDescription = GetDescription(helpParams, 0); + command.subparserHasFlag = HasFlag(); + command.subparserHasPositional = HasPositional(); + command.subparserHasCommand = HasCommand(); + command.subparserProgramLine = GetProgramLine(helpParams); + if (parser == nullptr) + { +#ifndef ARGS_NOEXCEPT + throw args::SubparserError(); +#else + error = Error::Subparser; + return; +#endif + } + + auto it = parser->Parse(args.begin(), args.end()); + command.Validate(parser->ShortPrefix(), parser->LongPrefix()); + kicked.assign(it, args.end()); + +#ifdef ARGS_NOEXCEPT + command.subparserError = GetError(); +#endif + } + + inline std::ostream &operator<<(std::ostream &os, const ArgumentParser &parser) + { + parser.Help(os); + return os; + } + + /** Boolean argument matcher + */ + class Flag : public FlagBase + { + public: + Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_): FlagBase(name_, help_, std::move(matcher_), options_) + { + group_.Add(*this); + } + + Flag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const bool extraError_ = false): Flag(group_, name_, help_, std::move(matcher_), extraError_ ? Options::Single : Options::None) + { + } + + virtual ~Flag() {} + + /** Get whether this was matched + */ + bool Get() const + { + return Matched(); + } + + virtual Nargs NumberOfArguments() const noexcept override + { + return 0; + } + + virtual void ParseValue(const std::vector&) override + { + } + }; + + /** Help flag class + * + * Works like a regular flag, but throws an instance of Help when it is matched + */ + class HelpFlag : public Flag + { + public: + HelpFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_ = {}): Flag(group_, name_, help_, std::move(matcher_), options_) {} + + virtual ~HelpFlag() {} + + virtual void ParseValue(const std::vector &) + { +#ifdef ARGS_NOEXCEPT + error = Error::Help; + errorMsg = Name(); +#else + throw Help(Name()); +#endif + } + + /** Get whether this was matched + */ + bool Get() const noexcept + { + return Matched(); + } + }; + + /** A flag class that simply counts the number of times it's matched + */ + class CounterFlag : public Flag + { + private: + const int startcount; + int count; + + public: + CounterFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const int startcount_ = 0, Options options_ = {}): + Flag(group_, name_, help_, std::move(matcher_), options_), startcount(startcount_), count(startcount_) {} + + virtual ~CounterFlag() {} + + virtual FlagBase *Match(const EitherFlag &arg) override + { + auto me = FlagBase::Match(arg); + if (me) + { + ++count; + } + return me; + } + + /** Get the count + */ + int &Get() noexcept + { + return count; + } + + virtual void Reset() noexcept override + { + FlagBase::Reset(); + count = startcount; + } + }; + + /** A flag class that calls a function when it's matched + */ + class ActionFlag : public FlagBase + { + private: + std::function &)> action; + Nargs nargs; + + public: + ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Nargs nargs_, std::function &)> action_, Options options_ = {}): + FlagBase(name_, help_, std::move(matcher_), options_), action(std::move(action_)), nargs(nargs_) + { + group_.Add(*this); + } + + ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, std::function action_, Options options_ = {}): + FlagBase(name_, help_, std::move(matcher_), options_), nargs(1) + { + group_.Add(*this); + action = [action_](const std::vector &a) { return action_(a.at(0)); }; + } + + ActionFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, std::function action_, Options options_ = {}): + FlagBase(name_, help_, std::move(matcher_), options_), nargs(0) + { + group_.Add(*this); + action = [action_](const std::vector &) { return action_(); }; + } + + virtual Nargs NumberOfArguments() const noexcept override + { return nargs; } + + virtual void ParseValue(const std::vector &value) override + { action(value); } + }; + + /** A default Reader class for argument classes + * + * If destination type is assignable to std::string it uses an assignment to std::string. + * Otherwise ValueReader simply uses a std::istringstream to read into the destination type, and + * raises a ParseError if there are any characters left. + */ + struct ValueReader + { + template + typename std::enable_if::value, bool>::type + operator ()(const std::string &name, const std::string &value, T &destination) + { + std::istringstream ss(value); + ss >> destination >> std::ws; + + if (ss.rdbuf()->in_avail() > 0) + { +#ifdef ARGS_NOEXCEPT + (void)name; + return false; +#else + std::ostringstream problem; + problem << "Argument '" << name << "' received invalid value type '" << value << "'"; + throw ParseError(problem.str()); +#endif + } + return true; + } + + template + typename std::enable_if::value, bool>::type + operator()(const std::string &, const std::string &value, T &destination) + { + destination = value; + return true; + } + }; + + /** An argument-accepting flag class + * + * \tparam T the type to extract the argument as + * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) + */ + template < + typename T, + typename Reader = ValueReader> + class ValueFlag : public ValueFlagBase + { + protected: + T value; + T defaultValue; + + virtual std::string GetDefaultString(const HelpParams&) const override + { + return detail::ToString(defaultValue); + } + + private: + Reader reader; + + public: + + ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_, Options options_): ValueFlagBase(name_, help_, std::move(matcher_), options_), value(defaultValue_), defaultValue(defaultValue_) + { + group_.Add(*this); + } + + ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_ = T(), const bool extraError_ = false): ValueFlag(group_, name_, help_, std::move(matcher_), defaultValue_, extraError_ ? Options::Single : Options::None) + { + } + + ValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_): ValueFlag(group_, name_, help_, std::move(matcher_), T(), options_) + { + } + + virtual ~ValueFlag() {} + + virtual void ParseValue(const std::vector &values_) override + { + const std::string &value_ = values_.at(0); + +#ifdef ARGS_NOEXCEPT + if (!reader(name, value_, this->value)) + { + error = Error::Parse; + } +#else + reader(name, value_, this->value); +#endif + } + + virtual void Reset() noexcept override + { + ValueFlagBase::Reset(); + value = defaultValue; + } + + /** Get the value + */ + T &Get() noexcept + { + return value; + } + + /** Get the default value + */ + const T &GetDefault() noexcept + { + return defaultValue; + } + }; + + /** An optional argument-accepting flag class + * + * \tparam T the type to extract the argument as + * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) + */ + template < + typename T, + typename Reader = ValueReader> + class ImplicitValueFlag : public ValueFlag + { + protected: + T implicitValue; + + public: + + ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &implicitValue_, const T &defaultValue_ = T(), Options options_ = {}) + : ValueFlag(group_, name_, help_, std::move(matcher_), defaultValue_, options_), implicitValue(implicitValue_) + { + } + + ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const T &defaultValue_ = T(), Options options_ = {}) + : ValueFlag(group_, name_, help_, std::move(matcher_), defaultValue_, options_), implicitValue(defaultValue_) + { + } + + ImplicitValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Options options_) + : ValueFlag(group_, name_, help_, std::move(matcher_), {}, options_), implicitValue() + { + } + + virtual ~ImplicitValueFlag() {} + + virtual Nargs NumberOfArguments() const noexcept override + { + return {0, 1}; + } + + virtual void ParseValue(const std::vector &value_) override + { + if (value_.empty()) + { + this->value = implicitValue; + } else + { + ValueFlag::ParseValue(value_); + } + } + }; + + /** A variadic arguments accepting flag class + * + * \tparam T the type to extract the argument as + * \tparam List the list type that houses the values + * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) + */ + template < + typename T, + template class List = std::vector, + typename Reader = ValueReader> + class NargsValueFlag : public FlagBase + { + protected: + + List values; + const List defaultValues; + Nargs nargs; + Reader reader; + + public: + + typedef List Container; + typedef T value_type; + typedef typename Container::allocator_type allocator_type; + typedef typename Container::pointer pointer; + typedef typename Container::const_pointer const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef typename Container::size_type size_type; + typedef typename Container::difference_type difference_type; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + NargsValueFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, Nargs nargs_, const List &defaultValues_ = {}, Options options_ = {}) + : FlagBase(name_, help_, std::move(matcher_), options_), values(defaultValues_), defaultValues(defaultValues_),nargs(nargs_) + { + group_.Add(*this); + } + + virtual ~NargsValueFlag() {} + + virtual Nargs NumberOfArguments() const noexcept override + { + return nargs; + } + + virtual void ParseValue(const std::vector &values_) override + { + values.clear(); + + for (const std::string &value : values_) + { + T v; +#ifdef ARGS_NOEXCEPT + if (!reader(name, value, v)) + { + error = Error::Parse; + } +#else + reader(name, value, v); +#endif + values.insert(std::end(values), v); + } + } + + List &Get() noexcept + { + return values; + } + + iterator begin() noexcept + { + return values.begin(); + } + + const_iterator begin() const noexcept + { + return values.begin(); + } + + const_iterator cbegin() const noexcept + { + return values.cbegin(); + } + + iterator end() noexcept + { + return values.end(); + } + + const_iterator end() const noexcept + { + return values.end(); + } + + const_iterator cend() const noexcept + { + return values.cend(); + } + + virtual void Reset() noexcept override + { + FlagBase::Reset(); + values = defaultValues; + } + + virtual FlagBase *Match(const EitherFlag &arg) override + { + const bool wasMatched = Matched(); + auto me = FlagBase::Match(arg); + if (me && !wasMatched) + { + values.clear(); + } + return me; + } + }; + + /** An argument-accepting flag class that pushes the found values into a list + * + * \tparam T the type to extract the argument as + * \tparam List the list type that houses the values + * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) + */ + template < + typename T, + template class List = std::vector, + typename Reader = ValueReader> + class ValueFlagList : public ValueFlagBase + { + private: + using Container = List; + Container values; + const Container defaultValues; + Reader reader; + + public: + + typedef T value_type; + typedef typename Container::allocator_type allocator_type; + typedef typename Container::pointer pointer; + typedef typename Container::const_pointer const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef typename Container::size_type size_type; + typedef typename Container::difference_type difference_type; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + ValueFlagList(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Container &defaultValues_ = Container(), Options options_ = {}): + ValueFlagBase(name_, help_, std::move(matcher_), options_), values(defaultValues_), defaultValues(defaultValues_) + { + group_.Add(*this); + } + + virtual ~ValueFlagList() {} + + virtual void ParseValue(const std::vector &values_) override + { + const std::string &value_ = values_.at(0); + + T v; +#ifdef ARGS_NOEXCEPT + if (!reader(name, value_, v)) + { + error = Error::Parse; + } +#else + reader(name, value_, v); +#endif + values.insert(std::end(values), v); + } + + /** Get the values + */ + Container &Get() noexcept + { + return values; + } + + virtual std::string Name() const override + { + return name + std::string("..."); + } + + virtual void Reset() noexcept override + { + ValueFlagBase::Reset(); + values = defaultValues; + } + + virtual FlagBase *Match(const EitherFlag &arg) override + { + const bool wasMatched = Matched(); + auto me = FlagBase::Match(arg); + if (me && !wasMatched) + { + values.clear(); + } + return me; + } + + iterator begin() noexcept + { + return values.begin(); + } + + const_iterator begin() const noexcept + { + return values.begin(); + } + + const_iterator cbegin() const noexcept + { + return values.cbegin(); + } + + iterator end() noexcept + { + return values.end(); + } + + const_iterator end() const noexcept + { + return values.end(); + } + + const_iterator cend() const noexcept + { + return values.cend(); + } + }; + + /** A mapping value flag class + * + * \tparam K the type to extract the argument as + * \tparam T the type to store the result as + * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) + * \tparam Map The Map type. Should operate like std::map or std::unordered_map + */ + template < + typename K, + typename T, + typename Reader = ValueReader, + template class Map = std::unordered_map> + class MapFlag : public ValueFlagBase + { + private: + const Map map; + T value; + const T defaultValue; + Reader reader; + + protected: + virtual std::vector GetChoicesStrings(const HelpParams &) const override + { + return detail::MapKeysToStrings(map); + } + + public: + + MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map &map_, const T &defaultValue_, Options options_): ValueFlagBase(name_, help_, std::move(matcher_), options_), map(map_), value(defaultValue_), defaultValue(defaultValue_) + { + group_.Add(*this); + } + + MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map &map_, const T &defaultValue_ = T(), const bool extraError_ = false): MapFlag(group_, name_, help_, std::move(matcher_), map_, defaultValue_, extraError_ ? Options::Single : Options::None) + { + } + + MapFlag(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map &map_, Options options_): MapFlag(group_, name_, help_, std::move(matcher_), map_, T(), options_) + { + } + + virtual ~MapFlag() {} + + virtual void ParseValue(const std::vector &values_) override + { + const std::string &value_ = values_.at(0); + + K key; +#ifdef ARGS_NOEXCEPT + if (!reader(name, value_, key)) + { + error = Error::Parse; + } +#else + reader(name, value_, key); +#endif + auto it = map.find(key); + if (it == std::end(map)) + { + std::ostringstream problem; + problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; +#ifdef ARGS_NOEXCEPT + error = Error::Map; + errorMsg = problem.str(); +#else + throw MapError(problem.str()); +#endif + } else + { + this->value = it->second; + } + } + + /** Get the value + */ + T &Get() noexcept + { + return value; + } + + virtual void Reset() noexcept override + { + ValueFlagBase::Reset(); + value = defaultValue; + } + }; + + /** A mapping value flag list class + * + * \tparam K the type to extract the argument as + * \tparam T the type to store the result as + * \tparam List the list type that houses the values + * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) + * \tparam Map The Map type. Should operate like std::map or std::unordered_map + */ + template < + typename K, + typename T, + template class List = std::vector, + typename Reader = ValueReader, + template class Map = std::unordered_map> + class MapFlagList : public ValueFlagBase + { + private: + using Container = List; + const Map map; + Container values; + const Container defaultValues; + Reader reader; + + protected: + virtual std::vector GetChoicesStrings(const HelpParams &) const override + { + return detail::MapKeysToStrings(map); + } + + public: + typedef T value_type; + typedef typename Container::allocator_type allocator_type; + typedef typename Container::pointer pointer; + typedef typename Container::const_pointer const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef typename Container::size_type size_type; + typedef typename Container::difference_type difference_type; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + MapFlagList(Group &group_, const std::string &name_, const std::string &help_, Matcher &&matcher_, const Map &map_, const Container &defaultValues_ = Container()): ValueFlagBase(name_, help_, std::move(matcher_)), map(map_), values(defaultValues_), defaultValues(defaultValues_) + { + group_.Add(*this); + } + + virtual ~MapFlagList() {} + + virtual void ParseValue(const std::vector &values_) override + { + const std::string &value = values_.at(0); + + K key; +#ifdef ARGS_NOEXCEPT + if (!reader(name, value, key)) + { + error = Error::Parse; + } +#else + reader(name, value, key); +#endif + auto it = map.find(key); + if (it == std::end(map)) + { + std::ostringstream problem; + problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; +#ifdef ARGS_NOEXCEPT + error = Error::Map; + errorMsg = problem.str(); +#else + throw MapError(problem.str()); +#endif + } else + { + this->values.emplace_back(it->second); + } + } + + /** Get the value + */ + Container &Get() noexcept + { + return values; + } + + virtual std::string Name() const override + { + return name + std::string("..."); + } + + virtual void Reset() noexcept override + { + ValueFlagBase::Reset(); + values = defaultValues; + } + + virtual FlagBase *Match(const EitherFlag &arg) override + { + const bool wasMatched = Matched(); + auto me = FlagBase::Match(arg); + if (me && !wasMatched) + { + values.clear(); + } + return me; + } + + iterator begin() noexcept + { + return values.begin(); + } + + const_iterator begin() const noexcept + { + return values.begin(); + } + + const_iterator cbegin() const noexcept + { + return values.cbegin(); + } + + iterator end() noexcept + { + return values.end(); + } + + const_iterator end() const noexcept + { + return values.end(); + } + + const_iterator cend() const noexcept + { + return values.cend(); + } + }; + + /** A positional argument class + * + * \tparam T the type to extract the argument as + * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) + */ + template < + typename T, + typename Reader = ValueReader> + class Positional : public PositionalBase + { + private: + T value; + const T defaultValue; + Reader reader; + public: + Positional(Group &group_, const std::string &name_, const std::string &help_, const T &defaultValue_ = T(), Options options_ = {}): PositionalBase(name_, help_, options_), value(defaultValue_), defaultValue(defaultValue_) + { + group_.Add(*this); + } + + Positional(Group &group_, const std::string &name_, const std::string &help_, Options options_): Positional(group_, name_, help_, T(), options_) + { + } + + virtual ~Positional() {} + + virtual void ParseValue(const std::string &value_) override + { +#ifdef ARGS_NOEXCEPT + if (!reader(name, value_, this->value)) + { + error = Error::Parse; + } +#else + reader(name, value_, this->value); +#endif + ready = false; + matched = true; + } + + /** Get the value + */ + T &Get() noexcept + { + return value; + } + + virtual void Reset() noexcept override + { + PositionalBase::Reset(); + value = defaultValue; + } + }; + + /** A positional argument class that pushes the found values into a list + * + * \tparam T the type to extract the argument as + * \tparam List the list type that houses the values + * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) + */ + template < + typename T, + template class List = std::vector, + typename Reader = ValueReader> + class PositionalList : public PositionalBase + { + private: + using Container = List; + Container values; + const Container defaultValues; + Reader reader; + + public: + typedef T value_type; + typedef typename Container::allocator_type allocator_type; + typedef typename Container::pointer pointer; + typedef typename Container::const_pointer const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef typename Container::size_type size_type; + typedef typename Container::difference_type difference_type; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + PositionalList(Group &group_, const std::string &name_, const std::string &help_, const Container &defaultValues_ = Container(), Options options_ = {}): PositionalBase(name_, help_, options_), values(defaultValues_), defaultValues(defaultValues_) + { + group_.Add(*this); + } + + PositionalList(Group &group_, const std::string &name_, const std::string &help_, Options options_): PositionalList(group_, name_, help_, {}, options_) + { + } + + virtual ~PositionalList() {} + + virtual void ParseValue(const std::string &value_) override + { + T v; +#ifdef ARGS_NOEXCEPT + if (!reader(name, value_, v)) + { + error = Error::Parse; + } +#else + reader(name, value_, v); +#endif + values.insert(std::end(values), v); + matched = true; + } + + virtual std::string Name() const override + { + return name + std::string("..."); + } + + /** Get the values + */ + Container &Get() noexcept + { + return values; + } + + virtual void Reset() noexcept override + { + PositionalBase::Reset(); + values = defaultValues; + } + + virtual PositionalBase *GetNextPositional() override + { + const bool wasMatched = Matched(); + auto me = PositionalBase::GetNextPositional(); + if (me && !wasMatched) + { + values.clear(); + } + return me; + } + + iterator begin() noexcept + { + return values.begin(); + } + + const_iterator begin() const noexcept + { + return values.begin(); + } + + const_iterator cbegin() const noexcept + { + return values.cbegin(); + } + + iterator end() noexcept + { + return values.end(); + } + + const_iterator end() const noexcept + { + return values.end(); + } + + const_iterator cend() const noexcept + { + return values.cend(); + } + }; + + /** A positional argument mapping class + * + * \tparam K the type to extract the argument as + * \tparam T the type to store the result as + * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) + * \tparam Map The Map type. Should operate like std::map or std::unordered_map + */ + template < + typename K, + typename T, + typename Reader = ValueReader, + template class Map = std::unordered_map> + class MapPositional : public PositionalBase + { + private: + const Map map; + T value; + const T defaultValue; + Reader reader; + + protected: + virtual std::vector GetChoicesStrings(const HelpParams &) const override + { + return detail::MapKeysToStrings(map); + } + + public: + + MapPositional(Group &group_, const std::string &name_, const std::string &help_, const Map &map_, const T &defaultValue_ = T(), Options options_ = {}): + PositionalBase(name_, help_, options_), map(map_), value(defaultValue_), defaultValue(defaultValue_) + { + group_.Add(*this); + } + + virtual ~MapPositional() {} + + virtual void ParseValue(const std::string &value_) override + { + K key; +#ifdef ARGS_NOEXCEPT + if (!reader(name, value_, key)) + { + error = Error::Parse; + } +#else + reader(name, value_, key); +#endif + auto it = map.find(key); + if (it == std::end(map)) + { + std::ostringstream problem; + problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; +#ifdef ARGS_NOEXCEPT + error = Error::Map; + errorMsg = problem.str(); +#else + throw MapError(problem.str()); +#endif + } else + { + this->value = it->second; + ready = false; + matched = true; + } + } + + /** Get the value + */ + T &Get() noexcept + { + return value; + } + + virtual void Reset() noexcept override + { + PositionalBase::Reset(); + value = defaultValue; + } + }; + + /** A positional argument mapping list class + * + * \tparam K the type to extract the argument as + * \tparam T the type to store the result as + * \tparam List the list type that houses the values + * \tparam Reader The functor type used to read the argument, taking the name, value, and destination reference with operator(), and returning a bool (if ARGS_NOEXCEPT is defined) + * \tparam Map The Map type. Should operate like std::map or std::unordered_map + */ + template < + typename K, + typename T, + template class List = std::vector, + typename Reader = ValueReader, + template class Map = std::unordered_map> + class MapPositionalList : public PositionalBase + { + private: + using Container = List; + + const Map map; + Container values; + const Container defaultValues; + Reader reader; + + protected: + virtual std::vector GetChoicesStrings(const HelpParams &) const override + { + return detail::MapKeysToStrings(map); + } + + public: + typedef T value_type; + typedef typename Container::allocator_type allocator_type; + typedef typename Container::pointer pointer; + typedef typename Container::const_pointer const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef typename Container::size_type size_type; + typedef typename Container::difference_type difference_type; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + MapPositionalList(Group &group_, const std::string &name_, const std::string &help_, const Map &map_, const Container &defaultValues_ = Container(), Options options_ = {}): + PositionalBase(name_, help_, options_), map(map_), values(defaultValues_), defaultValues(defaultValues_) + { + group_.Add(*this); + } + + virtual ~MapPositionalList() {} + + virtual void ParseValue(const std::string &value_) override + { + K key; +#ifdef ARGS_NOEXCEPT + if (!reader(name, value_, key)) + { + error = Error::Parse; + } +#else + reader(name, value_, key); +#endif + auto it = map.find(key); + if (it == std::end(map)) + { + std::ostringstream problem; + problem << "Could not find key '" << key << "' in map for arg '" << name << "'"; +#ifdef ARGS_NOEXCEPT + error = Error::Map; + errorMsg = problem.str(); +#else + throw MapError(problem.str()); +#endif + } else + { + this->values.emplace_back(it->second); + matched = true; + } + } + + /** Get the value + */ + Container &Get() noexcept + { + return values; + } + + virtual std::string Name() const override + { + return name + std::string("..."); + } + + virtual void Reset() noexcept override + { + PositionalBase::Reset(); + values = defaultValues; + } + + virtual PositionalBase *GetNextPositional() override + { + const bool wasMatched = Matched(); + auto me = PositionalBase::GetNextPositional(); + if (me && !wasMatched) + { + values.clear(); + } + return me; + } + + iterator begin() noexcept + { + return values.begin(); + } + + const_iterator begin() const noexcept + { + return values.begin(); + } + + const_iterator cbegin() const noexcept + { + return values.cbegin(); + } + + iterator end() noexcept + { + return values.end(); + } + + const_iterator end() const noexcept + { + return values.end(); + } + + const_iterator cend() const noexcept + { + return values.cend(); + } + }; +} + +#endif diff --git a/simulation/src/nuevisim.cpp b/simulation/src/nuevisim.cpp index d17d216..cacb3c9 100644 --- a/simulation/src/nuevisim.cpp +++ b/simulation/src/nuevisim.cpp @@ -12,15 +12,16 @@ #include "GL/gl3w.h" #include "examples/imgui_impl_sdl.h" #include "examples/imgui_impl_opengl3.h" +#include "EEPROM.h" #include -#include "argparse.hpp" +#include "args.hxx" // Forward declarations static void SimQuit(void); static int SimInit(void); -static int SimRun(void); +static int SimRun(std::string eepromFile, bool eepromWrite, bool factoryReset); static void SimLoop(std::function, std::function); @@ -31,6 +32,8 @@ SimWire Wire; SimSerial Serial; SimSerial Serial3; //Midi SimUsbMidi usbMIDI; +EEPROMClass EEPROM; + static const int scale = 3; @@ -510,12 +513,20 @@ static int SimRun(std::string eepromFile, bool eepromWrite, bool factoryReset) { if( 0 != SimInit() ) { return 1; } - // Dummy to always force full reset of EEPROM, to circumvent bug in NuEVI.ino - digitalInputs[mPin] = 0; - digitalInputs[ePin] = 0; + //Set up EEPROM file storage + if(eepromFile.length()>0) { + EEPROM.setStorage(eepromFile.c_str(), eepromWrite); + } + + // Holding down buttons if doing a factory reset on startup + if(factoryReset) { + digitalInputs[mPin] = 0; + digitalInputs[ePin] = 0; + } setup(); + //Let it go, let it go, not resetting any more digitalInputs[mPin] = 1; digitalInputs[ePin] = 1; @@ -585,6 +596,8 @@ static void SimQuit() { printf("Leaving Sim, see you later!\n"); + EEPROM.closeStorage(); + if( window != NULL ) { SDL_DestroyWindow( window ); // SDL_FreeSurface( surface ); @@ -597,25 +610,15 @@ static void SimQuit() int main(int argc, const char** argv) { - ArgumentParser parser; - parser.addArgument("--eeprom-file", 1); - parser.addArgument("--eeprom-write"); - parser.addArgument("--factory-reset"); - - parser.parse(argc, argv); - - std::string eepromFile; - bool eepromWrite; - bool factoryReset; + args::ArgumentParser parser("This is a test program.", "This goes after the options."); - eepromFile = parser.retrieve("eeprom-file"); - eepromWrite = parser.exists("eeprom-write"); - factoryReset = parser.exists("factory-reset"); + args::ValueFlag eepromFile(parser, "eeprom-write", "File to use for EEPROM data", {'e', "eeprom-file"}); + args::Flag eepromWrite(parser, "eeprom-write", "Write EEPROM changes to file", {'w', "eeprom-write"}); + args::Flag factoryReset(parser, "factory-reset", "Trigger factory reset", {'r', "factory-reset"}); - printf("%d: %s\n",eepromWrite, eepromFile.c_str()); - return 0; + parser.ParseCLI(argc, argv); - return SimRun(eepromFile, eepromWrite, factoryReset); + return SimRun(args::get(eepromFile), args::get(eepromWrite), args::get(factoryReset)); } diff --git a/simulation/src/simeeprom.cpp b/simulation/src/simeeprom.cpp index 97d0b3e..fc58537 100644 --- a/simulation/src/simeeprom.cpp +++ b/simulation/src/simeeprom.cpp @@ -8,6 +8,8 @@ EEPROMClass::EEPROMClass() { memset(someFakeEEPROM_memory, 0xff, sizeof(someFakeEEPROM_memory)); + storage = NULL; + autoUpdate = false; } @@ -22,15 +24,98 @@ void EEPROMClass::write( int idx, uint8_t val ) { printf("Writing to EEPROM address %u = %u\n", idx, val); someFakeEEPROM_memory[idx] = val; + + if(autoUpdate && storage) + { + fseek(storage, idx, SEEK_SET); + fputc(val, storage); + fflush(storage); + } } void EEPROMClass::update( int idx, uint8_t val ) { write(idx, val); } + uint16_t EEPROMClass::length() { return sizeof(someFakeEEPROM_memory); } -// TODO: Add missing functioality.. \ No newline at end of file +int16_t EEPROMClass::setStorage(const char* filename, bool write) +{ + + //Close any open storage file + if(storage) + { + fclose(storage); + storage = NULL; + } + + autoUpdate = write; + + storage = fopen(filename, "rb"); + + + //If only reading, fail if file does not exist (makes no sense otherwise) + if(!storage && !autoUpdate) { + printf("Could not open EEPROM storage file: '%s'\n", filename); + return -1; + } + + if(storage) + { + printf("Reading EEPROM storage file: '%s'\n", filename); + rewind(storage); + fread(someFakeEEPROM_memory, sizeof(someFakeEEPROM_memory), 1, storage); + } + + if(!autoUpdate) + { + //No need for the file anymore, close it + fclose(storage); + storage = NULL; + } + + //Create file if it doesn't exist (so we can write to it) + if(!storage && autoUpdate) + { + storage = fopen(filename, "wb"); + if(!storage) + { + printf("Could not create EEPROM storage file: '%s'\n", filename); + autoUpdate = false; + return -2; + } + } + + if(storage && autoUpdate) + { + //Reopen file for writing without overwriting it + storage = freopen(filename, "r+b", storage); + + if(!storage) + { + printf("Could not access EEPROM storage file for writing: '%s'\n", filename); + autoUpdate = false; + return -3; + } + + printf("Writing any EEPROM changes to '%s'\n", filename); + } + + return 0; +} + +void EEPROMClass::closeStorage() { + if(storage==NULL) + { + return; + } + + printf("Closing EEPROM storage\n"); + + fclose(storage); + storage=NULL; +} From 2c3df24fc181272fd936fb4435db0e8924e7b13e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Mon, 24 Jun 2019 14:11:58 +0200 Subject: [PATCH 3/4] Use default eeprom file if none is provided --- simulation/src/nuevisim.cpp | 11 ++++++++++- simulation/src/simeeprom.cpp | 10 +++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/simulation/src/nuevisim.cpp b/simulation/src/nuevisim.cpp index cacb3c9..7a4d29b 100644 --- a/simulation/src/nuevisim.cpp +++ b/simulation/src/nuevisim.cpp @@ -620,5 +620,14 @@ int main(int argc, const char** argv) parser.ParseCLI(argc, argv); - return SimRun(args::get(eepromFile), args::get(eepromWrite), args::get(factoryReset)); + std::string eepromFileName = args::get(eepromFile); + + //Use a default EEPROM file if none is provided. + if(eepromFileName.length()==0) + { + eepromFileName = SDL_GetPrefPath("Vulk Data System", "NuEVI Simulator"); + eepromFileName += "eeprom.bin"; + } + + return SimRun(eepromFileName, args::get(eepromWrite), args::get(factoryReset)); } diff --git a/simulation/src/simeeprom.cpp b/simulation/src/simeeprom.cpp index fc58537..5c960ec 100644 --- a/simulation/src/simeeprom.cpp +++ b/simulation/src/simeeprom.cpp @@ -109,13 +109,9 @@ int16_t EEPROMClass::setStorage(const char* filename, bool write) } void EEPROMClass::closeStorage() { - if(storage==NULL) + if(storage!=NULL) { - return; + fclose(storage); + storage=NULL; } - - printf("Closing EEPROM storage\n"); - - fclose(storage); - storage=NULL; } From f5fc08617787574c5d98e301e033fa53711e98da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Mon, 24 Jun 2019 16:01:27 +0200 Subject: [PATCH 4/4] Only write to file if value has actually changed. --- simulation/src/simeeprom.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/simulation/src/simeeprom.cpp b/simulation/src/simeeprom.cpp index 5c960ec..c3fb3c5 100644 --- a/simulation/src/simeeprom.cpp +++ b/simulation/src/simeeprom.cpp @@ -23,6 +23,13 @@ uint8_t EEPROMClass::read( int idx ) void EEPROMClass::write( int idx, uint8_t val ) { printf("Writing to EEPROM address %u = %u\n", idx, val); + + if(val == someFakeEEPROM_memory[idx]) + { + //Value unchanged, do nothing. + return; + } + someFakeEEPROM_memory[idx] = val; if(autoUpdate && storage)