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
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
|