cmdopt: add positional argument support

This commit is contained in:
Danny Robson 2016-03-15 13:55:49 +11:00
parent 8ea827aab0
commit f13c4487c6
4 changed files with 267 additions and 117 deletions

View File

@ -11,7 +11,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
* *
* Copyright 2013 Danny Robson <danny@nerdcruft.net> * Copyright 2013-2016 Danny Robson <danny@nerdcruft.net>
*/ */
#include "cmdopt.hpp" #include "cmdopt.hpp"
@ -23,25 +23,11 @@
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
using util::cmdopt::option::base; using namespace util::cmdopt;
using util::cmdopt::option::bytes; using namespace util::cmdopt::option;
using util::cmdopt::option::count;
using util::cmdopt::option::null;
using util::cmdopt::option::present;
using util::cmdopt::option::value;
using util::cmdopt::parser;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
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 () base::~base ()
{ ; } { ; }
@ -50,15 +36,15 @@ base::~base ()
void void
base::execute (void) base::execute (void)
{ {
throw invalid_null (m_name); throw invalid_null ();
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void 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) base::finish (void)
{ {
if (m_required && !m_seen) if (m_required && !m_seen)
throw invalid_required (m_name); throw invalid_required ();
}
//-----------------------------------------------------------------------------
const std::string&
base::name (void) const
{
return m_name;
}
//-----------------------------------------------------------------------------
const std::string&
base::description (void) const
{
return m_description;
} }
@ -128,12 +98,6 @@ base::seen (bool _seen)
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
null::null (std::string _name, std::string _description):
base (std::move (_name), std::move (_description))
{ ; }
//-----------------------------------------------------------------------------
void void
null::execute (void) null::execute (void)
{ {
@ -159,8 +123,7 @@ null::example (void) const
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
present::present (std::string _name, std::string _description, bool &_data): present::present (bool &_data):
base (std::move (_name), std::move (_description)),
m_data (_data) m_data (_data)
{ ; } { ; }
@ -243,8 +206,8 @@ namespace util { namespace cmdopt { namespace option {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
template <typename T> template <typename T>
count<T>::count (std::string _name, std::string _description, T &_data): count<T>::count (T &_data):
value<T> (std::move (_name), std::move (_description), _data) value<T> (_data)
{ ; } { ; }
@ -294,7 +257,8 @@ suffix_to_multiplier (char c)
return util::pow (1024UL, 1); return util::pow (1024UL, 1);
default: 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); CHECK_LE (tail, last);
if (tail == str) { if (tail == str) {
throw invalid_value (name ()); throw invalid_value (str);
} else if (tail == last) { } else if (tail == last) {
data (val); data (val);
} else if (tail + 1 == last) { } else if (tail + 1 == last) {
try {
data (val * suffix_to_multiplier (*tail)); data (val * suffix_to_multiplier (*tail));
} catch (const std::invalid_argument&)
{ throw invalid_value (str); }
} else } else
throw invalid_value (name ()); throw invalid_value (str);
} }
@ -328,7 +295,7 @@ parser::scan (int argc, const char *const *argv)
CHECK (argv); CHECK (argv);
for (auto &j: m_options) for (auto &j: m_options)
std::get<2> (j)->start (); std::get<std::unique_ptr<option::base>> (j)->start ();
// start iterating after our program's name // start iterating after our program's name
int i = 1; int i = 1;
@ -337,7 +304,13 @@ parser::scan (int argc, const char *const *argv)
// bail if there's no potential for an option // bail if there's no potential for an option
if (len < 2 || argv[i][0] != '-') if (len < 2 || argv[i][0] != '-')
return i; break;
// stop processing named options on '--'
if (len == 2 && argv[i][1] == '-') {
++i;
break;
}
// parse longopt // parse longopt
auto inc = argv[i][1] == '-' auto inc = argv[i][1] == '-'
@ -348,8 +321,17 @@ parser::scan (int argc, const char *const *argv)
i += inc; 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) for (auto &j: m_options)
std::get<2> (j)->finish (); std::get<std::unique_ptr<option::base>> (j)->finish ();
return i; return i;
} }
@ -379,11 +361,11 @@ parser::parse_long (int pos, int argc, const char *const *argv)
// find the handler // find the handler
auto handle_pos = std::find_if (m_long.begin (), auto handle_pos = std::find_if (m_long.begin (),
m_long.end (), m_long.end (),
[&] (auto i) { return std::get<0> (i) == key; }); [&] (auto i) { return std::get<std::string> (i) == key; });
if (handle_pos == m_long.end ()) if (handle_pos == m_long.end ())
throw invalid_key (key); throw invalid_key (key);
auto &handler = std::get<1> (*handle_pos); auto &handler = std::get<option::base&> (*handle_pos);
// maybe grab a value from the next atom and dispatch // maybe grab a value from the next atom and dispatch
if (!eq) { 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 (), auto hpos = std::find_if (m_short.begin (),
m_short.end (), m_short.end (),
[letter] (auto j) { return std::get<0> (j) == letter; }); [letter] (auto j) { return std::get<char> (j) == letter; });
if (hpos == m_short.end ()) if (hpos == m_short.end ())
throw invalid_key (std::to_string (letter)); throw invalid_key (std::to_string (letter));
std::get<1> (*hpos).execute (); std::get<option::base&> (*hpos).execute ();
} }
return 1; return 1;
@ -438,10 +420,10 @@ parser::parse_short (int pos, int argc, const char *const *argv)
auto letter = argv[pos][1]; auto letter = argv[pos][1];
auto hpos = std::find_if (m_short.begin (), auto hpos = std::find_if (m_short.begin (),
m_short.end (), m_short.end (),
[letter] (auto i) { return std::get<0> (i) == letter; }); [letter] (auto i) { return std::get<char> (i) == letter; });
if (hpos == m_short.end ()) if (hpos == m_short.end ())
throw invalid_key (std::to_string (letter)); throw invalid_key (std::to_string (letter));
std::get<1> (*hpos).execute (argv[pos+1]); std::get<option::base&> (*hpos).execute (argv[pos+1]);
return 2; return 2;
} }
@ -492,13 +474,109 @@ parser::print_help (const int argc,
std::cout << "usage: " << argv[0] << '\n'; std::cout << "usage: " << argv[0] << '\n';
for (auto &o: m_options) { for (auto &o: m_options) {
std::cout << '\t' auto ptr = std::get<std::unique_ptr<option::base>> (o).get ();
<< '-' << std::get<char> (o) << '\t'
<< std::setw (longwidth) << std::get<std::string> (o) << '\t' auto s = std::find_if (
<< std::setw (longexample) << std::get<std::unique_ptr<option::base>> (o)->example () << '\t' std::cbegin (m_short),
<< std::setw (0) << std::get<2> (o)->description () std::cend (m_short),
[ptr] (auto j) {
return &std::get<option::base&> (j) == ptr;
}
);
auto l = std::find_if (
std::cbegin (m_long),
std::cend (m_long),
[ptr] (auto j) {
return &std::get<option::base&> (j) == ptr;
}
);
std::cout << '\t';
if (s != std::cend (m_short))
std::cout << '-' << std::get<char> (*s) << '\t';
else
std::cout << '\t';
std::cout << std::setw (longwidth);
if (l != std::cend (m_long))
std::cout << std::get<std::string> (*l) << '\t';
else
std::cout << ' ' << '\t';
std::cout << std::setw (longexample) << ptr->example () << '\t'
<< std::setw (0) << std::get<std::string> (o)
<< '\n'; << '\n';
} }
exit (0); 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;
}

