Imported libcmdopt option parsing
This commit is contained in:
parent
5ac2e6e26b
commit
6f4d899c0b
@ -85,6 +85,8 @@ UTIL_FILES = \
|
|||||||
noise/fractal.hpp \
|
noise/fractal.hpp \
|
||||||
noise/lut.cpp \
|
noise/lut.cpp \
|
||||||
noise/lut.hpp \
|
noise/lut.hpp \
|
||||||
|
options.cpp \
|
||||||
|
options.hpp \
|
||||||
platform.hpp \
|
platform.hpp \
|
||||||
point.cpp \
|
point.cpp \
|
||||||
point.hpp \
|
point.hpp \
|
||||||
|
537
options.cpp
Normal file
537
options.cpp
Normal file
@ -0,0 +1,537 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of libgim.
|
||||||
|
*
|
||||||
|
* libgim is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* libgim is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with libgim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Copyright 2013 Danny Robson <danny@nerdcruft.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "options.hpp"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cmath>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace util;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generic option operations, failure or default modes
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
option::option (char _letter,
|
||||||
|
const char *_name,
|
||||||
|
const char *_desc,
|
||||||
|
bool _required):
|
||||||
|
m_shortopt (_letter),
|
||||||
|
m_longopt (_name),
|
||||||
|
m_description(_desc),
|
||||||
|
m_required (_required),
|
||||||
|
m_found (false)
|
||||||
|
{ reset(); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void option::execute (void) {
|
||||||
|
throw runtime_error(
|
||||||
|
"Cannot provide no value for the option '" + m_longopt + "'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void option::execute (const string& data) {
|
||||||
|
assert(data.size() > 0);
|
||||||
|
throw runtime_error(
|
||||||
|
"Cannot provide a value for the option '" + m_longopt + "'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ostream& operator<< (ostream & os, const option& opt) {
|
||||||
|
os << (opt.is_required () ? " -" : "[-" ) << opt.shortopt ()
|
||||||
|
<< (opt.is_required () ? " \t" : "]\t") << opt.longopt ()
|
||||||
|
<< "\t" << opt.description ();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
option::finish (void) {
|
||||||
|
if (m_required && !m_found)
|
||||||
|
throw runtime_error ("Required argument not found: " + m_longopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Nulloption
|
||||||
|
*/
|
||||||
|
|
||||||
|
nulloption::nulloption (char _letter,
|
||||||
|
const char *_name,
|
||||||
|
const char *_desc,
|
||||||
|
bool _required):
|
||||||
|
option (_letter, _name, _desc, _required)
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Present option
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
presentoption::presentoption (char _letter,
|
||||||
|
const char *_name,
|
||||||
|
const char *_desc,
|
||||||
|
bool *_data,
|
||||||
|
bool _required):
|
||||||
|
option (_letter, _name, _desc, _required),
|
||||||
|
m_data (_data)
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
presentoption::execute (void) {
|
||||||
|
*m_data = true;
|
||||||
|
m_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* bytesoption
|
||||||
|
*/
|
||||||
|
|
||||||
|
bytesoption::bytesoption (char _letter,
|
||||||
|
const char *_name,
|
||||||
|
const char *_desc,
|
||||||
|
size_t *_data,
|
||||||
|
bytestype _type,
|
||||||
|
bytesmodifier _modifier,
|
||||||
|
bool _required):
|
||||||
|
valueoption<size_t> (_letter, _name, _desc, _data),
|
||||||
|
m_type (_type),
|
||||||
|
m_modifier (_modifier)
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
|
||||||
|
bytesoption::bytestype
|
||||||
|
bytesoption::type_from_character (char c) {
|
||||||
|
switch (c) {
|
||||||
|
case 'e':
|
||||||
|
case 'E':
|
||||||
|
return BYTES_EXA;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
case 'P':
|
||||||
|
return BYTES_PETA;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
case 'T':
|
||||||
|
return BYTES_TERA;
|
||||||
|
|
||||||
|
case 'g':
|
||||||
|
case 'G':
|
||||||
|
return BYTES_GIGA;
|
||||||
|
|
||||||
|
case 'm':
|
||||||
|
case 'M':
|
||||||
|
return BYTES_MEGA;
|
||||||
|
|
||||||
|
case 'k':
|
||||||
|
case 'K':
|
||||||
|
return BYTES_KILO;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw domain_error("Invalid magnitude specifier");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
bytesoption::execute (const std::string& data) {
|
||||||
|
// We shouldn't get this far into processing with a zero sized argument,
|
||||||
|
// that's what the null data execute function is for.
|
||||||
|
assert (data.size () > 0);
|
||||||
|
|
||||||
|
size_t defaultvalue = *m_data;
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
bytesmodifier modifier = m_modifier;
|
||||||
|
off_t cursor = data.size () - 1;
|
||||||
|
|
||||||
|
// Consume an optional trailing `byte' type
|
||||||
|
if (data[cursor] == 'B' || data[cursor] == 'b') {
|
||||||
|
if (--cursor < 0)
|
||||||
|
throw invalid_argument ("Size is too short");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we explicitly request base2
|
||||||
|
if (data[cursor] == 'i') {
|
||||||
|
modifier = BYTES_BASE2;
|
||||||
|
if (--cursor < 0)
|
||||||
|
throw invalid_argument("Size is too short");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine what constant factor is needed for the raw size
|
||||||
|
uint64_t multiplier = 1;
|
||||||
|
uint64_t modifier_factor;
|
||||||
|
switch (modifier) {
|
||||||
|
case BYTES_BASE2:
|
||||||
|
modifier_factor = 1024;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BYTES_BASE10:
|
||||||
|
modifier_factor = 1000;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
abort ();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the difference in magnitude between what is desired, and what
|
||||||
|
// is specified by the user. Raise the multiplier by that magnitude.
|
||||||
|
bytestype specified = m_type;
|
||||||
|
try {
|
||||||
|
specified = type_from_character (data[cursor]);
|
||||||
|
// If the character is a digit, it just means the user skipped the
|
||||||
|
// size specifier, which is ok.
|
||||||
|
} catch (domain_error x) {
|
||||||
|
if (!isdigit (data[cursor]))
|
||||||
|
throw invalid_argument ("Not a size");
|
||||||
|
|
||||||
|
// Falsely increment the cursor if there's no size specifier...
|
||||||
|
++cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... so that we can easily decrement the cursor without special logic
|
||||||
|
// after reading the specifiers.
|
||||||
|
--cursor;
|
||||||
|
assert (cursor >= 0);
|
||||||
|
|
||||||
|
multiplier = pow ((double)modifier_factor, (int)specified);
|
||||||
|
get_arg (data.substr(0, cursor + 1), m_data);
|
||||||
|
*m_data *= multiplier;
|
||||||
|
} catch (...) {
|
||||||
|
// Ensure that we haven't nuked a default value due to a half
|
||||||
|
// completed calculation followed by an exception.
|
||||||
|
|
||||||
|
*m_data = defaultvalue;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Internal helper options. Print help and usage.
|
||||||
|
* A callback to the processor which triggers output of the help message.
|
||||||
|
*
|
||||||
|
* This should never be instanced by a user of the system. The processor will
|
||||||
|
* automatically adds this to its available options where needed.
|
||||||
|
*/
|
||||||
|
class helpoption : public option {
|
||||||
|
protected:
|
||||||
|
static const char HELP_CHARACTER;
|
||||||
|
static const char *HELP_NAME;
|
||||||
|
static const char *HELP_DESCRIPTION;
|
||||||
|
|
||||||
|
processor * m_processor;
|
||||||
|
|
||||||
|
public:
|
||||||
|
helpoption (processor * _processor):
|
||||||
|
option (HELP_CHARACTER, HELP_NAME, HELP_DESCRIPTION, false),
|
||||||
|
m_processor (_processor)
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
|
||||||
|
virtual void execute (void);
|
||||||
|
virtual void execute (const std::string& data)
|
||||||
|
{ option::execute (data); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const char helpoption::HELP_CHARACTER = 'h';
|
||||||
|
const char *helpoption::HELP_NAME = "help";
|
||||||
|
const char *helpoption::HELP_DESCRIPTION =
|
||||||
|
"display help and usage information";
|
||||||
|
|
||||||
|
|
||||||
|
void helpoption::execute (void) {
|
||||||
|
m_processor->print_usage ();
|
||||||
|
exit (EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Command line processing options
|
||||||
|
*/
|
||||||
|
|
||||||
|
processor::processor ()
|
||||||
|
{ add_option (new helpoption (this)); }
|
||||||
|
|
||||||
|
|
||||||
|
processor::~processor () {
|
||||||
|
for(auto i = m_options.begin(); i != m_options.end(); i++)
|
||||||
|
delete *i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void processor::print_usage (void) {
|
||||||
|
cout << "Usage: " << m_command << " [options]" << endl;
|
||||||
|
|
||||||
|
for(list<option *>::const_iterator i = m_options.begin ();
|
||||||
|
i != m_options.end (); i++)
|
||||||
|
cout << '\t' << **i << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a single argument in short form. We are given an offset into the
|
||||||
|
* argument array, and arguments. Establishes whether a value is present and
|
||||||
|
* passes control over to the option.
|
||||||
|
*
|
||||||
|
* The format of the options are "-f [b]" || "-abcde"
|
||||||
|
*
|
||||||
|
* @param pos the current offset into the argument array
|
||||||
|
* @param argc the size of the argument array
|
||||||
|
* @param argv the argument array given to the application
|
||||||
|
*
|
||||||
|
* @return the number of tokens consumed for this option; must be at least 1.
|
||||||
|
*/
|
||||||
|
unsigned int processor::parse_short (int pos, int argc, const char ** argv) {
|
||||||
|
assert (pos > 0);
|
||||||
|
assert (pos < argc);
|
||||||
|
|
||||||
|
assert (argv[pos] != NULL);
|
||||||
|
const char * arg = argv[pos];
|
||||||
|
|
||||||
|
// Must begin with a dash, then have at least one non-dash character
|
||||||
|
assert (arg[0] == '-');
|
||||||
|
assert (arg[1] != '-');
|
||||||
|
assert (strlen(arg) >= 2);
|
||||||
|
|
||||||
|
// Check if we have a value option, Ie `-a [val]`
|
||||||
|
// First: must be of form '-[a-zA-Z]'.
|
||||||
|
if (strlen (arg) == 2) {
|
||||||
|
// Second: the next segment (which contains the value) shouldn't begin
|
||||||
|
// with a dash.
|
||||||
|
if (pos + 1 < argc && argv[pos+1][0] != '-') {
|
||||||
|
option * o = m_shortopt[arg[1]];
|
||||||
|
if (!o)
|
||||||
|
throw runtime_error ("Cannot match option");
|
||||||
|
|
||||||
|
o->execute (argv[pos+1]);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We must have a string of no-value flags, `-abcdef...`, execute each
|
||||||
|
// option in the list after the dash.
|
||||||
|
for(unsigned int i = 1; i < strlen (arg); ++i) {
|
||||||
|
option * o = m_shortopt[arg[i]];
|
||||||
|
|
||||||
|
if (!o)
|
||||||
|
throw runtime_error ("Cannot match option");
|
||||||
|
o->execute ();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a single argument in long form. We are given an offset into the
|
||||||
|
* argument array, and arguments. Establishes whether a value is present and
|
||||||
|
* passes control over to the option.
|
||||||
|
*
|
||||||
|
* The format of the options are "--foo[=bar]"
|
||||||
|
*
|
||||||
|
* @param pos the current offset into the argument array
|
||||||
|
* @param argc the size of the argument array
|
||||||
|
* @param argv the argument array given to the application`--foo[=bar]`
|
||||||
|
*
|
||||||
|
* @return the number of tokens consumed for this option; must be 1.
|
||||||
|
*/
|
||||||
|
unsigned int processor::parse_long (int pos, int argc, const char ** argv) {
|
||||||
|
assert (pos > 0);
|
||||||
|
assert (pos < argc);
|
||||||
|
|
||||||
|
assert (argv[pos] != NULL);
|
||||||
|
const char * arg = argv[pos];
|
||||||
|
|
||||||
|
// We must have at least two hyphens, and two letters (else a short opt)
|
||||||
|
assert (arg[0] == '-');
|
||||||
|
assert (arg[1] == '-');
|
||||||
|
assert (strlen(arg) >= 4);
|
||||||
|
|
||||||
|
// Skip past the dashes to the argument name
|
||||||
|
arg += 2;
|
||||||
|
|
||||||
|
// If there's an equals it's has a value to extract
|
||||||
|
const char * data = strchr (arg, '=');
|
||||||
|
if( data) {
|
||||||
|
arg = strndup (arg, data - arg); // Copy just the arg name
|
||||||
|
data++; // Skip the '='
|
||||||
|
}
|
||||||
|
|
||||||
|
option * o = m_longopt[arg];
|
||||||
|
if (!o)
|
||||||
|
throw runtime_error ("Cannot match option");
|
||||||
|
|
||||||
|
if(data)
|
||||||
|
o->execute (data);
|
||||||
|
else
|
||||||
|
o->execute ();
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass long and short options to specific parsing handlers.
|
||||||
|
*
|
||||||
|
* Establishes enough context to determine if the argument is long, short, or
|
||||||
|
* none. The functions parse_long and parse_short are given the task of
|
||||||
|
* dispatching control to the relevant option handlers.
|
||||||
|
*
|
||||||
|
* @param argc the raw parameter from the application main
|
||||||
|
* @param argv the raw parameter from the application main
|
||||||
|
*/
|
||||||
|
|
||||||
|
void
|
||||||
|
processor::parse_args (int argc, const char ** argv) {
|
||||||
|
// While technically we can have zero arguments, for practical purposes
|
||||||
|
// this is a reasonable method at catching odd errors in the library.
|
||||||
|
assert (argc > 0);
|
||||||
|
assert (argv[0]);
|
||||||
|
|
||||||
|
m_command.assign (argv[0]);
|
||||||
|
|
||||||
|
// Must make sure each option has a chance to reset state (clear flags,
|
||||||
|
// etc) between parses
|
||||||
|
for(auto i = m_options.begin (); i != m_options.end (); i++)
|
||||||
|
(*i)->reset ();
|
||||||
|
|
||||||
|
const unsigned int FIRST_ARGUMENT = 1;
|
||||||
|
try {
|
||||||
|
for (int i = FIRST_ARGUMENT; i < argc; ++i) {
|
||||||
|
// An argument must begin with a dash, if not we've reached the
|
||||||
|
// end of the argument lists or we have a parsing error.
|
||||||
|
if (argv[i][0] != '-')
|
||||||
|
return;
|
||||||
|
|
||||||
|
// An argument must consist of at least one non-dash character
|
||||||
|
if (strlen (argv[i]) <= 1)
|
||||||
|
throw runtime_error ("Invalid argument");
|
||||||
|
|
||||||
|
// Actually hand off args to be parsed. Subtract one from the
|
||||||
|
// tokens consumed, as the for loop increments tokens too.
|
||||||
|
unsigned int consumed;
|
||||||
|
if (argv[i][1] != '-')
|
||||||
|
consumed = parse_short (i, argc, argv);
|
||||||
|
else
|
||||||
|
consumed = parse_long (i, argc, argv);
|
||||||
|
|
||||||
|
assert (consumed >= 1);
|
||||||
|
i += consumed - 1;
|
||||||
|
}
|
||||||
|
} catch (runtime_error &x) {
|
||||||
|
print_usage ();
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i = m_options.begin (); i != m_options.end (); ++i)
|
||||||
|
(*i)->finish ();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void processor::add_option (option * opt) {
|
||||||
|
if (m_shortopt.find (opt->shortopt ()) != m_shortopt.end ())
|
||||||
|
throw logic_error ("Short option already exists");
|
||||||
|
if (m_longopt.find (opt->longopt ()) != m_longopt.end ())
|
||||||
|
throw logic_error ("Long option already exists");
|
||||||
|
|
||||||
|
m_shortopt[opt->shortopt ()] = opt;
|
||||||
|
m_longopt [opt->longopt ()] = opt;
|
||||||
|
|
||||||
|
m_options.push_back( opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
option* processor::remove_option (char letter) {
|
||||||
|
// Locate the option by short name
|
||||||
|
std::map<char, option*>::iterator s_candidate = m_shortopt.find (letter);
|
||||||
|
|
||||||
|
if (s_candidate == m_shortopt.end ())
|
||||||
|
throw logic_error ("Cannot remove an option which is not present");
|
||||||
|
option * opt = (*s_candidate).second;
|
||||||
|
|
||||||
|
// Locate the long option entry
|
||||||
|
std::map<string, option*>::iterator l_candidate = m_longopt.find (opt->longopt ());
|
||||||
|
assert (l_candidate != m_longopt.end ());
|
||||||
|
|
||||||
|
// Remove all references and return
|
||||||
|
m_shortopt.erase (s_candidate);
|
||||||
|
m_longopt.erase (l_candidate);
|
||||||
|
m_options.remove (opt);
|
||||||
|
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
option* processor::remove_option (const char * name) {
|
||||||
|
// Locate the option by long name
|
||||||
|
std::map<string, option*>::iterator l_candidate = m_longopt.find (name);
|
||||||
|
|
||||||
|
if (l_candidate == m_longopt.end ())
|
||||||
|
throw logic_error ("Cannot remove an option which is not present");
|
||||||
|
option * opt = (*l_candidate).second;
|
||||||
|
|
||||||
|
// Locate the short option entry
|
||||||
|
std::map<char, option*>::iterator s_candidate = m_shortopt.find (opt->shortopt ());
|
||||||
|
assert (s_candidate != m_shortopt.end ());
|
||||||
|
|
||||||
|
// Remove all references and return the option object
|
||||||
|
m_shortopt.erase (s_candidate);
|
||||||
|
m_longopt.erase (l_candidate);
|
||||||
|
m_options.remove (opt);
|
||||||
|
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Parse args from a stream, one arg per line
|
||||||
|
*/
|
||||||
|
void parse_stream (std::istream & is) {
|
||||||
|
char * buffer = new char[MAX_CHUNK_LENGTH + 1];
|
||||||
|
try {
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
delete [] buffer;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
323
options.hpp
Normal file
323
options.hpp
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of libgim.
|
||||||
|
*
|
||||||
|
* libgim is free software: you can redistribute it and/or modify it under the
|
||||||
|
* terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* libgim is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with libgim. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Copyright 2013 Danny Robson <danny@nerdcruft.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef __UTIL_OPTIONS_HPP
|
||||||
|
#define __UTIL_OPTIONS_HPP
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <list>
|
||||||
|
#include <map>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum length of an option, its value, and any supplementary characters
|
||||||
|
* required for interpreting them. Ie, strlen("--foo=bar")
|
||||||
|
*/
|
||||||
|
const size_t MAX_CHUNK_LENGTH = 1023;
|
||||||
|
|
||||||
|
namespace util {
|
||||||
|
/**
|
||||||
|
* The generic base class for all options.
|
||||||
|
*
|
||||||
|
* Stores enough information to determine whether a short or long style option
|
||||||
|
* has been specified. The `execute' methods performs an action if the option
|
||||||
|
* is either present, or present with a value.
|
||||||
|
*
|
||||||
|
* By default they will throw an exception and can be relied upon to provide
|
||||||
|
* some form of abort action.
|
||||||
|
*/
|
||||||
|
class option {
|
||||||
|
protected:
|
||||||
|
char m_shortopt;
|
||||||
|
const std::string m_longopt;
|
||||||
|
const std::string m_description;
|
||||||
|
bool m_required;
|
||||||
|
bool m_found;
|
||||||
|
|
||||||
|
public:
|
||||||
|
option (char _letter,
|
||||||
|
const char *_name,
|
||||||
|
const char *_desc,
|
||||||
|
bool _required);
|
||||||
|
|
||||||
|
virtual ~option() { ; }
|
||||||
|
|
||||||
|
virtual void execute (void) = 0;
|
||||||
|
virtual void execute (const std::string& _data) = 0;
|
||||||
|
virtual void finish (void);
|
||||||
|
|
||||||
|
virtual bool is_required (void) const
|
||||||
|
{ return m_required; }
|
||||||
|
|
||||||
|
virtual void reset(void)
|
||||||
|
{ m_found = false; }
|
||||||
|
|
||||||
|
virtual char shortopt(void) const
|
||||||
|
{ return m_shortopt; }
|
||||||
|
virtual const std::string& longopt(void) const
|
||||||
|
{ return m_longopt; }
|
||||||
|
virtual const std::string& description(void) const
|
||||||
|
{ return m_description; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies a debugging or deprecated option.
|
||||||
|
*
|
||||||
|
* The option will be recognised but no action will be performed when
|
||||||
|
* encountered. A description will still be visible when usage information is
|
||||||
|
* printed.
|
||||||
|
*/
|
||||||
|
class nulloption : public option {
|
||||||
|
public:
|
||||||
|
nulloption(char _letter,
|
||||||
|
const char *_name,
|
||||||
|
const char *_desc,
|
||||||
|
bool _required = false);
|
||||||
|
|
||||||
|
virtual void execute(void)
|
||||||
|
{ m_found = true; }
|
||||||
|
virtual void execute(const std::string&)
|
||||||
|
{ m_found = true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a boolean value to reflect if the option has been specified.
|
||||||
|
*
|
||||||
|
* A provided boolean pointer will be set to false if the option isn't
|
||||||
|
* present, true if present.
|
||||||
|
* Assumes that the user will not modify the value for the duration of option
|
||||||
|
* processing.
|
||||||
|
* Throws an exception if a value is specified for the option.
|
||||||
|
*/
|
||||||
|
class presentoption : public option {
|
||||||
|
protected:
|
||||||
|
bool *m_data;
|
||||||
|
|
||||||
|
public:
|
||||||
|
presentoption(char _letter,
|
||||||
|
const char *_name,
|
||||||
|
const char *_desc,
|
||||||
|
bool *_data,
|
||||||
|
bool _required = false);
|
||||||
|
|
||||||
|
virtual void execute(void);
|
||||||
|
virtual void execute(const std::string& data)
|
||||||
|
{ option::execute(data); }
|
||||||
|
|
||||||
|
virtual void reset(void)
|
||||||
|
{ *m_data = false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a value given on the command line into a specified type.
|
||||||
|
*
|
||||||
|
* Given a value, return it to the user via the specified pointer. Uses the
|
||||||
|
* istream operators to read the value, so it should be relatively generic.
|
||||||
|
* If the value is malformed then an exception will be thrown.
|
||||||
|
* If there is no value, then perform the default action.
|
||||||
|
*
|
||||||
|
* It is safe to assume that when no value has been extracted the data pointer
|
||||||
|
* provided will not be overwritten. Useful for pre-loading with default
|
||||||
|
* values.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
class valueoption : public option {
|
||||||
|
protected:
|
||||||
|
T *m_data;
|
||||||
|
|
||||||
|
public:
|
||||||
|
valueoption(char _letter,
|
||||||
|
const char *_name,
|
||||||
|
const char *_desc,
|
||||||
|
T *_data,
|
||||||
|
bool _required = false):
|
||||||
|
option (_letter, _name, _desc, _required),
|
||||||
|
m_data (_data)
|
||||||
|
{ ; }
|
||||||
|
|
||||||
|
virtual void execute(void)
|
||||||
|
{ option::execute(); }
|
||||||
|
virtual void execute(const std::string& data) {
|
||||||
|
get_arg(data, m_data);
|
||||||
|
m_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Retrieve string to value conversion
|
||||||
|
T& get_arg(const std::string &arg,
|
||||||
|
T *val) {
|
||||||
|
std::istringstream stream (arg, std::istringstream::in);
|
||||||
|
stream.exceptions (
|
||||||
|
std::istringstream::failbit
|
||||||
|
| std::istringstream::badbit
|
||||||
|
);
|
||||||
|
stream >> *val;
|
||||||
|
|
||||||
|
return *val;
|
||||||
|
}
|
||||||
|
|
||||||
|
T& get_arg(const std::string &_arg,
|
||||||
|
T *val,
|
||||||
|
const T &defaultval) {
|
||||||
|
try {
|
||||||
|
return get_arg(_arg, val);
|
||||||
|
} catch(...) {
|
||||||
|
return *val = defaultval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// God-damn Solaris strikes again with its antiquated everything. So you
|
||||||
|
// don't get these goodies on that system, with its ancient compiler.
|
||||||
|
#if __GNUC__ >= 4
|
||||||
|
template<>
|
||||||
|
bool& valueoption<bool>::get_arg(const std::string& arg, bool * val) {
|
||||||
|
if (arg == "true" || arg == "yes" || arg == "1")
|
||||||
|
*val = true;
|
||||||
|
else if (arg == "false" || arg == "no" || arg == "0")
|
||||||
|
*val = false;
|
||||||
|
else
|
||||||
|
throw std::domain_error("Invalid form for boolean argument");
|
||||||
|
|
||||||
|
return *val;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interpret a (possibly) numeric value as a data size.
|
||||||
|
*
|
||||||
|
* A developer specifies the default scale of the value and what base it is
|
||||||
|
* to be interpreted in - when not otherwise explicitly stated in the
|
||||||
|
* arguments.
|
||||||
|
*
|
||||||
|
* Recognises various postfixes and modifiers to a numeric value such as MiB.
|
||||||
|
*/
|
||||||
|
class bytesoption : public valueoption<size_t> {
|
||||||
|
public:
|
||||||
|
/* Description of types available for parsing
|
||||||
|
*/
|
||||||
|
enum bytestype {
|
||||||
|
BYTES_SINGLE,
|
||||||
|
BYTES_KILO,
|
||||||
|
BYTES_MEGA,
|
||||||
|
BYTES_GIGA,
|
||||||
|
BYTES_TERA,
|
||||||
|
BYTES_PETA,
|
||||||
|
BYTES_EXA,
|
||||||
|
|
||||||
|
// Currently does not support yota or zeta as there can be
|
||||||
|
// trouble converting them without loss into 64bit quantities.
|
||||||
|
// That and they're fricking huge...
|
||||||
|
//
|
||||||
|
// BYTES_ZETA,
|
||||||
|
// BYTES_YOTA,
|
||||||
|
|
||||||
|
NUM_BYTESTYPE
|
||||||
|
};
|
||||||
|
|
||||||
|
enum bytesmodifier {
|
||||||
|
BYTES_BASE2,
|
||||||
|
BYTES_BASE10,
|
||||||
|
|
||||||
|
NUM_BYTESMODIFIER
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bytestype m_type;
|
||||||
|
bytesmodifier m_modifier;
|
||||||
|
|
||||||
|
static bytestype type_from_character(char c);
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
/* Constructors and methods
|
||||||
|
*/
|
||||||
|
bytesoption(char _letter,
|
||||||
|
const char *_name,
|
||||||
|
const char *_desc,
|
||||||
|
size_t *_data,
|
||||||
|
bytestype _type = BYTES_SINGLE,
|
||||||
|
bytesmodifier _modifier = BYTES_BASE2,
|
||||||
|
bool _required = false);
|
||||||
|
|
||||||
|
virtual void execute(const std::string &);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* High level handler for option registration, dispatch, and interpretation.
|
||||||
|
*
|
||||||
|
* The processor is the central point of operation on a program's arguments.
|
||||||
|
* It contains a list of all options which will interpret and act upon any
|
||||||
|
* arguments. These must be registered by the user prior to argument parsing,
|
||||||
|
* each of which must be primed with data to act upon separately.
|
||||||
|
*
|
||||||
|
* parse_args will perform minimal interpretation of the arguments to allow
|
||||||
|
* dispatch to registered options. No direct actions upon any input is
|
||||||
|
* performed within this class, merely dispatch and tokenisation.
|
||||||
|
*/
|
||||||
|
class processor {
|
||||||
|
protected:
|
||||||
|
std::map<char, option *> m_shortopt;
|
||||||
|
std::map<std::string, option *> m_longopt;
|
||||||
|
|
||||||
|
std::list<option *> m_options;
|
||||||
|
|
||||||
|
// The command to execute the application
|
||||||
|
std::string m_command;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
unsigned int parse_short(int pos, int argc, const char ** argv);
|
||||||
|
unsigned int parse_long(int pos, int argc, const char ** argv);
|
||||||
|
|
||||||
|
public:
|
||||||
|
processor();
|
||||||
|
~processor();
|
||||||
|
|
||||||
|
void print_usage(void);
|
||||||
|
|
||||||
|
// TODO: Use function overloading here...
|
||||||
|
void parse_args(int argc, const char ** argv);
|
||||||
|
void parse_args(int argc, char ** argv)
|
||||||
|
{ parse_args(argc, const_cast<const char**>(argv)); }
|
||||||
|
void parse_stream(std::istream & is);
|
||||||
|
|
||||||
|
|
||||||
|
void add_option(option * opt);
|
||||||
|
|
||||||
|
option* remove_option(char letter);
|
||||||
|
option* remove_option(const char * name);
|
||||||
|
option* remove_option(const std::string& name)
|
||||||
|
{ return remove_option(name.c_str()); }
|
||||||
|
option* remove_option(const option * opt)
|
||||||
|
{ return remove_option(opt->longopt ()); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
3
test/.gitignore
vendored
3
test/.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
|
/*.log
|
||||||
|
/*.trs
|
||||||
/adler*
|
/adler*
|
||||||
/backtrace*
|
/backtrace*
|
||||||
/checksum*
|
/checksum*
|
||||||
@ -7,6 +9,7 @@
|
|||||||
/json-check*
|
/json-check*
|
||||||
/maths*
|
/maths*
|
||||||
/matrix*
|
/matrix*
|
||||||
|
/option
|
||||||
/pool*
|
/pool*
|
||||||
/range*
|
/range*
|
||||||
/signal*
|
/signal*
|
||||||
|
@ -16,6 +16,7 @@ TEST_BIN = \
|
|||||||
json \
|
json \
|
||||||
maths \
|
maths \
|
||||||
matrix \
|
matrix \
|
||||||
|
option \
|
||||||
pool \
|
pool \
|
||||||
range \
|
range \
|
||||||
region \
|
region \
|
||||||
@ -51,6 +52,9 @@ maths_SOURCES = maths.cpp
|
|||||||
matrix_LDADD = $(builddir)/../libutil.la
|
matrix_LDADD = $(builddir)/../libutil.la
|
||||||
matrix_SOURCES = matrix.cpp
|
matrix_SOURCES = matrix.cpp
|
||||||
|
|
||||||
|
option_LDADD = $(builddir)/../libutil.la
|
||||||
|
option_SOURCES = options/success.cpp
|
||||||
|
|
||||||
pool_LDADD = $(builddir)/../libutil.la
|
pool_LDADD = $(builddir)/../libutil.la
|
||||||
pool_SOURCES = pool.cpp
|
pool_SOURCES = pool.cpp
|
||||||
|
|
||||||
|
253
test/options/success.cpp
Normal file
253
test/options/success.cpp
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
#include "options.hpp"
|
||||||
|
|
||||||
|
#include "debug.hpp"
|
||||||
|
#include "types.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <limits>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace util;
|
||||||
|
|
||||||
|
|
||||||
|
// Check that null options don't throw anything
|
||||||
|
void test_null_opt(void) {
|
||||||
|
unique_ptr<processor> p(new processor());
|
||||||
|
|
||||||
|
p->add_option(new nulloption('n', "null", "testing null option"));
|
||||||
|
|
||||||
|
const char * argv1[] = { "./foo", "-n", "foo" };
|
||||||
|
p->parse_args(elems(argv1), argv1);
|
||||||
|
|
||||||
|
const char * argv2[] = { "./foo", "--null", "foo" };
|
||||||
|
p->parse_args(elems(argv2), argv2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Check if presence options can be used successfully
|
||||||
|
void test_present_opt(void) {
|
||||||
|
unique_ptr<processor> p(new processor());
|
||||||
|
bool is_present;
|
||||||
|
p->add_option(new presentoption('p', "present", "option is present", &is_present));
|
||||||
|
|
||||||
|
// Short option form
|
||||||
|
const char * argv1[] = { "./foo", "-p" };
|
||||||
|
is_present = false;
|
||||||
|
p->parse_args(elems(argv1), argv1);
|
||||||
|
CHECK (is_present);
|
||||||
|
|
||||||
|
// Long option form
|
||||||
|
const char * argv2[] = { "./foo", "--present" };
|
||||||
|
is_present = false;
|
||||||
|
p->parse_args(elems(argv2), argv2);
|
||||||
|
CHECK (is_present);
|
||||||
|
|
||||||
|
// Check that value is reset from true if not present
|
||||||
|
const char * argv3[] = { "./foo" };
|
||||||
|
is_present = true;
|
||||||
|
p->parse_args(elems(argv3), argv3);
|
||||||
|
CHECK (!is_present);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check all forms of boolean inputs
|
||||||
|
void test_bool_opt(void) {
|
||||||
|
unique_ptr<processor> p(new processor());
|
||||||
|
bool value = false;
|
||||||
|
|
||||||
|
p->add_option(new valueoption<bool>('b', "bool",
|
||||||
|
"testing boolean actions", &value));
|
||||||
|
|
||||||
|
// List all legal forms of positive or negative boolean values
|
||||||
|
const char * argv[] = { "./foo", "-b", NULL };
|
||||||
|
const char * positive[] = { "1", "true", "yes" };
|
||||||
|
const char * negative[] = { "0", "false", "no" };
|
||||||
|
|
||||||
|
// For each boolean value, ensure that it returns as expected
|
||||||
|
for(off_t i = 0; i < elems(positive); ++i) {
|
||||||
|
argv[2] = positive[i];
|
||||||
|
p->parse_args(elems(argv), argv);
|
||||||
|
CHECK (value == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(off_t i = 0; i < elems(negative); ++i) {
|
||||||
|
argv[2] = negative[i];
|
||||||
|
p->parse_args(elems(argv), argv);
|
||||||
|
CHECK (value == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that invalid forms of boolean all throw exceptions
|
||||||
|
const char * invalid[] = { "foo", "y", "null" };
|
||||||
|
|
||||||
|
for(off_t i = 0; i < elems(invalid); ++i) {
|
||||||
|
argv[2] = invalid[i];
|
||||||
|
CHECK_THROWS (
|
||||||
|
std::domain_error,
|
||||||
|
p->parse_args (elems(argv), argv)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void test_numeric_opt(void) {
|
||||||
|
unique_ptr<processor> p(new processor());
|
||||||
|
T value;
|
||||||
|
p->add_option(new valueoption<T>('t', "type",
|
||||||
|
"testing type option", &value));
|
||||||
|
|
||||||
|
T values[] = {
|
||||||
|
// TODO: Enable minimum value testing. Currently disabled as
|
||||||
|
// a negative numerical value looks just like a proceeding
|
||||||
|
// option.
|
||||||
|
//numeric_limits<T>::min(),
|
||||||
|
|
||||||
|
numeric_limits<T>::max(),
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
const char * argv_short[] = { "./foo", "-t", NULL };
|
||||||
|
const char * argv_long[] = { "./foo", NULL };
|
||||||
|
|
||||||
|
for(off_t i = 0; i < elems(values); ++i) {
|
||||||
|
ostringstream out_short, out_long;
|
||||||
|
string str_short, str_long;
|
||||||
|
|
||||||
|
out_short << values[i];
|
||||||
|
str_short = out_short.str();
|
||||||
|
argv_short[2] = str_short.c_str();
|
||||||
|
|
||||||
|
value = 2;
|
||||||
|
p->parse_args(elems(argv_short), argv_short);
|
||||||
|
CHECK (value == values[i]);
|
||||||
|
|
||||||
|
out_long << "--type=" << values[i];
|
||||||
|
str_long = out_long.str();
|
||||||
|
argv_long[1] = str_long.c_str();
|
||||||
|
|
||||||
|
value = 2;
|
||||||
|
p->parse_args(elems(argv_long), argv_long);
|
||||||
|
CHECK (value == values[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void test_bytes_opt(void) {
|
||||||
|
unique_ptr<processor> p(new processor());
|
||||||
|
|
||||||
|
struct {
|
||||||
|
const char * param;
|
||||||
|
bytesoption::bytestype type;
|
||||||
|
bytesoption::bytesmodifier mod;
|
||||||
|
size_t size;
|
||||||
|
} commands[] = {
|
||||||
|
{ "1", bytesoption::BYTES_MEGA,
|
||||||
|
bytesoption::BYTES_BASE2,
|
||||||
|
1UL * 1024 * 1024 },
|
||||||
|
|
||||||
|
{ "1k", bytesoption::BYTES_KILO,
|
||||||
|
bytesoption::BYTES_BASE2,
|
||||||
|
1UL * 1024 },
|
||||||
|
|
||||||
|
{ "1M", bytesoption::BYTES_SINGLE,
|
||||||
|
bytesoption::BYTES_BASE2,
|
||||||
|
1UL * 1024 * 1024 },
|
||||||
|
|
||||||
|
{ "1G", bytesoption::BYTES_MEGA,
|
||||||
|
bytesoption::BYTES_BASE2,
|
||||||
|
1UL * 1024 * 1024 * 1024 },
|
||||||
|
|
||||||
|
{ "1M", bytesoption::BYTES_SINGLE,
|
||||||
|
bytesoption::BYTES_BASE10,
|
||||||
|
1UL * 1000 * 1000 },
|
||||||
|
|
||||||
|
{ "1MB", bytesoption::BYTES_SINGLE,
|
||||||
|
bytesoption::BYTES_BASE10,
|
||||||
|
1UL * 1000 * 1000 },
|
||||||
|
|
||||||
|
{ "1MiB", bytesoption::BYTES_SINGLE,
|
||||||
|
bytesoption::BYTES_BASE10,
|
||||||
|
1UL * 1024 * 1024 },
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const char * argv[] = {
|
||||||
|
"./foo",
|
||||||
|
"-b",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
for(unsigned int i = 0; i < elems(commands); ++i) {
|
||||||
|
size_t size = 0;
|
||||||
|
option * opt = new bytesoption('b', "bytes",
|
||||||
|
"testing sizeof bytes", &size, commands[i].type,
|
||||||
|
commands[i].mod);
|
||||||
|
p->add_option(opt);
|
||||||
|
|
||||||
|
argv[elems(argv) - 1] = commands[i].param;
|
||||||
|
p->parse_args(elems(argv), argv);
|
||||||
|
|
||||||
|
CHECK_EQ (commands[i].size, size);
|
||||||
|
delete p->remove_option(opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void test_insert_remove_opt(void) {
|
||||||
|
unique_ptr<processor> p(new processor());
|
||||||
|
nulloption opt('n', "null-option", "null testing action");
|
||||||
|
|
||||||
|
p->add_option(&opt);
|
||||||
|
CHECK_EQ (p->remove_option('n'), (option*)&opt);
|
||||||
|
|
||||||
|
p->add_option(&opt);
|
||||||
|
CHECK_EQ (p->remove_option("null-option"), (option*)&opt);
|
||||||
|
|
||||||
|
p->add_option(&opt);
|
||||||
|
CHECK_THROWS (std::logic_error, p->add_option(&opt));
|
||||||
|
|
||||||
|
p->remove_option(&opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void test_required (void) {
|
||||||
|
unique_ptr<processor> p(new processor());
|
||||||
|
p->add_option (new nulloption ('n',
|
||||||
|
"null",
|
||||||
|
"null testing",
|
||||||
|
true));
|
||||||
|
static const char *argv[] = {
|
||||||
|
"./cpptest",
|
||||||
|
"-n",
|
||||||
|
"value"
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK_NOTHROW (p->parse_args (elems(argv), argv));
|
||||||
|
CHECK_THROWS (std::runtime_error, p->parse_args (1, argv));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char **argv) {
|
||||||
|
test_null_opt ();
|
||||||
|
test_present_opt ();
|
||||||
|
test_bool_opt ();
|
||||||
|
test_numeric_opt< int16_t> ();
|
||||||
|
test_numeric_opt< int32_t> ();
|
||||||
|
test_numeric_opt< int64_t> ();
|
||||||
|
test_numeric_opt<uint16_t> ();
|
||||||
|
test_numeric_opt<uint32_t> ();
|
||||||
|
test_numeric_opt<uint64_t> ();
|
||||||
|
test_bytes_opt ();
|
||||||
|
test_insert_remove_opt ();
|
||||||
|
test_required ();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user