add short strings to options, improve help output, improve tests

This commit is contained in:
Carl Pearson
2020-04-17 17:06:16 -05:00
parent 193219ac50
commit 661d569657
4 changed files with 172 additions and 41 deletions

View File

@@ -22,13 +22,13 @@ int main(int argc, char **argv) {
bool verbose = false;
std::string toPrint;
std::string maybePrint;
int repeats = 1;
int repeats = 2;
// Inform the parser of the program data. It will do type-specific conversion
// from string.
// An option invoked with `--repeat N`
p.add_option(repeats, "--repeat");
p.add_option(repeats, "--repeat")->help("how many times to repeat first argument");
// A flag invoked with `--verbose` or `-v`
p.add_flag(verbose, "--verbose", "-v");
// a required positional argument (position 1)
@@ -65,25 +65,29 @@ int main(int argc, char **argv) {
## Adding Options
Options may be `int`, `size_t`, `float`, `double`, or `std::string` and currently support long strings.
They are invoked like `--long-opt value` (not `--long-opt=value`).
Options may be `int`, `size_t`, `float`, `double`, or `std::string`.
They are invoked like `--long-opt value` (not `--long-opt=value`) or `-s value`, if provided.
If they are not present, the value is not modified.
```c++
argparse::Parser p;
p.add_option(var1, "--long-opt")
p.add_option(var1, "--long-opt");
p.add_option(var2, "--long-opt, -s");
p.add_option(var3, "--long-opt, -s")->help("a demo option");
```
## Adding Flags
Flags are always `bool`s, and currently support long or short strings.
The boolean variable is ALWAYS set to `true` if the flag is found.
They are invoked like `--long-flag` (not `--long-flag=true` or `--long-flag true`).
They are invoked like `--long-flag` (not `--long-flag=true` or `--long-flag true`) or `-s`, if provided.
```c++
argparse::Parser p;
p.add_option(flag1, "--long-flag")
p.add_option(flag2, "--antother-flag", "-s");
p.add_option(flag2, "--another-flag", "-s");
p.add_option(flag3, "--another-flag", "-s")->help("a demo flag");
```
## Positional Arguments
@@ -102,7 +106,7 @@ if (something->found()) {
}
```
Anything after `--` is considered a positional argument, unless -- follows an option.
Anything after `--` is considered a positional argument, unless `--` follows an option.
Here, the first `--` is the value for `--option` and will be in `s1`.
The second `--` marks the beginning of positional arguments. and the string `aa` will be in `s2`.
@@ -150,11 +154,11 @@ To error instead, call `p->no_unrecognized()`.
- [x] `float`
- [x] `double`
- [x] `std::string`
- [x] Support short option strings
- [x] Help string output
## Roadmap
- [ ] Reject duplicate flags / options at run time
- [ ] Support short option strings
- [ ] Help string output
- [ ] support --long-option=value
- [ ] have the last positional argument fill a vector with remaining

View File

@@ -9,13 +9,13 @@ int main(int argc, char **argv) {
bool verbose = false;
std::string toPrint;
std::string maybePrint;
int repeats = 1;
int repeats = 2;
// Inform the parser of the program data. It will do type-specific conversion
// from string.
// An option invoked with `--repeat N`
p.add_option(repeats, "--repeat");
p.add_option(repeats, "--repeat")->help("how many times to repeat first argument");
// A flag invoked with `--verbose` or `-v`
p.add_flag(verbose, "--verbose", "-v");
// a required positional argument (position 1)

View File

@@ -7,22 +7,70 @@
namespace argparse {
namespace detail {
template <typename T> const char *type_str();
template <> const char *type_str<int32_t>() { return "INT32"; }
template <> const char *type_str<int64_t>() { return "INT64"; }
template <> const char *type_str<std::string>() { return "STR"; }
template <> const char *type_str<float>() { return "F32"; }
template <> const char *type_str<double>() { return "F64"; }
template <> const char *type_str<size_t>() { return "SIZE_T"; }
} // namespace detail
class Parser;
/* Interface for an object representing a command-line option
*/
class OptionBase {
friend class Parser;
public:
virtual void set_val(const std::string &valStr) = 0;
/* return the short and long options
*/
virtual const std::string &short_str() = 0;
virtual const std::string &long_str() = 0;
/* return the help string
*/
virtual const std::string &help_str() = 0;
/* set help string
*/
virtual void help(const std::string &s) = 0;
private:
/* a string representing the type of the option (INT, STR, FLOAT)
*/
virtual const char *type_str() = 0;
/* set the value provided to this option
*/
virtual void set_val(const std::string &valStr) = 0;
};
template <typename T> class Option : public OptionBase {
std::string short_;
std::string long_;
std::string help_;
T *val_;
public:
Option(T &val, const std::string &l) : long_(l), val_(&val) {}
void set_val(const std::string &val) override { set_val((T *)nullptr, val); }
Option(T &val, const std::string &l, const std::string &s = "")
: long_(l), short_(s), val_(&val) {}
const std::string &long_str() override { return long_; }
const std::string &short_str() override { return short_; }
const std::string &help_str() override { return help_; }
void help(const std::string &s) { help_ = s; }
private:
/* uses a T* = nullpointer to disambiguate the call
*/
const char *type_str() override { return detail::type_str<T>(); }
void set_val(const std::string &val) override { set_val((T *)nullptr, val); }
/* how to parse supported types
*/
void set_val(size_t *, const std::string &val) { // convert to size_t
*val_ = std::stoull(val);
}
@@ -40,6 +88,8 @@ private:
}
};
/* Represents a command-line flag
*/
class Flag {
std::string long_;
std::string short_;
@@ -61,11 +111,16 @@ public:
};
class PosnlBase {
friend class Parser;
public:
virtual bool is_required() = 0;
virtual PosnlBase *required() = 0;
virtual void set_val(const std::string &val) = 0;
virtual bool found() = 0;
private:
virtual const char *type_str() = 0;
};
template <typename T> class Positional : public PosnlBase {
@@ -93,7 +148,8 @@ public:
bool found() override { return found_; }
private:
// https://stackoverflow.com/questions/5512910/explicit-specialization-of-template-class-member-function
const char *type_str() override { return detail::type_str<T>(); }
template <typename C>
void get_as(C *, const std::string &val) { // to be overridden
}
@@ -125,6 +181,8 @@ class Parser {
std::vector<Flag> flags_;
std::vector<PosnlBase *> posnls_;
std::string argv0_; // will hold the name of the most recent parse(...)
static bool starts_with(const std::string &s, const std::string &prefix) {
if (s.rfind(prefix, 0) == 0) {
return true;
@@ -134,8 +192,10 @@ class Parser {
OptionBase *match_opt(const char *arg) const {
std::string sarg(arg);
// match options back-to-front to prefer later duplicates
for (int64_t i = int64_t(opts_.size()) - 1; i >= 0; --i) {
if (opts_[i]->long_str() == sarg) {
if (opts_[i]->long_str() == sarg || opts_[i]->short_str() == sarg) {
return opts_[i];
}
}
@@ -144,6 +204,8 @@ class Parser {
Flag *match_flag(const char *arg) {
std::string sarg(arg);
// match flags back-to-front to prefer later duplicates
for (int64_t i = int64_t(flags_.size()) - 1; i >= 0; --i) {
if (flags_[i].long_str() == sarg || flags_[i].short_str() == sarg) {
return &flags_[i];
@@ -153,7 +215,9 @@ class Parser {
}
public:
Parser() : noUnrecognized_(false), help_(false), consume_(true) {
Parser()
: description_("an argparse-powered program"), noUnrecognized_(false),
help_(false), consume_(true) {
add_flag(help_, "--help", "-h")->help("Print help message");
}
Parser(const std::string &description)
@@ -167,6 +231,12 @@ public:
std::vector<char *> newArgv;
if (argc > 0) {
newArgv.push_back(argv[0]);
if (argv[0]) {
argv0_ = argv[0];
} else {
argv0_ = "";
}
}
size_t pi = 0; // positional argument position
@@ -176,9 +246,10 @@ public:
// try interpreting as a flag or option if it looks like one
if (optsOkay && starts_with(argv[i], "-")) {
// '--' indicates only positional arguments follow
// the second '--' should be interpreted as a positional argument
if (argv[i] == std::string("--")) {
optsOkay = false;
continue;
optsOkay = false;
continue;
}
OptionBase *opt = match_opt(argv[i]);
if (opt) {
@@ -225,8 +296,11 @@ public:
return true;
};
template <typename T> void add_option(T &val, const std::string &l) {
opts_.push_back(new Option<T>(val, l));
template <typename T>
OptionBase *add_option(T &val, const std::string &l,
const std::string &s = "") {
opts_.push_back(new Option<T>(val, l, s));
return opts_.back();
}
Flag *add_flag(bool &val, const std::string &l, const std::string &s = "") {
@@ -242,10 +316,26 @@ public:
std::string help() const {
std::stringstream ss;
ss << "Usage: " << argv0_ << " [OPTION]...";
for (auto &p : posnls_) {
ss << " ";
ss << (p->is_required() ? "" : "[");
ss << p->type_str();
ss << (p->is_required() ? "" : "]");
}
ss << "\n";
ss << description_ << "\n";
for (auto &o : opts_) {
ss << o->long_str() << "\n";
ss << " ";
if (!o->short_str().empty()) {
ss << o->short_str() << ", ";
}
ss << o->long_str() << "\t" << o->type_str();
ss << "\t\t" << o->help_str();
ss << "\n";
}
for (auto &f : flags_) {

View File

@@ -6,6 +6,20 @@
TEST_CASE("argparse") {
SECTION("no args") {
char ** argv = nullptr;
int argc = 0;
argparse::Parser p;
REQUIRE(p.parse(argc, argv));
}
SECTION("null argv[0]") {
char *argv[] = {nullptr};
int argc = sizeof(argv) / sizeof(argv[0]);
argparse::Parser p;
REQUIRE(p.parse(argc, argv));
}
SECTION("types") {
char *argv[] = {
"some-exe", "--campi", "--f", "10", "1.7", "1.8",
@@ -42,13 +56,6 @@ TEST_CASE("argparse") {
REQUIRE(argc == 2); // does not use --f or some-exe
}
SECTION("no args") {
char *argv[] = {nullptr};
int argc = sizeof(argv) / sizeof(argv[0]);
argparse::Parser p;
REQUIRE(p.parse(argc, argv));
}
SECTION("description") {
char *argv[] = {
@@ -95,23 +102,15 @@ TEST_CASE("argparse") {
char *argv[] = {"some-exe", "-h"};
int argc = sizeof(argv) / sizeof(argv[0]);
std::string a;
std::string b;
argparse::Parser p;
REQUIRE(true == p.parse(argc, argv));
REQUIRE(p.need_help());
std::cerr << p.help() << "\n";
}
SECTION("--help") {
char *argv[] = {"some-exe", "--help"};
int argc = sizeof(argv) / sizeof(argv[0]);
std::string a;
std::string b;
argparse::Parser p;
REQUIRE(true == p.parse(argc, argv));
REQUIRE(p.need_help());
@@ -121,12 +120,50 @@ TEST_CASE("argparse") {
char *argv[] = {"some-exe", "--help", "--"};
int argc = sizeof(argv) / sizeof(argv[0]);
std::string a;
std::string b;
argparse::Parser p;
REQUIRE(true == p.parse(argc, argv));
REQUIRE(p.need_help());
}
SECTION("double --") {
char *argv[] = {"some-exe", "--flag", "--", "--", "aa"};
int argc = sizeof(argv) / sizeof(argv[0]);
std::string a,b;
bool flag = false;
argparse::Parser p;
p.add_flag(flag, "--flag");
p.add_positional(a);
p.add_positional(b);
REQUIRE(true == p.parse(argc, argv));
REQUIRE(!p.need_help());
REQUIRE(a == "--");
REQUIRE(b == "aa");
REQUIRE(flag == true);
}
SECTION("-- in option") {
char *argv[] = {"some-exe", "--option", "--", "--", "aa"};
int argc = sizeof(argv) / sizeof(argv[0]);
std::string a, option;
argparse::Parser p;
p.add_option(option, "--option");
p.add_positional(a);
REQUIRE(true == p.parse(argc, argv));
REQUIRE(!p.need_help());
REQUIRE(a == "aa");
REQUIRE(option == "--");
}
}