View File

@ -11,26 +11,30 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
* *
* Copyright 2013 Danny Robson <danny@nerdcruft.net> * Copyright 2013-2016 Danny Robson <danny@nerdcruft.net>
*/ */
#ifndef __UTIL_CMDLINE_HPP #ifndef __UTIL_CMDLINE_HPP
#define __UTIL_CMDLINE_HPP #define __UTIL_CMDLINE_HPP
#include <functional>
#include <memory> #include <memory>
#include <tuple>
#include <vector>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <tuple>
#include <vector>
namespace util { namespace cmdopt { namespace util { namespace cmdopt {
namespace option { namespace option {
class base { class base {
protected:
base (std::string name, std::string description);
public: 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 ~base ();
virtual void execute (void); virtual void execute (void);
@ -40,9 +44,6 @@ namespace util { namespace cmdopt {
virtual const std::string& example (void) const = 0; 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 (void) const;
bool required (bool); bool required (bool);
@ -50,18 +51,13 @@ namespace util { namespace cmdopt {
bool seen (bool); bool seen (bool);
private: private:
bool m_required; bool m_required = false;
bool m_seen; bool m_seen = false;
std::string m_name;
std::string m_description;
}; };
class null : public base { class null : public base {
public: public:
explicit null (std::string name, std::string description);
virtual void execute (void) override; virtual void execute (void) override;
virtual void execute (const char *restrict) override; virtual void execute (const char *restrict) override;
@ -71,7 +67,7 @@ namespace util { namespace cmdopt {
class present : public base { class present : public base {
public: public:
present (std::string name, std::string description, bool&); explicit present (bool&);
using base::execute; using base::execute;
virtual void execute (void) override; virtual void execute (void) override;
@ -88,7 +84,7 @@ namespace util { namespace cmdopt {
template <typename T> template <typename T>
class value : public base { class value : public base {
public: public:
value (std::string name, std::string description, T&); explicit value (T&);
using base::execute; using base::execute;
void execute (const char *restrict) override; void execute (const char *restrict) override;
@ -107,7 +103,7 @@ namespace util { namespace cmdopt {
template <typename T = unsigned> template <typename T = unsigned>
class count : public value<T> { class count : public value<T> {
public: public:
count (std::string name, std::string description, T&); explicit count (T&);
using value<T>::execute; using value<T>::execute;
void execute (void) override; 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 { class parser {
public: public:
template <typename T, typename ...Args> template <typename T, typename ...Args>
@ -148,6 +128,10 @@ namespace util { namespace cmdopt {
std::string description, std::string description,
Args&&...); Args&&...);
template <typename T, typename ...Args>
T&
append (std::string description, Args&&...);
int scan (int argc, const char *const *argv); int scan (int argc, const char *const *argv);
private: private:
@ -161,14 +145,57 @@ namespace util { namespace cmdopt {
std::vector<short_t> m_short; std::vector<short_t> m_short;
std::vector<long_t> m_long; std::vector<long_t> m_long;
std::vector<std::reference_wrapper<option::base>> m_positional;
std::vector< std::vector<
std::tuple< std::tuple<
char, std::string, // description
std::string, std::unique_ptr<option::base>
std::unique_ptr<option::base>> >
> m_options; > 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" #include "cmdopt.ipp"

View File

@ -11,7 +11,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
* *
* Copyright 2013 Danny Robson <danny@nerdcruft.net> * Copyright 2013-2016 Danny Robson <danny@nerdcruft.net>
*/ */
#ifdef __UTIL_CMDLINE_IPP #ifdef __UTIL_CMDLINE_IPP
@ -27,8 +27,7 @@
namespace util { namespace cmdopt { namespace util { namespace cmdopt {
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
template <typename T> template <typename T>
option::value<T>::value (std::string _name, std::string _description, T &_data): option::value<T>::value (T &_data):
base (std::move (_name), std::move (_description)),
m_data (_data) m_data (_data)
{ ; } { ; }
@ -130,14 +129,28 @@ namespace util { namespace cmdopt {
std::string description, std::string description,
Args&&... args) Args&&... args)
{ {
auto handler = std::make_unique<T> (longname, description, std::forward<Args> (args)...); auto handler = std::make_unique<T> (std::forward<Args> (args)...);
T& ref = *handler; T& ref = *handler;
m_short.emplace_back (shortname, ref); 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; return ref;
} }
//-------------------------------------------------------------------------
template <typename T, typename ...Args>
T&
parser::append (std::string description,
Args &&...args)
{
auto handler = std::make_unique<T> (std::forward<Args> (args)...);
auto &ref = *handler;
m_positional.push_back (ref);
m_options.emplace_back (std::move (description), std::move (handler));
return ref;
}
} } } }

View File

@ -6,13 +6,14 @@
#include "maths.hpp" #include "maths.hpp"
//----------------------------------------------------------------------------- ///////////////////////////////////////////////////////////////////////////////
// Check that null options don't strhrow anything // Check that null options don't strhrow anything
static
void void
test_null (util::TAP::logger &tap) test_null (util::TAP::logger &tap)
{ {
util::cmdopt::parser p; util::cmdopt::parser p;
auto n = p.add<util::cmdopt::option::null> ('n', "null", "testing null option"); p.add<util::cmdopt::option::null> ('n', "null", "testing null option");
static const char *argv1[] = { "./foo", "-n", "foo" }; static const char *argv1[] = { "./foo", "-n", "foo" };
tap.expect_nothrow ([&] () { tap.expect_nothrow ([&] () {
@ -26,8 +27,9 @@ test_null (util::TAP::logger &tap)
} }
//----------------------------------------------------------------------------- ///////////////////////////////////////////////////////////////////////////////
// Check if presence options can be used successfully // Check if presence options can be used successfully
static
void void
test_present (util::TAP::logger &tap) test_present (util::TAP::logger &tap)
{ {
@ -62,8 +64,9 @@ test_present (util::TAP::logger &tap)
} }
//----------------------------------------------------------------------------- ///////////////////////////////////////////////////////////////////////////////
// Check all forms of boolean inputs // Check all forms of boolean inputs
static
void void
test_bool (util::TAP::logger &tap) test_bool (util::TAP::logger &tap)
{ {
@ -102,8 +105,9 @@ test_bool (util::TAP::logger &tap)
} }
//----------------------------------------------------------------------------- ///////////////////////////////////////////////////////////////////////////////
template<typename T> template<typename T>
static
void void
test_numeric (util::TAP::logger &tap) test_numeric (util::TAP::logger &tap)
{ {
@ -152,7 +156,8 @@ test_numeric (util::TAP::logger &tap)
} }
//----------------------------------------------------------------------------- ///////////////////////////////////////////////////////////////////////////////
static
void void
test_bytes (util::TAP::logger &tap) test_bytes (util::TAP::logger &tap)
{ {
@ -186,7 +191,8 @@ test_bytes (util::TAP::logger &tap)
} }
//----------------------------------------------------------------------------- ///////////////////////////////////////////////////////////////////////////////
static
void void
test_required (util::TAP::logger &tap) 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<util::cmdopt::option::value<unsigned>> ("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 int
main (int, char **) { main (int, char **) {
util::TAP::logger tap; util::TAP::logger tap;
@ -229,6 +260,7 @@ main (int, char **) {
test_numeric<uint64_t> (tap); test_numeric<uint64_t> (tap);
test_bytes (tap); test_bytes (tap);
test_required (tap); test_required (tap);
test_positional (tap);
return tap.status (); return tap.status ();
} }