Loading...
Searching...
No Matches
argagg.hpp
Go to the documentation of this file.
/*
* @file
* @brief
* Defines a very simple command line argument parser.
*
* @copyright
* Copyright (c) 2018 Viet The Nguyen
*
* @copyright
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* @copyright
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* @copyright
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#pragma once
#ifndef ARGAGG_ARGAGG_ARGAGG_HPP
#define ARGAGG_ARGAGG_ARGAGG_HPP
#include <algorithm>
#include <array>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <ostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace argagg {
struct unexpected_argument_error
: public std::runtime_error {
using std::runtime_error::runtime_error;
};
struct unexpected_option_error
: public std::runtime_error {
using std::runtime_error::runtime_error;
};
struct option_lacks_argument_error
: public std::runtime_error {
using std::runtime_error::runtime_error;
};
struct invalid_flag
: public std::runtime_error {
using std::runtime_error::runtime_error;
};
struct unknown_option
: public std::runtime_error {
using std::runtime_error::runtime_error;
};
namespace convert {
template <typename T>
T arg(const char* arg);
template <typename T>
struct converter {
static T convert(const char* arg);
};
template <typename T>
bool parse_next_component(
const char*& s,
T& out_arg,
const char delim = ',');
}
struct option_result {
const char* arg;
template <typename T>
T as() const;
template <typename T>
T as(const T& t) const;
template <typename T>
operator T () const;
bool operator ! () const;
};
struct option_results {
std::vector<option_result> all;
std::size_t count() const;
option_result& operator [] (std::size_t index);
const option_result& operator [] (std::size_t index) const;
template <typename T>
T as() const;
template <typename T>
T as(const T& t) const;
template <typename T>
operator T () const;
bool operator ! () const;
};
struct parser_results {
const char* program;
std::unordered_map<std::string, option_results> options;
std::vector<const char*> pos;
bool has_option(const std::string& name) const;
option_results& operator [] (const std::string& name);
const option_results& operator [] (const std::string& name) const;
std::size_t count() const;
const char* operator [] (std::size_t index) const;
template <typename T>
T as(std::size_t i = 0) const;
template <typename T>
std::vector<T> all_as() const;
};
struct definition {
const std::string name;
std::vector<std::string> flags;
std::string help;
unsigned int num_args;
bool wants_no_arguments() const;
bool requires_arguments() const;
};
bool cmd_line_arg_is_option_flag(
const char* s);
bool is_valid_flag_definition(
const char* s);
bool flag_is_short(
const char* s);
struct parser_map {
std::array<const definition*, 256> short_map;
std::unordered_map<std::string, const definition*> long_map;
bool known_short_flag(
const char flag) const;
const definition* get_definition_for_short_flag(
const char flag) const;
bool known_long_flag(
const std::string& flag) const;
const definition* get_definition_for_long_flag(
const std::string& flag) const;
};
parser_map validate_definitions(
const std::vector<definition>& definitions);
struct parser {
std::vector<definition> definitions;
parser_results parse(int argc, const char** argv) const;
parser_results parse(int argc, char** argv) const;
};
struct fmt_ostream : public std::ostringstream {
std::ostream& output;
fmt_ostream(std::ostream& output);
~fmt_ostream();
};
std::string fmt_string(const std::string& s);
} // namespace argagg
std::ostream& operator << (std::ostream& os, const argagg::parser& x);
// ---- end of declarations, header-only implementations follow ----
namespace argagg {
template <typename T>
T option_result::as() const
{
if (this->arg) {
return convert::arg<T>(this->arg);
} else {
throw option_lacks_argument_error("option has no argument");
}
}
template <typename T>
T option_result::as(const T& t) const
{
if (this->arg) {
try {
return convert::arg<T>(this->arg);
} catch (...) {
return t;
}
} else {
// I actually think this will never happen. To call this method you have
// to access a specific option_result for an option. If there's a
// specific option_result then the option was found. If the option
// requires an argument then it will definitely have an argument
// otherwise the parser would have complained.
return t;
}
}
template <typename T>
option_result::operator T () const
{
return this->as<T>();
}
template <> inline
option_result::operator bool () const
{
return this->arg != nullptr;
}
inline
bool option_result::operator ! () const
{
return !static_cast<bool>(*this);
}
inline
std::size_t option_results::count() const
{
return this->all.size();
}
inline
option_result& option_results::operator [] (std::size_t index)
{
return this->all[index];
}
inline
const option_result& option_results::operator [] (std::size_t index) const
{
return this->all[index];
}
template <typename T>
T option_results::as() const
{
if (this->all.size() == 0) {
throw std::out_of_range("no option arguments to convert");
}
return this->all.back().as<T>();
}
template <typename T>
T option_results::as(const T& t) const
{
if (this->all.size() == 0) {
return t;
}
return this->all.back().as<T>(t);
}
template <typename T>
option_results::operator T () const
{
return this->as<T>();
}
template <> inline
option_results::operator bool () const
{
return this->all.size() > 0;
}
inline
bool option_results::operator ! () const
{
return !static_cast<bool>(*this);
}
inline
bool parser_results::has_option(const std::string& name) const
{
const auto it = this->options.find(name);
return ( it != this->options.end()) && it->second.all.size() > 0;
}
inline
option_results& parser_results::operator [] (const std::string& name)
try {
return this->options.at(name);
} catch (const std::out_of_range& e) {
std::ostringstream msg;
msg << "no option named \"" << name << "\" in parser_results";
throw unknown_option(msg.str());
}
inline
const option_results&
parser_results::operator [] (const std::string& name) const
try {
return this->options.at(name);
} catch (const std::out_of_range& e) {
std::ostringstream msg;
msg << "no option named \"" << name << "\" in parser_results";
throw unknown_option(msg.str());
}
inline
std::size_t parser_results::count() const
{
return this->pos.size();
}
inline
const char* parser_results::operator [] (std::size_t index) const
{
return this->pos[index];
}
template <typename T>
T parser_results::as(std::size_t i) const
{
return convert::arg<T>(this->pos[i]);
}
template <typename T>
std::vector<T> parser_results::all_as() const
{
std::vector<T> v(this->pos.size());
std::transform(
this->pos.begin(), this->pos.end(), v.begin(),
[](const char* arg) {
return convert::arg<T>(arg);
});
return v;
}
inline
bool definition::wants_no_arguments() const
{
return this->num_args == 0;
}
inline
bool definition::requires_arguments() const
{
return this->num_args > 0;
}
inline
bool cmd_line_arg_is_option_flag(
const char* s)
{
auto len = std::strlen(s);
// The shortest possible flag has two characters: a hyphen and an
// alpha-numeric character.
if (len < 2) {
return false;
}
// All flags must start with a hyphen.
if (s[0] != '-') {
return false;
}
// Shift the name forward by a character to account for the initial hyphen.
// This means if s was originally "-v" then name will be "v".
const char* name = s + 1;
// Check if we're dealing with a long flag.
bool is_long = false;
if (s[1] == '-') {
is_long = true;
// Just -- is not a valid flag.
if (len == 2) {
return false;
}
// Shift the name forward to account for the extra hyphen. This means if s
// was originally "--output" then name will be "output".
name = s + 2;
}
// The first character of the flag name must be alpha-numeric. This is to
// prevent things like "---a" from being valid flags.
len = std::strlen(name);
if (!std::isalnum(name[0])) {
return false;
}
// At this point in is_valid_flag_definition() we would check if the short
// flag has only one character. At command line specification you can group
// short flags together or even add an argument to a short flag without a
// space delimiter. Thus we don't check if this has only one character
// because it might not.
// If this is a long flag then we expect all characters *up to* an equal sign
// to be alpha-numeric or a hyphen. After the equal sign you are specify the
// argument to a long flag which can be basically anything.
if (is_long) {
bool encountered_equal = false;
return std::all_of(name, name + len, [&](const char& c) {
if (encountered_equal) {
return true;
} else {
if (c == '=') {
encountered_equal = true;
return true;
}
return std::isalnum(c) || c == '-';
}
});
}
// At this point we are not dealing with a long flag. We already checked that
// the first character is alpha-numeric so we've got the case of a single
// short flag covered. This might be a short flag group though and we might
// be tempted to check that each character of the short flag group is
// alpha-numeric. However, you can specify the argument for a short flag
// without a space delimiter (e.g. "-I/usr/local/include") so you can't tell
// if the rest of a short flag group is part of the argument or not unless
// you know what is a defined flag or not. We leave that kind of processing
// to the parser.
return true;
}
inline
bool is_valid_flag_definition(
const char* s)
{
auto len = std::strlen(s);
// The shortest possible flag has two characters: a hyphen and an
// alpha-numeric character.
if (len < 2) {
return false;
}
// All flags must start with a hyphen.
if (s[0] != '-') {
return false;
}
// Shift the name forward by a character to account for the initial hyphen.
// This means if s was originally "-v" then name will be "v".
const char* name = s + 1;
// Check if we're dealing with a long flag.
bool is_long = false;
if (s[1] == '-') {
is_long = true;
// Just -- is not a valid flag.
if (len == 2) {
return false;
}
// Shift the name forward to account for the extra hyphen. This means if s
// was originally "--output" then name will be "output".
name = s + 2;
}
// The first character of the flag name must be alpha-numeric. This is to
// prevent things like "---a" from being valid flags.
len = std::strlen(name);
if (!std::isalnum(name[0])) {
return false;
}
// If this is a short flag then it must only have one character.
if (!is_long && len > 1) {
return false;
}
// The rest of the characters must be alpha-numeric, but long flags are
// allowed to have hyphens too.
return std::all_of(name + 1, name + len, [&](const char& c) {
return std::isalnum(c) || (c == '-' && is_long);
});
}
inline
bool flag_is_short(
const char* s)
{
return s[0] == '-' && std::isalnum(s[1]);
}
inline
bool parser_map::known_short_flag(
const char flag) const
{
return this->short_map[static_cast<std::size_t>(flag)] != nullptr;
}
inline
const definition* parser_map::get_definition_for_short_flag(
const char flag) const
{
return this->short_map[static_cast<std::size_t>(flag)];
}
inline
bool parser_map::known_long_flag(
const std::string& flag) const
{
const auto existing_long_flag = this->long_map.find(flag);
return existing_long_flag != long_map.end();
}
inline
const definition* parser_map::get_definition_for_long_flag(
const std::string& flag) const
{
const auto existing_long_flag = this->long_map.find(flag);
if (existing_long_flag == long_map.end()) {
return nullptr;
}
return existing_long_flag->second;
}
inline
parser_map validate_definitions(
const std::vector<definition>& definitions)
{
std::unordered_map<std::string, const definition*> long_map;
parser_map map {{{nullptr}}, std::move(long_map)};
for (auto& defn : definitions) {
if (defn.flags.size() == 0) {
std::ostringstream msg;
msg << "option \"" << defn.name << "\" has no flag definitions";
throw invalid_flag(msg.str());
}
for (auto& flag : defn.flags) {
if (!is_valid_flag_definition(flag.data())) {
std::ostringstream msg;
msg << "flag \"" << flag << "\" specified for option \"" << defn.name
<< "\" is invalid";
throw invalid_flag(msg.str());
}
if (flag_is_short(flag.data())) {
const std::size_t short_flag_letter = static_cast<std::size_t>(flag[1]);
const auto existing_short_flag =
map.short_map[short_flag_letter];
bool short_flag_already_exists = (existing_short_flag != nullptr);
if (short_flag_already_exists) {
std::ostringstream msg;
msg << "duplicate short flag \"" << flag
<< "\" found, specified by both option \"" << defn.name
<< "\" and option \"" << existing_short_flag->name;
throw invalid_flag(msg.str());
}
map.short_map[static_cast<std::size_t>(short_flag_letter)] = &defn;
continue;
}
// If we're here then this is a valid, long-style flag.
if (map.known_long_flag(flag)) {
const auto existing_long_flag = map.get_definition_for_long_flag(flag);
std::ostringstream msg;
msg << "duplicate long flag \"" << flag
<< "\" found, specified by both option \"" << defn.name
<< "\" and option \"" << existing_long_flag->name;
throw invalid_flag(msg.str());
}
map.long_map.insert(std::make_pair(flag, &defn));
}
}
return map;
}
inline
parser_results parser::parse(int argc, const char** argv) const
{
// Inspect each definition to see if its valid. You may wonder "why don't
// you do this validation on construction?" I had thought about it but
// realized that since I've made the parser an aggregate type (granted it
// just "aggregates" a single vector) I would need to track any changes to
// the definitions vector and re-run the validity check in order to
// maintain this expected "validity invariant" on the object. That would
// then require hiding the definitions vector as a private entry and then
// turning the parser into a thin interface (by re-exposing setters and
// getters) to the vector methods just so that I can catch when the
// definition has been modified. It seems much simpler to just enforce the
// validity when you actually want to parse because it's at the moment of
// parsing that you know the definitions are complete.
parser_map map = validate_definitions(this->definitions);
// Initialize the parser results that we'll be returning. Store the program
// name (assumed to be the first command line argument) and initialize
// everything else as empty.
std::unordered_map<std::string, option_results> options {};
std::vector<const char*> pos;
parser_results results {argv[0], std::move(options), std::move(pos)};
// Add an empty option result for each definition.
for (const auto& defn : this->definitions) {
option_results opt_results {{}};
results.options.insert(
std::make_pair(defn.name, opt_results));
}
// Don't start off ignoring flags. We only ignore flags after a -- shows up
// in the command line arguments.
bool ignore_flags = false;
// Keep track of any options that are expecting arguments.
const char* last_flag_expecting_args = nullptr;
option_result* last_option_expecting_args = nullptr;
unsigned int num_option_args_to_consume = 0;
// Get pointers to pointers so we can treat the raw pointer array as an
// iterator for standard library algorithms. This isn't used yet but can be
// used to template this function to work on iterators over strings or
// C-strings.
const char** arg_i = argv + 1;
const char** arg_end = argv + argc;
while (arg_i != arg_end) {
auto arg_i_cstr = *arg_i;
auto arg_i_len = std::strlen(arg_i_cstr);
// Some behavior to note: if the previous option is expecting an argument
// then the next entry will be treated as a positional argument even if
// it looks like a flag.
bool treat_as_positional_argument = (
ignore_flags
|| num_option_args_to_consume > 0
|| !cmd_line_arg_is_option_flag(arg_i_cstr)
);
if (treat_as_positional_argument) {
// If last option is expecting some specific positive number of
// arguments then give this argument to that option, *regardless of
// whether or not the argument looks like a flag or is the special "--"
// argument*.
if (num_option_args_to_consume > 0) {
last_option_expecting_args->arg = arg_i_cstr;
--num_option_args_to_consume;
++arg_i;
continue;
}
// Now we check if this is just "--" which is a special argument that
// causes all following arguments to be treated as non-options and is
// itselve discarded.
if (std::strncmp(arg_i_cstr, "--", 2) == 0 && arg_i_len == 2) {
ignore_flags = true;
++arg_i;
continue;
}
// If there are no expectations for option arguments then simply use
// this argument as a positional argument.
results.pos.push_back(arg_i_cstr);
++arg_i;
continue;
}
// Reset the "expecting argument" state.
last_flag_expecting_args = nullptr;
last_option_expecting_args = nullptr;
num_option_args_to_consume = 0;
// If we're at this point then we're definitely dealing with something
// that is flag-like and has hyphen as the first character and has a
// length of at least two characters. How we handle this potential flag
// depends on whether or not it is a long-option so we check that first.
bool is_long_flag = (arg_i_cstr[1] == '-');
if (is_long_flag) {
// Long flags have a complication: their arguments can be specified
// using an '=' character right inside the argument. That means an
// argument like "--output=foobar.txt" is actually an option with flag
// "--output" and argument "foobar.txt". So we look for the first
// instance of the '=' character and keep it in long_flag_arg. If
// long_flag_arg is nullptr then we didn't find '='. We need the
// flag_len to construct long_flag_str below.
auto long_flag_arg = std::strchr(arg_i_cstr, '=');
std::size_t flag_len = arg_i_len;
if (long_flag_arg != nullptr) {
flag_len = static_cast<std::size_t>(long_flag_arg - arg_i_cstr);
}
std::string long_flag_str(arg_i_cstr, flag_len);
if (!map.known_long_flag(long_flag_str)) {
std::ostringstream msg;
msg << "found unexpected flag: " << long_flag_str;
throw unexpected_option_error(msg.str());
}
const auto defn = map.get_definition_for_long_flag(long_flag_str);
if (long_flag_arg != nullptr && defn->num_args == 0) {
std::ostringstream msg;
msg << "found argument for option not expecting an argument: "
<< arg_i_cstr;
throw unexpected_argument_error(msg.str());
}
// We've got a legitimate, known long flag option so we add an option
// result. This option result initially has an arg of nullptr, but that
// might change in the following block.
auto& opt_results = results.options[defn->name];
option_result opt_result {nullptr};
opt_results.all.push_back(std::move(opt_result));
if (defn->requires_arguments()) {
bool there_is_an_equal_delimited_arg = (long_flag_arg != nullptr);
if (there_is_an_equal_delimited_arg) {
// long_flag_arg would be "=foo" in the "--output=foo" case so we
// increment by 1 to get rid of the equal sign.
opt_results.all.back().arg = long_flag_arg + 1;
} else {
last_flag_expecting_args = arg_i_cstr;
last_option_expecting_args = &(opt_results.all.back());
num_option_args_to_consume = defn->num_args;
}
}
++arg_i;
continue;
}
// If we've made it here then we're looking at either a short flag or a
// group of short flags. Short flags can be grouped together so long as
// they don't require any arguments unless the option that does is the
// last in the group ("-o x -v" is okay, "-vo x" is okay, "-ov x" is
// not). So starting after the dash we're going to process each character
// as if it were a separate flag. Note "sf_idx" stands for "short flag
// index".
for (std::size_t sf_idx = 1; sf_idx < arg_i_len; ++sf_idx) {
const auto short_flag = arg_i_cstr[sf_idx];
if (!std::isalnum(short_flag)) {
std::ostringstream msg;
msg << "found non-alphanumeric character '" << arg_i_cstr[sf_idx]
<< "' in flag group '" << arg_i_cstr << "'";
throw std::domain_error(msg.str());
}
if (!map.known_short_flag(short_flag)) {
std::ostringstream msg;
msg << "found unexpected flag '" << arg_i_cstr[sf_idx]
<< "' in flag group '" << arg_i_cstr << "'";
throw unexpected_option_error(msg.str());
}
auto defn = map.get_definition_for_short_flag(short_flag);
auto& opt_results = results.options[defn->name];
// Create an option result with an empty argument (for now) and add it
// to this option's results.
option_result opt_result {nullptr};
opt_results.all.push_back(std::move(opt_result));
if (defn->requires_arguments()) {
// If this short flag's option requires an argument and we're the
// last flag in the short flag group then just put the parser into
// "expecting argument for last option" state and move onto the next
// command line argument.
bool is_last_short_flag_in_group = (sf_idx == arg_i_len - 1);
if (is_last_short_flag_in_group) {
last_flag_expecting_args = arg_i_cstr;
last_option_expecting_args = &(opt_results.all.back());
num_option_args_to_consume = defn->num_args;
break;
}
// If this short flag's option requires an argument and we're NOT the
// last flag in the short flag group then we automatically consume
// the rest of the short flag group as the argument for this flag.
// This is how we get the POSIX behavior of being able to specify a
// flag's arguments without a white space delimiter (e.g.
// "-I/usr/local/include").
opt_results.all.back().arg = arg_i_cstr + sf_idx + 1;
break;
}
}
++arg_i;
continue;
}
// If we're done with all of the arguments but are still expecting
// arguments for a previous option then we haven't satisfied that option.
// This is an error.
if (num_option_args_to_consume > 0) {
std::ostringstream msg;
msg << "last option \"" << last_flag_expecting_args
<< "\" expects an argument but the parser ran out of command line "
<< "arguments to parse";
throw option_lacks_argument_error(msg.str());
}
return results;
}
inline
parser_results parser::parse(int argc, char** argv) const
{
return parse(argc, const_cast<const char**>(argv));
}
namespace convert {
template <typename T> inline
T long_(const char* arg)
{
char* endptr = nullptr;
errno = 0;
T ret = static_cast<T>(std::strtol(arg, &endptr, 0));
if (endptr == arg) {
std::ostringstream msg;
msg << "unable to convert argument to integer: \"" << arg << "\"";
throw std::invalid_argument(msg.str());
}
if (errno == ERANGE) {
throw std::out_of_range("argument numeric value out of range");
}
return ret;
}
template <typename T> inline
T long_long_(const char* arg)
{
char* endptr = nullptr;
errno = 0;
T ret = static_cast<T>(std::strtoll(arg, &endptr, 0));
if (endptr == arg) {
std::ostringstream msg;
msg << "unable to convert argument to integer: \"" << arg << "\"";
throw std::invalid_argument(msg.str());
}
if (errno == ERANGE) {
throw std::out_of_range("argument numeric value out of range");
}
return ret;
}
#define DEFINE_CONVERSION_FROM_LONG_(TYPE) \
template <> inline \
TYPE arg(const char* arg) \
{ \
return long_<TYPE>(arg); \
}
DEFINE_CONVERSION_FROM_LONG_(char)
DEFINE_CONVERSION_FROM_LONG_(unsigned char)
DEFINE_CONVERSION_FROM_LONG_(signed char)
DEFINE_CONVERSION_FROM_LONG_(short)
DEFINE_CONVERSION_FROM_LONG_(unsigned short)
DEFINE_CONVERSION_FROM_LONG_(int)
DEFINE_CONVERSION_FROM_LONG_(unsigned int)
DEFINE_CONVERSION_FROM_LONG_(long)
DEFINE_CONVERSION_FROM_LONG_(unsigned long)
#undef DEFINE_CONVERSION_FROM_LONG_
#define DEFINE_CONVERSION_FROM_LONG_LONG_(TYPE) \
template <> inline \
TYPE arg(const char* arg) \
{ \
return long_long_<TYPE>(arg); \
}
DEFINE_CONVERSION_FROM_LONG_LONG_(long long)
DEFINE_CONVERSION_FROM_LONG_LONG_(unsigned long long)
#undef DEFINE_CONVERSION_FROM_LONG_LONG_
template <typename T>
T arg(const char* arg)
{
return converter<T>::convert(arg);
}
template <> inline
bool arg(const char* arg)
{
return argagg::convert::arg<int>(arg) != 0;
}
template <> inline
float arg(const char* arg)
{
char* endptr = nullptr;
errno = 0;
float ret = std::strtof(arg, &endptr);
if (endptr == arg) {
std::ostringstream msg;
msg << "unable to convert argument to integer: \"" << arg << "\"";
throw std::invalid_argument(msg.str());
}
if (errno == ERANGE) {
throw std::out_of_range("argument numeric value out of range");
}
return ret;
}
template <> inline
double arg(const char* arg)
{
char* endptr = nullptr;
errno = 0;
double ret = std::strtod(arg, &endptr);
if (endptr == arg) {
std::ostringstream msg;
msg << "unable to convert argument to integer: \"" << arg << "\"";
throw std::invalid_argument(msg.str());
}
if (errno == ERANGE) {
throw std::out_of_range("argument numeric value out of range");
}
return ret;
}
template <> inline
const char* arg(const char* arg)
{
return arg;
}
template <> inline
std::string arg(const char* arg)
{
return std::string(arg);
}
template <typename T>
bool parse_next_component(
const char*& s,
T& out_arg,
const char delim)
{
const char* begin = s;
s = std::strchr(s, delim);
if (s == nullptr) {
std::string arg_str(begin);
out_arg = argagg::convert::arg<T>(arg_str.c_str());
return false;
} else {
std::string arg_str(begin, static_cast<std::size_t>(s - begin));
out_arg = argagg::convert::arg<T>(arg_str.c_str());
s += 1;
return true;
}
}
} // namespace convert
inline
fmt_ostream::fmt_ostream(std::ostream& output)
: std::ostringstream(), output(output)
{
}
inline
fmt_ostream::~fmt_ostream()
{
output << fmt_string(this->str());
}
inline
std::string lstrip(const std::string& text)
{
auto result = text;
result.erase(
result.begin(),
std::find_if(
result.begin(),
result.end(),
[](int ch) { return !std::isspace(ch); }));
return result;
}
inline
std::string rstrip(const std::string& text)
{
auto result = text;
result.erase(
std::find_if(
result.rbegin(),
result.rend(),
[](int ch) { return !std::isspace(ch); }).base(),
result.end());
return result;
}
inline
std::string construct_line(const std::string& indent,
const std::string& contents)
{
return indent + rstrip(contents) + "\n";
}
inline
std::string wrap_line(const std::string& single_line,
const std::size_t wrap_width)
{
auto indentation_spaces = single_line.find_first_not_of(" ");
if (indentation_spaces == std::string::npos) {
indentation_spaces = 0;
}
const auto line = lstrip(single_line);
const auto indent = std::string(indentation_spaces, ' ');
std::string result;
std::size_t position = 0;
std::size_t line_start = 0;
while (true) {
const auto new_position = line.find_first_of(" ", position);
if (new_position == std::string::npos) {
break;
}
if (new_position + indentation_spaces > line_start + wrap_width) {
result += construct_line(
indent, line.substr(line_start, position - line_start - 1));
line_start = position;
}
position = new_position + 1;
}
return result + construct_line(indent, line.substr(line_start));
}
inline
std::string fmt_string(const std::string& s)
{
std::stringstream ss(s);
std::string line;
std::string result;
// Use default width of `fmt`.
const auto column_width = 75;
while (std::getline(ss, line, '\n')) {
result += wrap_line(line, column_width);
}
return result;
}
} // namespace argagg
inline
std::ostream& operator << (std::ostream& os, const argagg::parser& x)
{
for (auto& definition : x.definitions) {
os << " ";
for (auto& flag : definition.flags) {
os << flag;
if (flag != definition.flags.back()) {
os << ", ";
}
}
os << "\n " << definition.help << '\n';
}
return os;
}
#endif // ARGAGG_ARGAGG_ARGAGG_HPP
std::all_of
T all_of(T... args)
DEFINE_CONVERSION_FROM_LONG_LONG_
#define DEFINE_CONVERSION_FROM_LONG_LONG_(TYPE)
Definition argagg.hpp:1474
DEFINE_CONVERSION_FROM_LONG_
#define DEFINE_CONVERSION_FROM_LONG_(TYPE)
Definition argagg.hpp:1454
operator<<
std::ostream & operator<<(std::ostream &os, const argagg::parser &x)
Writes the option help to the given stream.
Definition argagg.hpp:1692
std::array
std::ostream
std::ostringstream
std::string
std::stringstream
std::vector::begin
T begin(T... args)
std::string::c_str
T c_str(T... args)
std::domain_error
std::string::erase
T erase(T... args)
std::string::find_first_not_of
T find_first_not_of(T... args)
std::find_if
T find_if(T... args)
std::getline
T getline(T... args)
std::invalid_argument
std::isalnum
T isalnum(T... args)
std::make_pair
T make_pair(T... args)
std::move
T move(T... args)
argagg::convert
The set of template instantiations that convert C-strings to other types for the option_result::as(),...
Definition argagg.hpp:164
argagg::convert::long_
T long_(const char *arg)
Templated function for conversion to T using the std::strtol() function. This is used for anything lo...
Definition argagg.hpp:1413
argagg::convert::parse_next_component
bool parse_next_component(const char *&s, T &out_arg, const char delim=',')
A utility function for parsing an argument as a delimited list. To use, initialize a const char* poin...
Definition argagg.hpp:1552
argagg::convert::arg
T arg(const char *arg)
Explicit instantiations of this function are used to convert arguments to types.
Definition argagg.hpp:1488
argagg::convert::long_long_
T long_long_(const char *arg)
Templated function for conversion to T using the std::strtoll() function. This is used for anything l...
Definition argagg.hpp:1437
argagg
There are only two hard things in Computer Science: cache invalidation and naming things (Phil Karlto...
Definition argagg.hpp:96
argagg::validate_definitions
parser_map validate_definitions(const std::vector< definition > &definitions)
Validates a collection (specifically an std::vector) of definition objects by checking if the contain...
Definition argagg.hpp:1110
argagg::rstrip
std::string rstrip(const std::string &text)
Definition argagg.hpp:1606
argagg::flag_is_short
bool flag_is_short(const char *s)
Tests whether or not a valid flag is short. Assumes the provided cstring is already a valid flag.
Definition argagg.hpp:1065
argagg::cmd_line_arg_is_option_flag
bool cmd_line_arg_is_option_flag(const char *s)
Checks whether or not a command line argument should be processed as an option flag....
Definition argagg.hpp:929
argagg::construct_line
std::string construct_line(const std::string &indent, const std::string &contents)
Definition argagg.hpp:1622
argagg::lstrip
std::string lstrip(const std::string &text)
Definition argagg.hpp:1590
argagg::fmt_string
std::string fmt_string(const std::string &s)
Processes the provided string using the fmt utility and returns the resulting output as a string....
Definition argagg.hpp:1670
argagg::wrap_line
std::string wrap_line(const std::string &single_line, const std::size_t wrap_width)
Return a wrapped version of a single line of text.
Definition argagg.hpp:1634
argagg::is_valid_flag_definition
bool is_valid_flag_definition(const char *s)
Checks whether a flag in an option definition is valid. I suggest reading through the function source...
Definition argagg.hpp:1009
std
STL namespace.
std::out_of_range
std::vector::push_back
T push_back(T... args)
std::runtime_error
std::size_t
std::ostringstream::str
T str(T... args)
std::strchr
T strchr(T... args)
std::strlen
T strlen(T... args)
std::strncmp
T strncmp(T... args)
std::strtof
T strtof(T... args)
std::strtol
T strtol(T... args)
argagg::convert::converter
For simple types the main extension point for adding argument conversions is argagg::convert::arg<T>(...
Definition argagg.hpp:186
argagg::convert::converter::convert
static T convert(const char *arg)
argagg::definition
An option definition which essentially represents what an option is.
Definition argagg.hpp:488
argagg::definition::requires_arguments
bool requires_arguments() const
Returns true if this option requires arguments.
Definition argagg.hpp:922
argagg::definition::wants_no_arguments
bool wants_no_arguments() const
Returns true if this option does not want any arguments.
Definition argagg.hpp:915
argagg::definition::name
const std::string name
Name of the option. Option parser results are keyed by this name.
Definition argagg.hpp:494
argagg::definition::flags
std::vector< std::string > flags
List of strings to match that correspond to this option. Should be fully specified with hyphens (e....
Definition argagg.hpp:501
argagg::definition::help
std::string help
Help string for this option.
Definition argagg.hpp:507
argagg::definition::num_args
unsigned int num_args
Number of arguments this option requires. Must be 0 or 1. All other values have undefined behavior....
Definition argagg.hpp:515
argagg::fmt_ostream::output
std::ostream & output
Reference to the final output stream that the formatted string will be streamed to.
Definition argagg.hpp:690
argagg::fmt_ostream::~fmt_ostream
~fmt_ostream()
Special destructor that will format the accumulated string using fmt (via the argagg::fmt_string() fu...
Definition argagg.hpp:1583
argagg::fmt_ostream::fmt_ostream
fmt_ostream(std::ostream &output)
Construct to output to the provided output stream when this object is destroyed.
Definition argagg.hpp:1576
argagg::invalid_flag
This exception is thrown when an option's flag is invalid. This can be the case if the flag is not pr...
Definition argagg.hpp:142
argagg::option_lacks_argument_error
This exception is thrown when an option requires an argument but is not provided one....
Definition argagg.hpp:129
argagg::option_result
Represents a single option parse result.
Definition argagg.hpp:264
argagg::option_result::arg
const char * arg
Argument parsed for this single option. If no argument was parsed this will be set to nullptr.
Definition argagg.hpp:271
argagg::option_result::operator!
bool operator!() const
Explicitly define a unary not operator that wraps the implicit boolean conversion specialization in c...
Definition argagg.hpp:781
argagg::option_result::as
T as() const
Converts the argument parsed for this single option instance into the given type using the type match...
Definition argagg.hpp:736
argagg::option_results
Represents multiple option parse results for a single option. If treated as a single parse result it ...
Definition argagg.hpp:332
argagg::option_results::count
std::size_t count() const
Gets the number of times the option shows up.
Definition argagg.hpp:788
argagg::option_results::operator[]
option_result & operator[](std::size_t index)
Gets a single option parse result by index.
Definition argagg.hpp:795
argagg::option_results::all
std::vector< option_result > all
All option parse results for this option.
Definition argagg.hpp:338
argagg::option_results::as
T as() const
Converts the argument parsed for the LAST option parse result for the parent definition to the provid...
Definition argagg.hpp:809
argagg::option_results::operator!
bool operator!() const
Explicitly define a unary not operator that wraps the implicit boolean conversion specialization in c...
Definition argagg.hpp:843
argagg::parser_map
Contains two maps which aid in option parsing. The first map, short_map, maps from a short flag (just...
Definition argagg.hpp:572
argagg::parser_map::get_definition_for_long_flag
const definition * get_definition_for_long_flag(const std::string &flag) const
If the long flag exists in the map object then it is returned by this method. If it doesn't then null...
Definition argagg.hpp:1098
argagg::parser_map::long_map
std::unordered_map< std::string, const definition * > long_map
Maps from a long flag (an std::string) to a pointer to the original definition that the flag represen...
Definition argagg.hpp:586
argagg::parser_map::known_short_flag
bool known_short_flag(const char flag) const
Returns true if the provided short flag exists in the map object.
Definition argagg.hpp:1073
argagg::parser_map::known_long_flag
bool known_long_flag(const std::string &flag) const
Returns true if the provided long flag exists in the map object.
Definition argagg.hpp:1089
argagg::parser_map::get_definition_for_short_flag
const definition * get_definition_for_short_flag(const char flag) const
If the short flag exists in the map object then it is returned by this method. If it doesn't then nul...
Definition argagg.hpp:1081
argagg::parser_map::short_map
std::array< const definition *, 256 > short_map
Maps from a short flag (just a character) to a pointer to the original definition that the flag repre...
Definition argagg.hpp:579
argagg::parser_results
Represents all results of the parser including options and positional arguments.
Definition argagg.hpp:411
argagg::parser_results::count
std::size_t count() const
Gets the number of positional arguments.
Definition argagg.hpp:881
argagg::parser_results::pos
std::vector< const char * > pos
Vector of positional arguments.
Definition argagg.hpp:431
argagg::parser_results::all_as
std::vector< T > all_as() const
Gets all positional arguments converted to the given type.
Definition argagg.hpp:902
argagg::parser_results::operator[]
option_results & operator[](const std::string &name)
Get the parser results for the given definition. If the definition never showed up then the exception...
Definition argagg.hpp:858
argagg::parser_results::has_option
bool has_option(const std::string &name) const
Used to check if an option was specified at all.
Definition argagg.hpp:850
argagg::parser_results::as
T as(std::size_t i=0) const
Gets a positional argument converted to the given type.
Definition argagg.hpp:895
argagg::parser_results::options
std::unordered_map< std::string, option_results > options
Maps from definition name to the structure which contains the parser results for that definition.
Definition argagg.hpp:425
argagg::parser_results::program
const char * program
Returns the name of the program from the original arguments list. This is always the first argument.
Definition argagg.hpp:418
argagg::parser
A list of option definitions used to inform how to parse arguments.
Definition argagg.hpp:636
argagg::parser::definitions
std::vector< definition > definitions
Vector of the option definitions which inform this parser how to parse the command line arguments.
Definition argagg.hpp:643
argagg::parser::parse
parser_results parse(int argc, const char **argv) const
Parses the provided command line arguments and returns the results as parser_results.
Definition argagg.hpp:1167
argagg::unexpected_argument_error
This exception is thrown when a long option is parsed and is given an argument using the "=" syntax b...
Definition argagg.hpp:105
argagg::unexpected_option_error
This exception is thrown when an option is parsed unexpectedly such as when an argument was expected ...
Definition argagg.hpp:117
argagg::unknown_option
This exception is thrown when an unknown option is requested by name from an argagg::parser_results t...
Definition argagg.hpp:153
std::transform
T transform(T... args)
std::unordered_map
std::vector