libcruft-util/options.cpp

559 lines
15 KiB
C++
Raw Normal View History

2013-02-26 18:31:14 +11:00
/*
* 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 "debug.hpp"
2013-08-05 16:39:31 +10:00
#include "string.hpp"
#include "types.hpp"
#include "types/casts.hpp"
#include <algorithm>
2013-02-26 18:31:14 +11:00
#include <cassert>
2013-02-26 18:56:25 +11:00
#include <cmath>
#include <cstdint>
2013-02-26 18:31:14 +11:00
#include <cstdlib>
#include <cstring>
2013-02-26 18:56:25 +11:00
#include <cstring>
#include <iostream>
#include <stdexcept>
2013-02-26 18:31:14 +11:00
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(); }
2013-02-26 18:56:25 +11:00
void
option::execute (void) {
2013-02-26 18:31:14 +11:00
throw runtime_error(
"Cannot provide no value for the option '" + m_longopt + "'"
);
}
2013-02-26 18:56:25 +11:00
void
option::execute (const string& data) {
2013-02-26 18:31:14 +11:00
assert(data.size() > 0);
throw runtime_error(
"Cannot provide a value for the option '" + m_longopt + "'"
);
}
2013-02-26 18:56:25 +11:00
ostream&
operator<< (ostream & os, const option& opt) {
2013-02-26 18:31:14 +11:00
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):
2013-02-27 15:11:05 +11:00
valueoption<size_t> (_letter, _name, _desc, _data, _required),
2013-02-26 18:31:14 +11:00
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;
2013-02-27 15:11:05 +11:00
default:
throw domain_error("Invalid magnitude specifier");
}
2013-02-26 18:31:14 +11:00
}
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;
2013-02-27 15:11:05 +11:00
size_t cursor = data.size () - 1;
2013-02-26 18:31:14 +11:00
// Consume an optional trailing `byte' type
if (data[cursor] == 'B' || data[cursor] == 'b') {
2013-02-27 15:11:05 +11:00
if (cursor == 0)
2013-02-26 18:31:14 +11:00
throw invalid_argument ("Size is too short");
2013-02-27 15:11:05 +11:00
--cursor;
2013-02-26 18:31:14 +11:00
}
// Check if we explicitly request base2
if (data[cursor] == 'i') {
modifier = BYTES_BASE2;
2013-02-27 15:11:05 +11:00
if (cursor == 0)
2013-02-26 18:31:14 +11:00
throw invalid_argument("Size is too short");
2013-02-27 15:11:05 +11:00
--cursor;
2013-02-26 18:31:14 +11:00
}
// 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:
2013-02-27 15:11:05 +11:00
unreachable ();
2013-02-26 18:31:14 +11:00
}
// 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;
multiplier = static_cast<uintmax_t> (std::pow (modifier_factor, (int)specified));
2013-02-26 18:31:14 +11:00
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;
}
2013-02-26 18:56:25 +11:00
2013-02-26 18:31:14 +11:00
/*
* 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)
2013-02-26 18:56:25 +11:00
{ ; }
2013-02-26 18:31:14 +11:00
virtual void execute (void);
virtual void execute (const std::string& data)
2013-02-26 18:56:25 +11:00
{ option::execute (data); }
2013-02-26 18:31:14 +11:00
};
const char helpoption::HELP_CHARACTER = 'h';
const char *helpoption::HELP_NAME = "help";
const char *helpoption::HELP_DESCRIPTION =
2013-02-26 18:56:25 +11:00
"display help and usage information";
2013-02-26 18:31:14 +11:00
2013-02-26 18:56:25 +11:00
void
helpoption::execute (void) {
2013-02-26 18:31:14 +11:00
m_processor->print_usage ();
exit (EXIT_SUCCESS);
}
/*
* Command line processing options
*/
processor::processor () {
add_option (make_unique<helpoption> (this));
}
2013-02-26 18:31:14 +11:00
processor::~processor ()
{ ; }
2013-02-26 18:31:14 +11:00
2013-02-26 18:56:25 +11:00
void
processor::print_usage (void) {
2013-02-26 18:31:14 +11:00
cout << "Usage: " << m_command << " [options]" << endl;
for (const auto &i: m_options)
2013-02-26 18:56:25 +11:00
cout << '\t' << *i << endl;
2013-02-26 18:31:14 +11:00
}
/**
* 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.
*/
2013-02-26 18:56:25 +11:00
unsigned int
processor::parse_short (int pos, int argc, const char **argv) {
2013-02-26 18:31:14 +11:00
assert (pos > 0);
assert (pos < argc);
assert (argv[pos] != NULL);
2013-02-26 18:56:25 +11:00
const char *arg = argv[pos];
2013-02-26 18:31:14 +11:00
// 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.
*/
2013-02-26 18:56:25 +11:00
unsigned int
processor::parse_long (int pos, int argc, const char ** argv) {
2013-02-26 18:31:14 +11:00
assert (pos > 0);
assert (pos < argc);
assert (argv[pos] != NULL);
2013-02-26 18:56:25 +11:00
const char *arg = argv[pos];
2013-02-26 18:31:14 +11:00
// We must have at least two hyphens, and two letters (else a short opt)
assert (arg[0] == '-');
assert (arg[1] == '-');
2013-02-26 18:56:25 +11:00
assert (strlen (arg) >= 4);
2013-02-26 18:31:14 +11:00
// Skip past the dashes to the argument name
arg += 2;
// If there's an equals it's has a value to extract
2013-02-27 15:11:05 +11:00
const char *data = strchr (arg, '=');
2013-02-26 18:56:25 +11:00
if (data) {
2013-02-27 15:11:05 +11:00
arg = strndup (arg, sign_cast<size_t> (data - arg));
//arg = strndup (arg, data - arg); // Copy just the arg name
++data; // Skip the '='
2013-02-26 18:31:14 +11:00
}
2013-02-26 18:56:25 +11:00
option *o = m_longopt[arg];
2013-02-26 18:31:14 +11:00
if (!o)
throw runtime_error ("Cannot match option");
2013-02-26 18:56:25 +11:00
if (data)
2013-02-26 18:31:14 +11:00
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)
2013-02-26 18:56:25 +11:00
i->reset ();
2013-02-26 18:31:14 +11:00
const int FIRST_ARGUMENT = 1;
2013-02-26 18:31:14 +11:00
try {
for (int i = FIRST_ARGUMENT; i < argc; ++i) {
2013-02-26 18:31:14 +11:00
// 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);
2013-02-26 18:31:14 +11:00
assert (consumed >= 1);
i += sign_cast<int> (consumed - 1);
2013-02-26 18:31:14 +11:00
}
} catch (runtime_error &x) {
print_usage ();
exit (EXIT_FAILURE);
}
for (auto &i: m_options)
2013-02-26 18:56:25 +11:00
i->finish ();
2013-02-26 18:31:14 +11:00
}
2013-02-26 18:56:25 +11:00
void
processor::add_option (std::unique_ptr<option> opt) {
2013-02-26 18:31:14 +11:00
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.get ();
m_longopt [opt->longopt ()] = opt.get ();
2013-02-26 18:31:14 +11:00
m_options.push_back (move (opt));
2013-02-26 18:31:14 +11:00
}
std::unique_ptr<option>
2013-02-26 18:56:25 +11:00
processor::remove_option (char letter) {
2013-02-26 18:31:14 +11:00
// Locate the option by short name
2013-02-26 18:56:25 +11:00
const auto s_candidate = m_shortopt.find (letter);
2013-02-26 18:31:14 +11:00
if (s_candidate == m_shortopt.end ())
throw logic_error ("Cannot remove an option which is not present");
option *target = (*s_candidate).second;
2013-02-26 18:31:14 +11:00
// Locate the long option entry
const auto l_candidate = m_longopt.find (target->longopt ());
2013-02-26 18:31:14 +11:00
assert (l_candidate != m_longopt.end ());
// Remove all references and return
auto prime = std::find_if (
m_options.begin (),
m_options.end (),
[&target] (std::unique_ptr<option> &rhs) { return rhs.get () == target; }
);
std::unique_ptr<option> opt = move (*prime);
2013-02-26 18:31:14 +11:00
m_shortopt.erase (s_candidate);
m_longopt.erase (l_candidate);
m_options.erase (prime);
2013-02-26 18:31:14 +11:00
return opt;
}
std::unique_ptr<option>
2013-02-26 18:56:25 +11:00
processor::remove_option (const char *name) {
2013-02-26 18:31:14 +11:00
// Locate the option by long name
2013-02-26 18:56:25 +11:00
const auto l_candidate = m_longopt.find (name);
2013-02-26 18:31:14 +11:00
if (l_candidate == m_longopt.end ())
throw logic_error ("Cannot remove an option which is not present");
option *target = (*l_candidate).second;
2013-02-26 18:31:14 +11:00
// Locate the short option entry
const auto s_candidate = m_shortopt.find (target->shortopt ());
2013-02-26 18:31:14 +11:00
assert (s_candidate != m_shortopt.end ());
// Remove all references and return
auto prime = std::find_if (
m_options.begin (),
m_options.end (),
[&target] (std::unique_ptr<option> &rhs) { return rhs.get () == target; }
);
std::unique_ptr<option> opt = move (*prime);
2013-02-26 18:31:14 +11:00
m_shortopt.erase (s_candidate);
m_longopt.erase (l_candidate);
m_options.erase (prime);
2013-02-26 18:31:14 +11:00
return opt;
}
/* Parse args from a stream, one arg per line
*/
2013-02-27 15:11:05 +11:00
//void parse_stream (std::istream & is) {
// unique_ptr<char[]> buffer (new char [MAX_CHUNK_LENGTH + 1]);
//}
2013-02-26 18:31:14 +11:00