You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

1331 lines
36 KiB

/***
____ __ ____ __
( _ \ / \( _ \( )
) __/( O )) __// (_/\
(__) \__/(__) \____/
version 1.3.0
https://github.com/badaix/popl
This file is part of popl (program options parser lib)
Copyright (C) 2015-2021 Johannes Pohl
This software may be modified and distributed under the terms
of the MIT license. See the LICENSE file for details.
***/
/// checked with clang-tidy:
/// run-clang-tidy-3.8.py -header-filter='.*'
/// -checks='*,-misc-definitions-in-headers,-google-readability-braces-around-statements,-readability-braces-around-statements,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-google-build-using-namespace,-google-build-using-namespace'
#ifndef POPL_HPP
#define POPL_HPP
#ifndef NOMINMAX
#define NOMINMAX
#endif // NOMINMAX
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <vector>
#ifdef WINDOWS
#include <cctype>
#endif
namespace popl
{
#define POPL_VERSION "1.3.0"
/// Option's argument type
/**
* Switch has "no" argument
* Value has "required" argument
* Implicit has "optional" argument
*/
enum class Argument
{
no = 0, // option never takes an argument
required, // option always requires an argument
optional // option may take an argument
};
/// Option's attribute
/**
* inactive: Option is not set and will not be parsed
* hidden: Option is active, but will not show up in the help message
* required: Option must be set on the command line. Otherwise an exception will be thrown
* optional: Option must not be set. Default attribute.
* advanced: Option is advanced and will only show up in the advanced help message
* expoert: Option is expert and will only show up in the expert help message
*/
enum class Attribute
{
inactive = 0,
hidden = 1,
required = 2,
optional = 3,
advanced = 4,
expert = 5
};
/// Option name type. Used in invalid_option exception.
/**
* unspecified: not specified
* short_name: The option's short name
* long_name: The option's long name
*/
enum class OptionName
{
unspecified,
short_name,
long_name
};
/// Abstract Base class for Options
/**
* Base class for Options
* holds just configuration data, no runtime data.
* Option is not bound to a special type "T"
*/
class Option
{
friend class OptionParser;
public:
/// Construct an Option
/// @param short_name the options's short name. Must be empty or one character.
/// @param long_name the option's long name. Can be empty.
/// @param description the Option's description that will be shown in the help message
Option(const std::string& short_name, const std::string& long_name, std::string description);
/// Destructor
virtual ~Option() = default;
/// default copy constructor
Option(const Option&) = default;
/// default move constructor
Option(Option&&) = default;
/// default assignement operator
Option& operator=(const Option&) = default;
/// default move assignement operator
Option& operator=(Option&&) = default;
/// Get the Option's short name
/// @return character of the options's short name or 0 if no short name is defined
char short_name() const;
/// Get the Option's long name
/// @return the long name of the Option. Empty string if no long name is defined
std::string long_name() const;
/// Get the Option's long or short name
/// @param what_name the option's name to return
/// @param what_hyphen preced the returned name with (double-)hypen
/// @return the requested name of the Option. Empty string if not defined.
std::string name(OptionName what_name, bool with_hypen = false) const;
/// Get the Option's description
/// @return the description
std::string description() const;
/// Get the Option's default value
/// @param out stream to write the default value to
/// @return true if a default value is available, false if not
virtual bool get_default(std::ostream& out) const = 0;
/// Set the Option's attribute
/// @param attribute
void set_attribute(const Attribute& attribute);
/// Get the Option's attribute
/// @return the Options's attribute
Attribute attribute() const;
/// Get the Option's argument type
/// @return argument type (no, required, optional)
virtual Argument argument_type() const = 0;
/// Check how often the Option is set on command line
/// @return the Option's count on command line
virtual size_t count() const = 0;
/// Check if the Option is set
/// @return true if set at least once
virtual bool is_set() const = 0;
protected:
/// Parse the command line option and fill the internal data structure
/// @param what_name short or long option name
/// @param value the value as given on command line
virtual void parse(OptionName what_name, const char* value) = 0;
/// Clear the internal data structure
virtual void clear() = 0;
std::string short_name_;
std::string long_name_;
std::string description_;
Attribute attribute_;
};
/// Value option with optional default value
/**
* Value option with optional default value
* If set, it requires an argument
*/
template <class T>
class Value : public Option
{
public:
/// Construct an Value Option
/// @param short_name the option's short name. Must be empty or one character.
/// @param long_name the option's long name. Can be empty.
/// @param description the Option's description that will be shown in the help message
Value(const std::string& short_name, const std::string& long_name, const std::string& description);
/// Construct an Value Option
/// @param short_name the option's short name. Must be empty or one character.
/// @param long_name the option's long name. Can be empty.
/// @param description the Option's description that will be shown in the help message
/// @param default_val the Option's default value
/// @param assign_to pointer to a variable to assign the parsed command line value to
Value(const std::string& short_name, const std::string& long_name, const std::string& description, const T& default_val, T* assign_to = nullptr);
size_t count() const override;
bool is_set() const override;
/// Assign the last parsed command line value to "var"
/// @param var pointer to the variable where is value is written to
void assign_to(T* var);
/// Manually set the Option's value. Deletes current value(s)
/// @param value the new value of the option
void set_value(const T& value);
/// Get the Option's value. Will throw if option at index idx is not available
/// @param idx the zero based index of the value (if set multiple times)
/// @return the Option's value at index "idx"
T value(size_t idx = 0) const;
/// Get the Option's value, return default_value if not set.
/// @param default_value return value if value is not set
/// @param idx the zero based index of the value (if set multiple times)
/// @return the Option's value at index "idx" or the default value or default_value
T value_or(const T& default_value, size_t idx = 0) const;
/// Set the Option's default value
/// @param value the default value if not specified on command line
void set_default(const T& value);
/// Check if the Option has a default value
/// @return true if the Option has a default value
bool has_default() const;
/// Get the Option's default value. Will throw if no default is set.
/// @return the Option's default value
T get_default() const;
bool get_default(std::ostream& out) const override;
Argument argument_type() const override;
protected:
void parse(OptionName what_name, const char* value) override;
std::unique_ptr<T> default_;
virtual void update_reference();
virtual void add_value(const T& value);
void clear() override;
T* assign_to_;
std::vector<T> values_;
};
/// Value option with implicit default value
/**
* Value option with implicit default value
* If set, an argument is optional
* -without argument it carries the implicit default value
* -with argument it carries the explicit value
*/
template <class T>
class Implicit : public Value<T>
{
public:
Implicit(const std::string& short_name, const std::string& long_name, const std::string& description, const T& implicit_val, T* assign_to = nullptr);
Argument argument_type() const override;
protected:
void parse(OptionName what_name, const char* value) override;
};
/// Value option without value
/**
* Value option without value
* Does not require an argument
* Can be either set or not set
*/
class Switch : public Value<bool>
{
public:
Switch(const std::string& short_name, const std::string& long_name, const std::string& description, bool* assign_to = nullptr);
void set_default(const bool& value) = delete;
Argument argument_type() const override;
protected:
void parse(OptionName what_name, const char* value) override;
};
using Option_ptr = std::shared_ptr<Option>;
/// OptionParser manages all Options
/**
* OptionParser manages all Options
* Add Options (Option_Type = Value<T>, Implicit<T> or Switch) with "add<Option_Type>(option params)"
* Call "parse(argc, argv)" to trigger parsing of the options and to
* fill "non_option_args" and "unknown_options"
*/
class OptionParser
{
public:
/// Construct the OptionParser
/// @param description used for the help message
explicit OptionParser(std::string description = "");
/// Destructor
virtual ~OptionParser() = default;
/// Add an Option e.g. 'add<Value<int>>("i", "int", "description for the -i option")'
/// @param T the option type (Value, Switch, Implicit)
/// @param attribute the Option's attribute (inactive, hidden, required, optional, ...)
/// @param Ts the Option's parameter
template <typename T, Attribute attribute, typename... Ts>
std::shared_ptr<T> add(Ts&&... params);
/// Add an Option e.g. 'add<Value<int>>("i", "int", "description for the -i option")'
/// @param T the option type (Value, Switch, Implicit)
/// @param Ts the Option's parameter
template <typename T, typename... Ts>
std::shared_ptr<T> add(Ts&&... params);
/// Parse an ini file into the added Options
/// @param ini_filename full path of the ini file
void parse(const std::string& ini_filename);
/// Parse the command line into the added Options
/// @param argc command line argument count
/// @param argv command line arguments
void parse(int argc, const char* const argv[]);
/// Delete all parsed options
void reset();
/// Produce a help message
/// @param max_attribute show options up to this level (optional, advanced, expert)
/// @return the help message
std::string help(const Attribute& max_attribute = Attribute::optional) const;
/// Get the OptionParser's description
/// @return the description as given during construction
std::string description() const;
/// Get all options that where added with "add"
/// @return a vector of the contained Options
const std::vector<Option_ptr>& options() const;
/// Get command line arguments without option
/// e.g. "-i 5 hello" => hello
/// e.g. "-i 5 -- from here non option args" => "from", "here", "non", "option", "args"
/// @return vector to "stand-alone" command line arguments
const std::vector<std::string>& non_option_args() const;
/// Get unknown command options
/// e.g. '--some_unknown_option="hello"'
/// @return vector to "stand-alone" command line arguments
const std::vector<std::string>& unknown_options() const;
/// Get an Option by it's long name
/// @param the Option's long name
/// @return a pointer of type "Value, Switch, Implicit" to the Option or nullptr
template <typename T>
std::shared_ptr<T> get_option(const std::string& long_name) const;
/// Get an Option by it's short name
/// @param the Option's short name
/// @return a pointer of type "Value, Switch, Implicit" to the Option or nullptr
template <typename T>
std::shared_ptr<T> get_option(char short_name) const;
protected:
std::vector<Option_ptr> options_;
std::string description_;
std::vector<std::string> non_option_args_;
std::vector<std::string> unknown_options_;
Option_ptr find_option(const std::string& long_name) const;
Option_ptr find_option(char short_name) const;
};
class invalid_option : public std::invalid_argument
{
public:
enum class Error
{
missing_argument,
invalid_argument,
too_many_arguments,
missing_option
};
invalid_option(const Option* option, invalid_option::Error error, OptionName what_name, std::string value, const std::string& text)
: std::invalid_argument(text.c_str()), option_(option), error_(error), what_name_(what_name), value_(std::move(value))
{
}
invalid_option(const Option* option, invalid_option::Error error, const std::string& text)
: invalid_option(option, error, OptionName::unspecified, "", text)
{
}
const Option* option() const
{
return option_;
}
Error error() const
{
return error_;
}
OptionName what_name() const
{
return what_name_;
}
std::string value() const
{
return value_;
}
private:
const Option* option_;
Error error_;
OptionName what_name_;
std::string value_;
};
/// Base class for an OptionPrinter
/**
* OptionPrinter creates a help message for a given OptionParser
*/
class OptionPrinter
{
public:
/// Constructor
/// @param option_parser the OptionParser to create the help message from
explicit OptionPrinter(const OptionParser* option_parser) : option_parser_(option_parser)
{
}
/// Destructor
virtual ~OptionPrinter() = default;
/// Create a help message
/// @param max_attribute show options up to this level (optional, advanced, expert)
/// @return the help message
virtual std::string print(const Attribute& max_attribute = Attribute::optional) const = 0;
protected:
const OptionParser* option_parser_;
};
/// Option printer for the console
/**
* Standard console option printer
* Creates a human readable help message
*/
class ConsoleOptionPrinter : public OptionPrinter
{
public:
explicit ConsoleOptionPrinter(const OptionParser* option_parser);
~ConsoleOptionPrinter() override = default;
std::string print(const Attribute& max_attribute = Attribute::optional) const override;
private:
std::string to_string(Option_ptr option) const;
};
/// Option printer for man pages
/**
* Creates help messages in groff format that can be used in man pages
*/
class GroffOptionPrinter : public OptionPrinter
{
public:
explicit GroffOptionPrinter(const OptionParser* option_parser);
~GroffOptionPrinter() override = default;
std::string print(const Attribute& max_attribute = Attribute::optional) const override;
private:
std::string to_string(Option_ptr option) const;
};
/// Option printer for bash completion
/**
* Creates a script with all options (short and long) that can be used for bash completion
*/
class BashCompletionOptionPrinter : public OptionPrinter
{
public:
BashCompletionOptionPrinter(const OptionParser* option_parser, std::string program_name);
~BashCompletionOptionPrinter() override = default;
std::string print(const Attribute& max_attribute = Attribute::optional) const override;
private:
std::string program_name_;
};
/// Option implementation /////////////////////////////////
inline Option::Option(const std::string& short_name, const std::string& long_name, std::string description)
: short_name_(short_name), long_name_(long_name), description_(std::move(description)), attribute_(Attribute::optional)
{
if (short_name.size() > 1)
throw std::invalid_argument("length of short name must be <= 1: '" + short_name + "'");
if (short_name.empty() && long_name.empty())
throw std::invalid_argument("short and long name are empty");
}
inline char Option::short_name() const
{
if (!short_name_.empty())
return short_name_[0];
return 0;
}
inline std::string Option::long_name() const
{
return long_name_;
}
inline std::string Option::name(OptionName what_name, bool with_hypen) const
{
if (what_name == OptionName::short_name)
return short_name_.empty() ? "" : ((with_hypen ? "-" : "") + short_name_);
if (what_name == OptionName::long_name)
return long_name_.empty() ? "" : ((with_hypen ? "--" : "") + long_name_);
return "";
}
inline std::string Option::description() const
{
return description_;
}
inline void Option::set_attribute(const Attribute& attribute)
{
attribute_ = attribute;
}
inline Attribute Option::attribute() const
{
return attribute_;
}
/// Value implementation /////////////////////////////////
template <class T>
inline Value<T>::Value(const std::string& short_name, const std::string& long_name, const std::string& description)
: Option(short_name, long_name, description), assign_to_(nullptr)
{
}
template <class T>
inline Value<T>::Value(const std::string& short_name, const std::string& long_name, const std::string& description, const T& default_val, T* assign_to)
: Value<T>(short_name, long_name, description)
{
assign_to_ = assign_to;
set_default(default_val);
}
template <class T>
inline size_t Value<T>::count() const
{
return values_.size();
}
template <class T>
inline bool Value<T>::is_set() const
{
return !values_.empty();
}
template <class T>
inline void Value<T>::assign_to(T* var)
{
assign_to_ = var;
update_reference();
}
template <class T>
inline void Value<T>::set_value(const T& value)
{
clear();
add_value(value);
}
template <class T>
inline T Value<T>::value_or(const T& default_value, size_t idx) const
{
if (idx < values_.size())
return values_[idx];
else if (default_)
return *default_;
else
return default_value;
}
template <class T>
inline T Value<T>::value(size_t idx) const
{
if (!this->is_set() && default_)
return *default_;
if (!is_set() || (idx >= count()))
{
std::stringstream optionStr;
if (!is_set())
optionStr << "option not set: \"";
else
optionStr << "index out of range (" << idx << ") for \"";
if (short_name() != 0)
optionStr << "-" << short_name();
else
optionStr << "--" << long_name();
optionStr << "\"";
throw std::out_of_range(optionStr.str());
}
return values_[idx];
}
template <class T>
inline void Value<T>::set_default(const T& value)
{
this->default_.reset(new T);
*this->default_ = value;
update_reference();
}
template <class T>
inline bool Value<T>::has_default() const
{
return (this->default_ != nullptr);
}
template <class T>
inline T Value<T>::get_default() const
{
if (!has_default())
throw std::runtime_error("no default value set");
return *this->default_;
}
template <class T>
inline bool Value<T>::get_default(std::ostream& out) const
{
if (!has_default())
return false;
out << *this->default_;
return true;
}
template <class T>
inline Argument Value<T>::argument_type() const
{
return Argument::required;
}
template <>
inline void Value<std::string>::parse(OptionName what_name, const char* value)
{
if (strlen(value) == 0)
throw invalid_option(this, invalid_option::Error::missing_argument, what_name, value, "missing argument for " + name(what_name, true));
add_value(value);
}
template <>
inline void Value<bool>::parse(OptionName /*what_name*/, const char* value)
{
bool val =
((value != nullptr) && ((strcmp(value, "1") == 0) || (strcmp(value, "true") == 0) || (strcmp(value, "True") == 0) || (strcmp(value, "TRUE") == 0)));
add_value(val);
}
template <class T>
inline void Value<T>::parse(OptionName what_name, const char* value)
{
T parsed_value;
std::string strValue;
if (value != nullptr)
strValue = value;
std::istringstream is(strValue);
int valuesRead = 0;
while (is.good())
{
if (is.peek() != EOF)
is >> parsed_value;
else
break;
valuesRead++;
}
if (is.fail())
throw invalid_option(this, invalid_option::Error::invalid_argument, what_name, value,
"invalid argument for " + name(what_name, true) + ": '" + strValue + "'");
if (valuesRead > 1)
throw invalid_option(this, invalid_option::Error::too_many_arguments, what_name, value,
"too many arguments for " + name(what_name, true) + ": '" + strValue + "'");
if (strValue.empty())
throw invalid_option(this, invalid_option::Error::missing_argument, what_name, "", "missing argument for " + name(what_name, true));
this->add_value(parsed_value);
}
template <class T>
inline void Value<T>::update_reference()
{
if (this->assign_to_)
{
if (!this->is_set() && default_)
*this->assign_to_ = *default_;
else if (this->is_set())
*this->assign_to_ = values_.back();
}
}
template <class T>
inline void Value<T>::add_value(const T& value)
{
values_.push_back(value);
update_reference();
}
template <class T>
inline void Value<T>::clear()
{
values_.clear();
update_reference();
}
/// Implicit implementation /////////////////////////////////
template <class T>
inline Implicit<T>::Implicit(const std::string& short_name, const std::string& long_name, const std::string& description, const T& implicit_val, T* assign_to)
: Value<T>(short_name, long_name, description, implicit_val, assign_to)
{
}
template <class T>
inline Argument Implicit<T>::argument_type() const
{
return Argument::optional;
}
template <class T>
inline void Implicit<T>::parse(OptionName what_name, const char* value)
{
if ((value != nullptr) && (strlen(value) > 0))
Value<T>::parse(what_name, value);
else
this->add_value(*this->default_);
}
/// Switch implementation /////////////////////////////////
inline Switch::Switch(const std::string& short_name, const std::string& long_name, const std::string& description, bool* assign_to)
: Value<bool>(short_name, long_name, description, false, assign_to)
{
}
inline void Switch::parse(OptionName /*what_name*/, const char* /*value*/)
{
add_value(true);
}
inline Argument Switch::argument_type() const
{
return Argument::no;
}
/// OptionParser implementation /////////////////////////////////
inline OptionParser::OptionParser(std::string description) : description_(std::move(description))
{
}
template <typename T, typename... Ts>
inline std::shared_ptr<T> OptionParser::add(Ts&&... params)
{
return add<T, Attribute::optional>(std::forward<Ts>(params)...);
}
template <typename T, Attribute attribute, typename... Ts>
inline std::shared_ptr<T> OptionParser::add(Ts&&... params)
{
static_assert(std::is_base_of<Option, typename std::decay<T>::type>::value, "type T must be Switch, Value or Implicit");
std::shared_ptr<T> option = std::make_shared<T>(std::forward<Ts>(params)...);
for (const auto& o : options_)
{
if ((option->short_name() != 0) && (option->short_name() == o->short_name()))
throw std::invalid_argument("duplicate short option name '-" + std::string(1, option->short_name()) + "'");
if (!option->long_name().empty() && (option->long_name() == (o->long_name())))
throw std::invalid_argument("duplicate long option name '--" + option->long_name() + "'");
}
option->set_attribute(attribute);
options_.push_back(option);
return option;
}
inline std::string OptionParser::description() const
{
return description_;
}
inline const std::vector<Option_ptr>& OptionParser::options() const
{
return options_;
}
inline const std::vector<std::string>& OptionParser::non_option_args() const
{
return non_option_args_;
}
inline const std::vector<std::string>& OptionParser::unknown_options() const
{
return unknown_options_;
}
inline Option_ptr OptionParser::find_option(const std::string& long_name) const
{
for (const auto& option : options_)
if (option->long_name() == long_name)
return option;
return nullptr;
}
inline Option_ptr OptionParser::find_option(char short_name) const
{
for (const auto& option : options_)
if (option->short_name() == short_name)
return option;
return nullptr;
}
template <typename T>
inline std::shared_ptr<T> OptionParser::get_option(const std::string& long_name) const
{
Option_ptr option = find_option(long_name);
if (!option)
throw std::invalid_argument("option not found: " + long_name);
auto result = std::dynamic_pointer_cast<T>(option);
if (!result)
throw std::invalid_argument("cannot cast option to T: " + long_name);
return result;
}
template <typename T>
inline std::shared_ptr<T> OptionParser::get_option(char short_name) const
{
Option_ptr option = find_option(short_name);
if (!option)
throw std::invalid_argument("option not found: " + std::string(1, short_name));
auto result = std::dynamic_pointer_cast<T>(option);
if (!result)
throw std::invalid_argument("cannot cast option to T: " + std::string(1, short_name));
return result;
}
inline void OptionParser::parse(const std::string& ini_filename)
{
std::ifstream file(ini_filename.c_str());
std::string line;
auto trim = [](std::string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); }));
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end());
return s;
};
auto trim_copy = [trim](const std::string& s) {
std::string copy(s);
return trim(copy);
};
auto split = [trim_copy](const std::string& s) -> std::pair<std::string, std::string> {
size_t pos = s.find('=');
if (pos == std::string::npos)
return {"", ""};
return {trim_copy(s.substr(0, pos)), trim_copy(s.substr(pos + 1, std::string::npos))};
};
std::string section;
while (std::getline(file, line))
{
trim(line);
if (line.empty())
continue;
if (line.front() == '#')
continue;
if ((line.front() == '[') && (line.back() == ']'))
{
section = trim_copy(line.substr(1, line.size() - 2));
continue;
}
auto key_value = split(line);
if (key_value.first.empty())
continue;
std::string key = section.empty() ? key_value.first : section + "." + key_value.first;
Option_ptr option = find_option(key);
if (option && (option->attribute() == Attribute::inactive))
option = nullptr;
if (option)
option->parse(OptionName::long_name, key_value.second.c_str());
else
unknown_options_.push_back(key);
}
}
inline void OptionParser::parse(int argc, const char* const argv[])
{
for (int n = 1; n < argc; ++n)
{
const std::string arg(argv[n]);
if (arg == "--")
{
/// from here on only non opt args
for (int m = n + 1; m < argc; ++m)
non_option_args_.emplace_back(argv[m]);
}
else if (arg.find("--") == 0)
{
/// long option arg
std::string opt = arg.substr(2);
std::string optarg;
size_t equalIdx = opt.find('=');
if (equalIdx != std::string::npos)
{
optarg = opt.substr(equalIdx + 1);
opt.resize(equalIdx);
}
Option_ptr option = find_option(opt);
if (option && (option->attribute() == Attribute::inactive))
option = nullptr;
if (option)
{
if (option->argument_type() == Argument::no)
{
if (!optarg.empty())
option = nullptr;
}
else if (option->argument_type() == Argument::required)
{
if (optarg.empty() && n < argc - 1)
optarg = argv[++n];
}
}
if (option)
option->parse(OptionName::long_name, optarg.c_str());
else
unknown_options_.push_back(arg);
}
else if (arg.find('-') == 0)
{
/// short option arg
std::string opt = arg.substr(1);
bool unknown = false;
for (size_t m = 0; m < opt.size(); ++m)
{
char c = opt[m];
std::string optarg;
Option_ptr option = find_option(c);
if (option && (option->attribute() == Attribute::inactive))
option = nullptr;
if (option)
{
if (option->argument_type() == Argument::required)
{
/// use the rest of the current argument as optarg
optarg = opt.substr(m + 1);
/// or the next arg
if (optarg.empty() && n < argc - 1)
optarg = argv[++n];
m = opt.size();
}
else if (option->argument_type() == Argument::optional)
{
/// use the rest of the current argument as optarg
optarg = opt.substr(m + 1);
m = opt.size();
}
}
if (option)
option->parse(OptionName::short_name, optarg.c_str());
else
unknown = true;
}
if (unknown)
unknown_options_.push_back(arg);
}
else
{
non_option_args_.push_back(arg);
}
}
for (auto& opt : options_)
{
if ((opt->attribute() == Attribute::required) && !opt->is_set())
{
std::string option = opt->long_name().empty() ? std::string(1, opt->short_name()) : opt->long_name();
throw invalid_option(opt.get(), invalid_option::Error::missing_option, "option \"" + option + "\" is required");
}
}
}
inline void OptionParser::reset()
{
unknown_options_.clear();
non_option_args_.clear();
for (auto& opt : options_)
opt->clear();
}
inline std::string OptionParser::help(const Attribute& max_attribute) const
{
ConsoleOptionPrinter option_printer(this);
return option_printer.print(max_attribute);
}
/// ConsoleOptionPrinter implementation /////////////////////////////////
inline ConsoleOptionPrinter::ConsoleOptionPrinter(const OptionParser* option_parser) : OptionPrinter(option_parser)
{
}
inline std::string ConsoleOptionPrinter::to_string(Option_ptr option) const
{
std::stringstream line;
if (option->short_name() != 0)
{
line << " -" << option->short_name();
if (!option->long_name().empty())
line << ", ";
}
else
line << " ";
if (!option->long_name().empty())
line << "--" << option->long_name();
if (option->argument_type() == Argument::required)
{
line << " arg";
std::stringstream defaultStr;
if (option->get_default(defaultStr))
{
if (!defaultStr.str().empty())
line << " (=" << defaultStr.str() << ")";
}
}
else if (option->argument_type() == Argument::optional)
{
std::stringstream defaultStr;
if (option->get_default(defaultStr))
line << " [=arg(=" << defaultStr.str() << ")]";
}
return line.str();
}
inline std::string ConsoleOptionPrinter::print(const Attribute& max_attribute) const
{
if (option_parser_ == nullptr)
return "";
if (max_attribute < Attribute::optional)
throw std::invalid_argument("attribute must be 'optional', 'advanced', or 'default'");
std::stringstream s;
if (!option_parser_->description().empty())
s << option_parser_->description() << ":\n";
size_t optionRightMargin(20);
const size_t maxDescriptionLeftMargin(40);
// const size_t descriptionRightMargin(80);
for (const auto& option : option_parser_->options())
optionRightMargin = std::max(optionRightMargin, to_string(option).size() + 2);
optionRightMargin = std::min(maxDescriptionLeftMargin - 2, optionRightMargin);
for (const auto& option : option_parser_->options())
{
if ((option->attribute() <= Attribute::hidden) || (option->attribute() > max_attribute))
continue;
std::string optionStr = to_string(option);
if (optionStr.size() < optionRightMargin)
optionStr.resize(optionRightMargin, ' ');
else
optionStr += "\n" + std::string(optionRightMargin, ' ');
s << optionStr;
std::string line;
std::vector<std::string> lines;
std::stringstream description(option->description());
while (std::getline(description, line, '\n'))
lines.push_back(line);
std::string empty(optionRightMargin, ' ');
for (size_t n = 0; n < lines.size(); ++n)
{
if (n > 0)
s << "\n" << empty;
s << lines[n];
}
s << "\n";
}
return s.str();
}
/// GroffOptionPrinter implementation /////////////////////////////////
inline GroffOptionPrinter::GroffOptionPrinter(const OptionParser* option_parser) : OptionPrinter(option_parser)
{
}
inline std::string GroffOptionPrinter::to_string(Option_ptr option) const
{
std::stringstream line;
if (option->short_name() != 0)
{
line << "-" << option->short_name();
if (!option->long_name().empty())
line << ", ";
}
if (!option->long_name().empty())
line << "--" << option->long_name();
if (option->argument_type() == Argument::required)
{
line << " arg";
std::stringstream defaultStr;
if (option->get_default(defaultStr))
{
if (!defaultStr.str().empty())
line << " (=" << defaultStr.str() << ")";
}
}
else if (option->argument_type() == Argument::optional)
{
std::stringstream defaultStr;
if (option->get_default(defaultStr))
line << " [=arg(=" << defaultStr.str() << ")]";
}
return line.str();
}
inline std::string GroffOptionPrinter::print(const Attribute& max_attribute) const
{
if (option_parser_ == nullptr)
return "";
if (max_attribute < Attribute::optional)
throw std::invalid_argument("attribute must be 'optional', 'advanced', or 'default'");
std::stringstream s;
if (!option_parser_->description().empty())
s << ".SS " << option_parser_->description() << ":\n";
for (const auto& option : option_parser_->options())
{
if ((option->attribute() <= Attribute::hidden) || (option->attribute() > max_attribute))
continue;
s << ".TP\n\\fB" << to_string(option) << "\\fR\n";
if (!option->description().empty())
s << option->description() << "\n";
}
return s.str();
}
/// BashCompletionOptionPrinter implementation /////////////////////////////////
inline BashCompletionOptionPrinter::BashCompletionOptionPrinter(const OptionParser* option_parser, std::string program_name)
: OptionPrinter(option_parser), program_name_(std::move(program_name))
{
}
inline std::string BashCompletionOptionPrinter::print(const Attribute& /*max_attribute*/) const
{
if (option_parser_ == nullptr)
return "";
std::stringstream s;
s << "_" << program_name_ << "()\n";
s << R"({
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts=")";
for (const auto& option : option_parser_->options())
{
if (option->attribute() > Attribute::hidden)
{
if (option->short_name() != 0)
s << "-" << option->short_name() << " ";
if (!option->long_name().empty())
s << "--" << option->long_name() << " ";
}
}
s << R"("
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}
complete -F )";
s << "_" << program_name_ << " " << program_name_ << "\n";
return s.str();
}
static inline std::ostream& operator<<(std::ostream& out, const OptionParser& op)
{
return out << op.help();
}
} // namespace popl
#endif // POPL_HPP