diff --git a/cmdopt.cpp b/cmdopt.cpp index b12b20ba..4ead8eda 100644 --- a/cmdopt.cpp +++ b/cmdopt.cpp @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright 2013 Danny Robson + * Copyright 2013-2016 Danny Robson */ #include "cmdopt.hpp" @@ -23,25 +23,11 @@ #include #include -using util::cmdopt::option::base; -using util::cmdopt::option::bytes; -using util::cmdopt::option::count; -using util::cmdopt::option::null; -using util::cmdopt::option::present; -using util::cmdopt::option::value; -using util::cmdopt::parser; +using namespace util::cmdopt; +using namespace util::cmdopt::option; /////////////////////////////////////////////////////////////////////////////// -base::base (std::string _name, std::string _description): - m_required (false), - m_seen (false), - m_name (std::move (_name)), - m_description (std::move (_description)) -{ ; } - - -//----------------------------------------------------------------------------- base::~base () { ; } @@ -50,15 +36,15 @@ base::~base () void base::execute (void) { - throw invalid_null (m_name); + throw invalid_null (); } //----------------------------------------------------------------------------- void -base::execute (const char *restrict) +base::execute (const char *restrict value) { - throw invalid_value (m_name); + throw invalid_value (value); } @@ -75,23 +61,7 @@ void base::finish (void) { if (m_required && !m_seen) - throw invalid_required (m_name); -} - - -//----------------------------------------------------------------------------- -const std::string& -base::name (void) const -{ - return m_name; -} - - -//----------------------------------------------------------------------------- -const std::string& -base::description (void) const -{ - return m_description; + throw invalid_required (); } @@ -128,12 +98,6 @@ base::seen (bool _seen) /////////////////////////////////////////////////////////////////////////////// -null::null (std::string _name, std::string _description): - base (std::move (_name), std::move (_description)) -{ ; } - - -//----------------------------------------------------------------------------- void null::execute (void) { @@ -159,8 +123,7 @@ null::example (void) const /////////////////////////////////////////////////////////////////////////////// -present::present (std::string _name, std::string _description, bool &_data): - base (std::move (_name), std::move (_description)), +present::present (bool &_data): m_data (_data) { ; } @@ -243,8 +206,8 @@ namespace util { namespace cmdopt { namespace option { /////////////////////////////////////////////////////////////////////////////// template -count::count (std::string _name, std::string _description, T &_data): - value (std::move (_name), std::move (_description), _data) +count::count (T &_data): + value (_data) { ; } @@ -294,7 +257,8 @@ suffix_to_multiplier (char c) return util::pow (1024UL, 1); default: - throw util::cmdopt::invalid_value ("bytes"); + const char str[2] = { c, '\0' }; + throw std::invalid_argument (str); } } @@ -310,13 +274,16 @@ bytes::execute (const char *restrict str) CHECK_LE (tail, last); if (tail == str) { - throw invalid_value (name ()); + throw invalid_value (str); } else if (tail == last) { data (val); } else if (tail + 1 == last) { - data (val * suffix_to_multiplier (*tail)); + try { + data (val * suffix_to_multiplier (*tail)); + } catch (const std::invalid_argument&) + { throw invalid_value (str); } } else - throw invalid_value (name ()); + throw invalid_value (str); } @@ -328,7 +295,7 @@ parser::scan (int argc, const char *const *argv) CHECK (argv); for (auto &j: m_options) - std::get<2> (j)->start (); + std::get> (j)->start (); // start iterating after our program's name int i = 1; @@ -337,7 +304,13 @@ parser::scan (int argc, const char *const *argv) // bail if there's no potential for an option if (len < 2 || argv[i][0] != '-') - return i; + break; + + // stop processing named options on '--' + if (len == 2 && argv[i][1] == '-') { + ++i; + break; + } // parse longopt auto inc = argv[i][1] == '-' @@ -348,8 +321,17 @@ parser::scan (int argc, const char *const *argv) i += inc; } + // process the positional arguments + for (size_t cursor = 0; i < argc && cursor < m_positional.size (); ++i, ++cursor) + m_positional[cursor].get ().execute (argv[i]); + + // ensure we've processed all the arguments + if (i != argc) + throw unhandled_argument (i); + + // allow arguments to check if they've been successfully handled for (auto &j: m_options) - std::get<2> (j)->finish (); + std::get> (j)->finish (); return i; } @@ -379,11 +361,11 @@ parser::parse_long (int pos, int argc, const char *const *argv) // find the handler auto handle_pos = std::find_if (m_long.begin (), m_long.end (), - [&] (auto i) { return std::get<0> (i) == key; }); + [&] (auto i) { return std::get (i) == key; }); if (handle_pos == m_long.end ()) throw invalid_key (key); - auto &handler = std::get<1> (*handle_pos); + auto &handler = std::get (*handle_pos); // maybe grab a value from the next atom and dispatch if (!eq) { @@ -425,10 +407,10 @@ parser::parse_short (int pos, int argc, const char *const *argv) auto hpos = std::find_if (m_short.begin (), m_short.end (), - [letter] (auto j) { return std::get<0> (j) == letter; }); + [letter] (auto j) { return std::get (j) == letter; }); if (hpos == m_short.end ()) throw invalid_key (std::to_string (letter)); - std::get<1> (*hpos).execute (); + std::get (*hpos).execute (); } return 1; @@ -438,10 +420,10 @@ parser::parse_short (int pos, int argc, const char *const *argv) auto letter = argv[pos][1]; auto hpos = std::find_if (m_short.begin (), m_short.end (), - [letter] (auto i) { return std::get<0> (i) == letter; }); + [letter] (auto i) { return std::get (i) == letter; }); if (hpos == m_short.end ()) throw invalid_key (std::to_string (letter)); - std::get<1> (*hpos).execute (argv[pos+1]); + std::get (*hpos).execute (argv[pos+1]); return 2; } @@ -492,13 +474,109 @@ parser::print_help (const int argc, std::cout << "usage: " << argv[0] << '\n'; for (auto &o: m_options) { - std::cout << '\t' - << '-' << std::get (o) << '\t' - << std::setw (longwidth) << std::get (o) << '\t' - << std::setw (longexample) << std::get> (o)->example () << '\t' - << std::setw (0) << std::get<2> (o)->description () + auto ptr = std::get> (o).get (); + + auto s = std::find_if ( + std::cbegin (m_short), + std::cend (m_short), + [ptr] (auto j) { + return &std::get (j) == ptr; + } + ); + + auto l = std::find_if ( + std::cbegin (m_long), + std::cend (m_long), + [ptr] (auto j) { + return &std::get (j) == ptr; + } + ); + + std::cout << '\t'; + if (s != std::cend (m_short)) + std::cout << '-' << std::get (*s) << '\t'; + else + std::cout << '\t'; + + std::cout << std::setw (longwidth); + if (l != std::cend (m_long)) + std::cout << std::get (*l) << '\t'; + else + std::cout << ' ' << '\t'; + + std::cout << std::setw (longexample) << ptr->example () << '\t' + << std::setw (0) << std::get (o) << '\n'; } exit (0); } + + +/////////////////////////////////////////////////////////////////////////////// +invalid_key::invalid_key (std::string _key): + m_key (std::move (_key)) +{ ; } + + +//----------------------------------------------------------------------------- +const char* +invalid_key::what (void) const noexcept +{ + return m_key.c_str (); +} + + +/////////////////////////////////////////////////////////////////////////////// +invalid_value::invalid_value (std::string _value): + m_value (_value) +{ ; } + + +//----------------------------------------------------------------------------- +const char* +invalid_value::what (void) const noexcept +{ + return m_value.c_str (); +} + + +/////////////////////////////////////////////////////////////////////////////// +const char* +invalid_null::what (void) const noexcept +{ + static const char WHAT[] = "unexpected null option was encountered"; + return WHAT; +} + + +/////////////////////////////////////////////////////////////////////////////// +const char* +invalid_required::what (void) const noexcept +{ + static const char WHAT[] = "required option not seen"; + return WHAT; +} + + +/////////////////////////////////////////////////////////////////////////////// +unhandled_argument::unhandled_argument (int _index): + m_index (_index) +{ ; } + + +//----------------------------------------------------------------------------- +int +unhandled_argument::index (void) const noexcept +{ + return m_index; +} + + +//----------------------------------------------------------------------------- +const char* +unhandled_argument::what (void) const noexcept +{ + static const char WHAT[] = "unhandled argument"; + return WHAT; +} diff --git a/cmdopt.hpp b/cmdopt.hpp index 41313181..31934175 100644 --- a/cmdopt.hpp +++ b/cmdopt.hpp @@ -11,26 +11,30 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright 2013 Danny Robson + * Copyright 2013-2016 Danny Robson */ #ifndef __UTIL_CMDLINE_HPP #define __UTIL_CMDLINE_HPP +#include #include -#include -#include #include #include +#include +#include namespace util { namespace cmdopt { namespace option { class base { - protected: - base (std::string name, std::string description); - public: + // we deal almost exclusively with vtables, so disable copying + // just in case we do something stupid. + base () { } + base (const base&) = delete; + base& operator= (const base&) = delete; + virtual ~base (); virtual void execute (void); @@ -40,9 +44,6 @@ namespace util { namespace cmdopt { virtual const std::string& example (void) const = 0; - const std::string& name (void) const; - const std::string& description (void) const; - bool required (void) const; bool required (bool); @@ -50,18 +51,13 @@ namespace util { namespace cmdopt { bool seen (bool); private: - bool m_required; - bool m_seen; - - std::string m_name; - std::string m_description; + bool m_required = false; + bool m_seen = false; }; class null : public base { public: - explicit null (std::string name, std::string description); - virtual void execute (void) override; virtual void execute (const char *restrict) override; @@ -71,7 +67,7 @@ namespace util { namespace cmdopt { class present : public base { public: - present (std::string name, std::string description, bool&); + explicit present (bool&); using base::execute; virtual void execute (void) override; @@ -88,7 +84,7 @@ namespace util { namespace cmdopt { template class value : public base { public: - value (std::string name, std::string description, T&); + explicit value (T&); using base::execute; void execute (const char *restrict) override; @@ -107,7 +103,7 @@ namespace util { namespace cmdopt { template class count : public value { public: - count (std::string name, std::string description, T&); + explicit count (T&); using value::execute; void execute (void) override; @@ -124,22 +120,6 @@ namespace util { namespace cmdopt { } - class error : public std::runtime_error - { using runtime_error::runtime_error; }; - - class invalid_key : public error - { using error::error; }; - - class invalid_value : public error - { using error::error; }; - - class invalid_null : public error - { using error::error; }; - - class invalid_required : public error - { using error::error; }; - - class parser { public: template @@ -148,6 +128,10 @@ namespace util { namespace cmdopt { std::string description, Args&&...); + template + T& + append (std::string description, Args&&...); + int scan (int argc, const char *const *argv); private: @@ -161,14 +145,57 @@ namespace util { namespace cmdopt { std::vector m_short; std::vector m_long; + std::vector> m_positional; std::vector< std::tuple< - char, - std::string, - std::unique_ptr> + std::string, // description + std::unique_ptr + > > m_options; }; + + + class error : public std::exception { }; + + class invalid_key : public error { + public: + invalid_key (std::string _key); + virtual const char* what (void) const noexcept override; + + private: + const std::string m_key; + }; + + class invalid_value : public error { + public: + invalid_value (std::string _value); + virtual const char* what (void) const noexcept override; + + private: + const std::string m_value; + }; + + class invalid_null : public error { + public: + virtual const char* what (void) const noexcept override; + }; + + class invalid_required : public error { + public: + virtual const char* what (void) const noexcept override; + }; + + class unhandled_argument : public error { + public: + unhandled_argument (int index); + virtual const char* what (void) const noexcept override; + + int index (void) const noexcept; + + private: + const int m_index; + }; } } #include "cmdopt.ipp" diff --git a/cmdopt.ipp b/cmdopt.ipp index 2da603ed..ae0afaeb 100644 --- a/cmdopt.ipp +++ b/cmdopt.ipp @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright 2013 Danny Robson + * Copyright 2013-2016 Danny Robson */ #ifdef __UTIL_CMDLINE_IPP @@ -27,8 +27,7 @@ namespace util { namespace cmdopt { /////////////////////////////////////////////////////////////////////////// template - option::value::value (std::string _name, std::string _description, T &_data): - base (std::move (_name), std::move (_description)), + option::value::value (T &_data): m_data (_data) { ; } @@ -130,14 +129,28 @@ namespace util { namespace cmdopt { std::string description, Args&&... args) { - auto handler = std::make_unique (longname, description, std::forward (args)...); + auto handler = std::make_unique (std::forward (args)...); T& ref = *handler; m_short.emplace_back (shortname, ref); - m_long .emplace_back (longname, ref); + m_long .emplace_back (std::move (longname), ref); - m_options.emplace_back (shortname, longname, std::move (handler)); + m_options.emplace_back (std::move (description), std::move (handler)); return ref; } + + + //------------------------------------------------------------------------- + template + T& + parser::append (std::string description, + Args &&...args) + { + auto handler = std::make_unique (std::forward (args)...); + auto &ref = *handler; + m_positional.push_back (ref); + m_options.emplace_back (std::move (description), std::move (handler)); + return ref; + } } } diff --git a/test/cmdopt.cpp b/test/cmdopt.cpp index e65b7d2e..356301fa 100644 --- a/test/cmdopt.cpp +++ b/test/cmdopt.cpp @@ -6,13 +6,14 @@ #include "maths.hpp" -//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// // Check that null options don't strhrow anything +static void test_null (util::TAP::logger &tap) { util::cmdopt::parser p; - auto n = p.add ('n', "null", "testing null option"); + p.add ('n', "null", "testing null option"); static const char *argv1[] = { "./foo", "-n", "foo" }; tap.expect_nothrow ([&] () { @@ -26,8 +27,9 @@ test_null (util::TAP::logger &tap) } -//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// // Check if presence options can be used successfully +static void test_present (util::TAP::logger &tap) { @@ -62,8 +64,9 @@ test_present (util::TAP::logger &tap) } -//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// // Check all forms of boolean inputs +static void test_bool (util::TAP::logger &tap) { @@ -102,8 +105,9 @@ test_bool (util::TAP::logger &tap) } -//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// template +static void test_numeric (util::TAP::logger &tap) { @@ -152,7 +156,8 @@ test_numeric (util::TAP::logger &tap) } -//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// +static void test_bytes (util::TAP::logger &tap) { @@ -186,7 +191,8 @@ test_bytes (util::TAP::logger &tap) } -//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// +static void test_required (util::TAP::logger &tap) { @@ -213,7 +219,32 @@ test_required (util::TAP::logger &tap) } -//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// +static +void +test_positional (util::TAP::logger &tap) +{ + unsigned value = 0xdead; + constexpr unsigned expected = 0xbeef; + + util::cmdopt::parser p; + p.append> ("unsigned test", value); + + static const char *argv[] = { + "./cpptest", + "--", + "48879", + }; + + tap.expect_nothrow ([&] { + p.scan (elems (argv), argv); + }, "positional, nothrow"); + + tap.expect_eq (value, expected, "positiona, value success"); +} + + +/////////////////////////////////////////////////////////////////////////////// int main (int, char **) { util::TAP::logger tap; @@ -229,6 +260,7 @@ main (int, char **) { test_numeric (tap); test_bytes (tap); test_required (tap); + test_positional (tap); return tap.status (); }