add short strings to options, improve help output, improve tests
This commit is contained in:
24
README.md
24
README.md
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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_) {
|
||||
|
@@ -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 == "--");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user