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
* limitations under the License.
*
* Copyright 2013 Danny Robson <danny@nerdcruft.net>
* Copyright 2013-2016 Danny Robson <danny@nerdcruft.net>
*/
#include "cmdopt.hpp"
@ -23,25 +23,11 @@
#include <iostream>
#include <iomanip>
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 <typename T>
count<T>::count (std::string _name, std::string _description, T &_data):
value<T> (std::move (_name), std::move (_description), _data)
count<T>::count (T &_data):
value<T> (_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<std::unique_ptr<option::base>> (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<std::unique_ptr<option::base>> (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<std::string> (i) == key; });
if (handle_pos == m_long.end ())
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
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<char> (j) == letter; });
if (hpos == m_short.end ())
throw invalid_key (std::to_string (letter));
std::get<1> (*hpos).execute ();
std::get<option::base&> (*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<char> (i) == letter; });
if (hpos == m_short.end ())
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;
}
@ -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<char> (o) << '\t'
<< std::setw (longwidth) << std::get<std::string> (o) << '\t'
<< std::setw (longexample) << std::get<std::unique_ptr<option::base>> (o)->example () << '\t'
<< std::setw (0) << std::get<2> (o)->description ()
auto ptr = std::get<std::unique_ptr<option::base>> (o).get ();
auto s = std::find_if (
std::cbegin (m_short),
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';
}
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
* limitations under the License.
*
* Copyright 2013 Danny Robson <danny@nerdcruft.net>
* Copyright 2013-2016 Danny Robson <danny@nerdcruft.net>
*/
#ifndef __UTIL_CMDLINE_HPP
#define __UTIL_CMDLINE_HPP
#include <functional>
#include <memory>
#include <tuple>
#include <vector>
#include <stdexcept>
#include <string>
#include <tuple>
#include <vector>
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 <typename T>
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 <typename T = unsigned>
class count : public value<T> {
public:
count (std::string name, std::string description, T&);
explicit count (T&);
using value<T>::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 <typename T, typename ...Args>
@ -148,6 +128,10 @@ namespace util { namespace cmdopt {
std::string description,
Args&&...);
template <typename T, typename ...Args>
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<short_t> m_short;
std::vector<long_t> m_long;
std::vector<std::reference_wrapper<option::base>> m_positional;
std::vector<
std::tuple<
char,
std::string,
std::unique_ptr<option::base>>
std::string, // description
std::unique_ptr<option::base>
>
> 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"

View File

@ -11,7 +11,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2013 Danny Robson <danny@nerdcruft.net>
* Copyright 2013-2016 Danny Robson <danny@nerdcruft.net>
*/
#ifdef __UTIL_CMDLINE_IPP
@ -27,8 +27,7 @@
namespace util { namespace cmdopt {
///////////////////////////////////////////////////////////////////////////
template <typename T>
option::value<T>::value (std::string _name, std::string _description, T &_data):
base (std::move (_name), std::move (_description)),
option::value<T>::value (T &_data):
m_data (_data)
{ ; }
@ -130,14 +129,28 @@ namespace util { namespace cmdopt {
std::string description,
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;
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 <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"
//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
// Check that null options don't strhrow anything
static
void
test_null (util::TAP::logger &tap)
{
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" };
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<typename T>
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<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
main (int, char **) {
util::TAP::logger tap;
@ -229,6 +260,7 @@ main (int, char **) {
test_numeric<uint64_t> (tap);
test_bytes (tap);
test_required (tap);
test_positional (tap);
return tap.status ();
}