Danny Robson
f6056153e3
This places, at long last, the core library code into the same namespace as the extended library code.
511 lines
12 KiB
C++
511 lines
12 KiB
C++
/*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* Copyright 2013-2016 Danny Robson <danny@nerdcruft.net>
|
|
*/
|
|
|
|
#include "cmdopt.hpp"
|
|
|
|
#include "cast.hpp"
|
|
#include "debug.hpp"
|
|
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
|
|
using namespace cruft::cmdopt;
|
|
using namespace cruft::cmdopt::option;
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
base::~base ()
|
|
{ ; }
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
base::execute (void)
|
|
{
|
|
throw invalid_null ();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
base::execute (const char *restrict value)
|
|
{
|
|
throw invalid_value (value);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
base::start (void)
|
|
{
|
|
m_seen = false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
base::finish (void)
|
|
{
|
|
if (m_required && !m_seen)
|
|
throw invalid_required ();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool
|
|
base::required (void) const
|
|
{
|
|
return m_required;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool
|
|
base::required (bool _required)
|
|
{
|
|
return m_required = _required;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool
|
|
base::seen (void) const
|
|
{
|
|
return m_seen;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool
|
|
base::seen (bool _seen)
|
|
{
|
|
return m_seen = _seen;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void
|
|
null::execute (void)
|
|
{
|
|
seen (true);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
null::execute (const char *restrict)
|
|
{
|
|
seen (true);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
const std::string&
|
|
null::example (void) const
|
|
{
|
|
static const std::string EXAMPLE;
|
|
return EXAMPLE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
present::present (bool &_data):
|
|
m_data (_data)
|
|
{ ; }
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
present::execute (void)
|
|
{
|
|
seen (true);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
const std::string&
|
|
present::example (void) const
|
|
{
|
|
static const std::string EXAMPLE;
|
|
return EXAMPLE;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
present::finish (void)
|
|
{
|
|
m_data = seen ();
|
|
base::finish ();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
namespace cruft::cmdopt::option {
|
|
template class value<uint16_t>;
|
|
template class value<uint32_t>;
|
|
template class value<uint64_t>;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
template <typename T>
|
|
count<T>::count (T &_data):
|
|
value<T> (_data)
|
|
{ ; }
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
template <typename T>
|
|
void
|
|
count<T>::execute (void)
|
|
{
|
|
++this->data ();
|
|
this->seen (true);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
namespace cruft::cmdopt::option {
|
|
template class count<unsigned>;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
static size_t
|
|
suffix_to_multiplier (char c)
|
|
{
|
|
switch (c) {
|
|
case 'e':
|
|
case 'E':
|
|
return cruft::pow (1024UL, 6u);
|
|
|
|
case 'p':
|
|
case 'P':
|
|
return cruft::pow (1024UL, 5u);
|
|
|
|
case 't':
|
|
case 'T':
|
|
return cruft::pow (1024UL, 4u);
|
|
|
|
case 'g':
|
|
case 'G':
|
|
return cruft::pow (1024UL, 3u);
|
|
|
|
case 'm':
|
|
case 'M':
|
|
return cruft::pow (1024UL, 2u);
|
|
|
|
case 'k':
|
|
case 'K':
|
|
return cruft::pow (1024UL, 1u);
|
|
|
|
default:
|
|
const char str[2] = { c, '\0' };
|
|
throw std::invalid_argument (str);
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void
|
|
bytes::execute (const char *restrict str)
|
|
{
|
|
const char *tail;
|
|
const char *last = str + strlen (str);
|
|
unsigned long val = std::strtoul (const_cast<char *> (str), const_cast<char**> (&tail), 10);
|
|
|
|
CHECK_LE (tail, last);
|
|
|
|
if (tail == str) {
|
|
throw invalid_value (str);
|
|
} else if (tail == last) {
|
|
data (val);
|
|
} else if (tail + 1 == last) {
|
|
try {
|
|
data (val * suffix_to_multiplier (*tail));
|
|
} catch (const std::invalid_argument&)
|
|
{ throw invalid_value (str); }
|
|
} else
|
|
throw invalid_value (str);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
int
|
|
parser::scan (int argc, char const *const *argv)
|
|
{
|
|
CHECK_GT (argc, 0);
|
|
CHECK (argv);
|
|
|
|
for (auto &j: m_options)
|
|
j.handler->start ();
|
|
|
|
// start iterating after our program's name
|
|
int i = 1;
|
|
while (i < argc) {
|
|
auto arg = argv[i];
|
|
auto len = strlen (arg);
|
|
|
|
// bail if there's no potential for an option
|
|
if (len < 2 || arg[0] != '-')
|
|
break;
|
|
|
|
// stop processing named options on '--'
|
|
if (len == 2 && arg[1] == '-') {
|
|
++i;
|
|
break;
|
|
}
|
|
|
|
// parse longopt
|
|
auto inc = arg[1] == '-'
|
|
? parse_long (i, argc, argv)
|
|
: parse_short (i, argc, argv);
|
|
|
|
CHECK_GT (inc, 0);
|
|
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)
|
|
j.handler->finish ();
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int
|
|
parser::parse_long (int pos, int argc, const char *const *argv)
|
|
{
|
|
CHECK_LT (pos, argc);
|
|
CHECK_GE (pos, 0);
|
|
CHECK_GE (argc, 0);
|
|
CHECK (argv);
|
|
|
|
CHECK_EQ (argv[pos][0], '-');
|
|
CHECK_EQ (argv[pos][1], '-');
|
|
|
|
// break first atom into components and extract the key
|
|
const char *start = argv[pos] + 2;
|
|
const char *eq = strchr (start, '=');
|
|
const char *last = start + strlen (start);
|
|
|
|
std::string key { start, eq ? eq : last };
|
|
if (key == "help")
|
|
print_help (argc, argv);
|
|
|
|
auto &handler = m_long.at (key).get ();
|
|
|
|
// maybe grab a value from the next atom and dispatch
|
|
if (!eq) {
|
|
// check the next atom for the value
|
|
if (pos + 1 < argc)
|
|
if (argv[pos + 1][0] != '-') {
|
|
handler.execute (argv[pos+1]);
|
|
return 2;
|
|
}
|
|
|
|
handler.execute ();
|
|
} else {
|
|
handler.execute (eq+1);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
int
|
|
parser::parse_short (int pos, int argc, const char *const *argv)
|
|
{
|
|
CHECK_LT (pos, argc);
|
|
CHECK_GE (pos, 0);
|
|
CHECK_GE (argc, 0);
|
|
CHECK (argv);
|
|
|
|
CHECK_EQ (argv[pos][0], '-');
|
|
CHECK_NEQ (argv[pos][1], '-');
|
|
|
|
// we have a run of no-value keys
|
|
auto len = strlen (argv[pos]);
|
|
if (len > 2 || pos + 1 == argc || argv[pos+1][0] == '-') {
|
|
for (size_t i = 1; i < len; ++i) {
|
|
auto letter = argv[pos][i];
|
|
if (letter == 'h')
|
|
print_help (argc, argv);
|
|
|
|
m_short.at(letter).get ().execute ();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// we have a value following
|
|
auto letter = argv[pos][1];
|
|
m_short.at (letter).get ().execute (argv[pos + 1]);
|
|
return 2;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void
|
|
parser::print_help (const int argc,
|
|
const char *const *argv) const
|
|
{
|
|
(void)argc;
|
|
|
|
CHECK_EQ (m_short.size (), m_options.size ());
|
|
CHECK_EQ (m_long.size (), m_options.size ());
|
|
|
|
if (m_options.empty ())
|
|
exit (0);
|
|
|
|
// find the longest long form argument so we can set field alignment
|
|
auto largestwidth = std::max_element (
|
|
m_long.begin (),
|
|
m_long.end (),
|
|
[] (const auto &a, const auto &b)
|
|
{
|
|
auto const &[a_key,a_obj] = a;
|
|
auto const &[b_key,b_obj] = b;
|
|
|
|
return a_key.size () < b_key.size ();
|
|
});
|
|
|
|
auto longwidth = largestwidth->first.size ();
|
|
|
|
// find the longest example text
|
|
auto largestexample = std::max_element (
|
|
m_options.cbegin (),
|
|
m_options.cend (),
|
|
[] (const auto &a, const auto &b)
|
|
{
|
|
return a.handler->example ().size () > b.handler->example ().size ();
|
|
});
|
|
|
|
auto longexample = largestexample->handler->example ().size ();
|
|
|
|
// field width requires an alignment. we don't care about preserving
|
|
// state as we're about to bail anyway
|
|
std::cout << std::left;
|
|
|
|
// print all the option info
|
|
std::cout << "usage: " << argv[0] << '\n';
|
|
|
|
for (auto &opt: m_options) {
|
|
auto s = std::find_if (
|
|
std::cbegin (m_short),
|
|
std::cend (m_short),
|
|
[&] (auto j) { return &j.second.get () == opt.handler.get (); }
|
|
);
|
|
|
|
auto l = std::find_if (
|
|
std::cbegin (m_long),
|
|
std::cend (m_long),
|
|
[&] (auto j) { return &j.second.get () == opt.handler.get (); }
|
|
);
|
|
|
|
std::cout << '\t';
|
|
if (s != std::cend (m_short))
|
|
std::cout << '-' << s->first << '\t';
|
|
else
|
|
std::cout << '\t';
|
|
|
|
std::cout << std::setw (cruft::cast::lossless<int> (longwidth));
|
|
if (l != std::cend (m_long))
|
|
std::cout << l->first << '\t';
|
|
else
|
|
std::cout << ' ' << '\t';
|
|
|
|
std::cout << std::setw (cruft::cast::lossless<int> (longexample)) << opt.handler->example () << '\t'
|
|
<< std::setw (0) << opt.description
|
|
<< '\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 (std::move (_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;
|
|
}
|