From 5305cabf0b20a99a82aff4bcef26f2be0a8fd757 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Fri, 3 May 2013 12:42:27 +0300 Subject: [PATCH] Add support for positional arguments in GetOpt This addresses the issue of any positional arguments recognized by an application not showing up in the generated help text. It also reduces the amount of processing and validation required from the application itself and provides consistent error messages. --- source/core/getopt.cpp | 116 ++++++++++++++++++++++++++++++++++++++++- source/core/getopt.h | 75 ++++++++++++++++++++++---- 2 files changed, 180 insertions(+), 11 deletions(-) diff --git a/source/core/getopt.cpp b/source/core/getopt.cpp index fdeb2f8..2561c9a 100644 --- a/source/core/getopt.cpp +++ b/source/core/getopt.cpp @@ -15,6 +15,8 @@ GetOpt::~GetOpt() { for(OptionList::iterator i=opts.begin(); i!=opts.end(); ++i) delete *i; + for(ArgumentList::iterator i=args.begin(); i!=args.end(); ++i) + delete *i; } GetOpt::OptionImpl &GetOpt::add_option(char s, const string &l, const Store &t, ArgType a) @@ -39,6 +41,30 @@ GetOpt::OptionImpl &GetOpt::add_option(char s, const string &l, const Store &t, return *opts.back(); } +GetOpt::ArgumentImpl &GetOpt::add_argument(const string &n, const Store &t, ArgType a) +{ + if(a==NO_ARG) + throw invalid_argument("GetOpt::add_argument"); + + bool have_list = false; + bool have_optional = false; + for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i) + { + if((*i)->is_list_store()) + have_list = true; + else if((*i)->get_type()==OPTIONAL_ARG) + have_optional = true; + } + + if(have_optional && (t.is_list() || a!=OPTIONAL_ARG)) + throw invalid_argument("GetOpt::add_argument"); + if(have_list && (t.is_list() || a==OPTIONAL_ARG)) + throw invalid_argument("GetOpt::add_argument"); + + args.push_back(new ArgumentImpl(n, t, a)); + return *args.back(); +} + GetOpt::OptionImpl &GetOpt::get_option(char s) { for(OptionList::iterator i=opts.begin(); i!=opts.end(); ++i) @@ -59,6 +85,8 @@ void GetOpt::operator()(unsigned argc, const char *const *argv) { try { + /* Arguments must first be collected into an array to handle the case + where a variable-length argument list is followed by fixed arguments. */ unsigned i = 1; for(; iis_list_store()) + { + unsigned end = args_raw.size(); + for(ArgumentList::const_iterator k=j; ++k!=args.end(); ) + --end; + if(i==end && (*j)->get_type()==REQUIRED_ARG) + throw usage_error((*j)->get_name()+" is required"); + for(; iprocess(args_raw[i]); + } + else + { + if(iprocess(args_raw[i++]); + else if((*j)->get_type()==REQUIRED_ARG) + throw usage_error((*j)->get_name()+" is required"); + } + } + + // XXX Enable this when get_args() is completely removed + /*if(iget_type()==OPTIONAL_ARG) + result += '['; + result += format("<%s>", (*i)->get_name()); + if((*i)->is_list_store()) + result += " ..."; + if((*i)->get_type()==OPTIONAL_ARG) + result += ']'; + } + return result; } @@ -218,10 +284,26 @@ string GetOpt::generate_help() const maxw = max(maxw, swtch.size()); } + list pargs; + for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i) + { + string parg = format("<%s>", (*i)->get_name()); + pargs.push_back(parg); + maxw = max(maxw, parg.size()); + } + string result; + result += "Options:\n"; list::const_iterator j = switches.begin(); for(OptionList::const_iterator i=opts.begin(); i!=opts.end(); ++i, ++j) result += format(" %s%s%s\n", *j, string(maxw+2-j->size(), ' '), (*i)->get_help()); + if(!pargs.empty()) + { + result += "\nArguments:\n"; + j = pargs.begin(); + for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i, ++j) + result += format(" %s%s%s\n", *j, string(maxw+2-j->size(), ' '), (*i)->get_help()); + } return result; } @@ -299,4 +381,34 @@ void GetOpt::OptionImpl::process(const string &arg) } } + +GetOpt::ArgumentImpl::ArgumentImpl(const string &n, const Store &t, ArgType a): + name(n), + type(a), + store(t.clone()) +{ } + +GetOpt::ArgumentImpl::~ArgumentImpl() +{ + delete store; +} + +GetOpt::ArgumentImpl &GetOpt::ArgumentImpl::set_help(const string &h) +{ + help = h; + return *this; +} + +void GetOpt::ArgumentImpl::process(const string &arg) +{ + try + { + store->store(arg); + } + catch(const exception &e) + { + throw usage_error("Invalid "+name+" ("+e.what()+")"); + } +} + } // namespace Msp diff --git a/source/core/getopt.h b/source/core/getopt.h index 70841cb..f4e318e 100644 --- a/source/core/getopt.h +++ b/source/core/getopt.h @@ -44,9 +44,19 @@ A single option may have both alternative forms, but must always have at least a long form. This is to encourage self-documenting options; it's much easier to remember words than letters. -A built-in --help option is provided and will output a list of options and -their associated help texts. An application may override this by providing -its own option with the same name. +Positional arguments are also supported. They are identified by an arbitrary +string, but the identifier is only used in help text and error messages. Any +number of the final arguments may be optional. + +To support applications that take an arbitrary amount of arguments, a single +positional argument list can be specified. Fixed positional arguments are +allowed together with a list, but they can't be optional. An application that +wants to do complex processing on the argument list can declare a list of +string arguments. + +A built-in --help option is provided and will output a list of options, +arguments and their associated help texts. An application may override this by +providing its own option with the same name. */ class GetOpt { @@ -78,6 +88,16 @@ public: virtual unsigned get_seen_count() const = 0; }; + class Argument + { + protected: + Argument() { } + public: + virtual ~Argument() { } + + virtual Argument &set_help(const std::string &) = 0; + }; + private: class Store { @@ -122,6 +142,26 @@ private: void process(const std::string &); }; + class ArgumentImpl: public Argument + { + private: + std::string name; + ArgType type; + std::string help; + Store *store; + + public: + ArgumentImpl(const std::string &, const Store &, ArgType); + virtual ~ArgumentImpl(); + + virtual ArgumentImpl &set_help(const std::string &); + const std::string &get_name() const { return name; } + ArgType get_type() const { return type; } + const std::string &get_help() const { return help; } + bool is_list_store() const { return store->is_list(); } + void process(const std::string &); + }; + template class SimpleStore: public Store { @@ -163,17 +203,20 @@ private: }; typedef std::list OptionList; + typedef std::list ArgumentList; bool help; OptionList opts; - std::vector args; + ArgumentList args; + std::vector args_raw; public: GetOpt(); ~GetOpt(); - /// Returns any non-option arguments encountered during processing. - const std::vector &get_args() const { return args; } + /** Returns any non-option arguments encountered during processing. + Deprecated. */ + const std::vector &get_args() const { return args_raw; } /** Adds an option with both short and long forms. Processing depends on the type of the destination variable and whether an argument is taken or @@ -196,8 +239,22 @@ public: Option &add_option(const std::string &l, T &d, ArgType a) { return add_option(0, l, d, a); } + /** Adds a positional argument. The value will be lexical_cast to the + appropriate type and stored in the destination. */ + template + Argument &add_argument(const std::string &n, T &d, ArgType a = REQUIRED_ARG) + { return add_argument(n, SimpleStore(d), a); } + + /** Adds a positional argument list. If the list is declared as required, + at least one element must be given; an optional list may be empty. Only one + list may be added, and optional fixed arguments can't be used with it. */ + template + Argument &add_argument(const std::string &n, std::list &d, ArgType a = REQUIRED_ARG) + { return add_argument(n, ListStore >(d), a); } + private: OptionImpl &add_option(char, const std::string &, const Store &, ArgType); + ArgumentImpl &add_argument(const std::string &, const Store &, ArgType); OptionImpl &get_option(char); OptionImpl &get_option(const std::string &); @@ -215,11 +272,11 @@ private: unsigned process_short(const char *const *); public: - /** Generates a single line that describes known options. */ + /** Generates a single line that describes known options and arguments. */ std::string generate_usage(const std::string &) const; - /** Generates help for known options in tabular format, one option per - line. The returned string will have a linefeed at the end. */ + /** Generates help for known options and arguments in tabular format, one + item per line. The returned string will have a linefeed at the end. */ std::string generate_help() const; }; -- 2.43.0