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.
765 lines
21 KiB
765 lines
21 KiB
// MIT License |
|
// |
|
// Copyright (c) 2015 - 2016 Florian Rappl |
|
// |
|
// 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: |
|
// |
|
// The above copyright notice and this permission notice shall be included in all |
|
// copies or substantial portions of the Software. |
|
// |
|
// 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. |
|
|
|
/* |
|
This file is part of the C++ CmdParser utility. |
|
Copyright (c) 2015 - 2019 Florian Rappl |
|
*/ |
|
|
|
#pragma once |
|
#include <functional> |
|
#include <iostream> |
|
#include <sstream> |
|
#include <stdexcept> |
|
#include <string> |
|
#include <vector> |
|
|
|
namespace cli |
|
{ |
|
/// Class used to wrap integer types to specify desired numerical base for specific argument parsing |
|
template<typename T, int numericalBase = 0> |
|
class NumericalBase |
|
{ |
|
public: |
|
/// This constructor required for correct AgrumentCountChecker initialization |
|
NumericalBase() : value(0), base(numericalBase) {} |
|
|
|
/// This constructor required for default value initialization |
|
/// \param val comes from default value |
|
NumericalBase(T val) : value(val), base(numericalBase) {} |
|
|
|
operator T() const |
|
{ |
|
return this->value; |
|
} |
|
operator T*() |
|
{ |
|
return this->value; |
|
} |
|
|
|
T value; |
|
unsigned int base; |
|
}; |
|
|
|
struct CallbackArgs |
|
{ |
|
const std::vector<std::string>& arguments; |
|
std::ostream& output; |
|
std::ostream& error; |
|
}; |
|
class Parser |
|
{ |
|
private: |
|
class CmdBase |
|
{ |
|
public: |
|
explicit CmdBase(const std::string& name, |
|
const std::string& alternative, |
|
const std::string& description, |
|
bool required, |
|
bool dominant, |
|
bool variadic) |
|
: name(name) |
|
, command(name.size() > 0 ? "-" + name : "") |
|
, alternative(alternative.size() > 0 ? "--" + alternative : "") |
|
, description(description) |
|
, required(required) |
|
, handled(false) |
|
, arguments({}) |
|
, dominant(dominant) |
|
, variadic(variadic) |
|
{} |
|
|
|
virtual ~CmdBase() {} |
|
|
|
std::string name; |
|
std::string command; |
|
std::string alternative; |
|
std::string description; |
|
bool required; |
|
bool handled; |
|
std::vector<std::string> arguments; |
|
bool const dominant; |
|
bool const variadic; |
|
|
|
virtual std::string print_value() const = 0; |
|
virtual bool parse(std::ostream& output, std::ostream& error) = 0; |
|
|
|
bool is(const std::string& given) const |
|
{ |
|
return given == command || given == alternative; |
|
} |
|
}; |
|
|
|
template<typename T> |
|
struct ArgumentCountChecker |
|
{ |
|
static constexpr bool Variadic = false; |
|
}; |
|
|
|
template<typename T> |
|
struct ArgumentCountChecker<cli::NumericalBase<T>> |
|
{ |
|
static constexpr bool Variadic = false; |
|
}; |
|
|
|
template<typename T> |
|
struct ArgumentCountChecker<std::vector<T>> |
|
{ |
|
static constexpr bool Variadic = true; |
|
}; |
|
|
|
template<typename T> |
|
class CmdFunction final : public CmdBase |
|
{ |
|
public: |
|
explicit CmdFunction(const std::string& name, |
|
const std::string& alternative, |
|
const std::string& description, |
|
bool required, |
|
bool dominant) |
|
: CmdBase(name, |
|
alternative, |
|
description, |
|
required, |
|
dominant, |
|
ArgumentCountChecker<T>::Variadic) |
|
{} |
|
|
|
virtual bool parse(std::ostream& output, std::ostream& error) |
|
{ |
|
try |
|
{ |
|
CallbackArgs args{arguments, output, error}; |
|
value = callback(args); |
|
return true; |
|
} |
|
catch(...) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
virtual std::string print_value() const |
|
{ |
|
return ""; |
|
} |
|
|
|
std::function<T(CallbackArgs&)> callback; |
|
T value; |
|
}; |
|
|
|
template<typename T> |
|
class CmdArgument final : public CmdBase |
|
{ |
|
public: |
|
explicit CmdArgument(const std::string& name, |
|
const std::string& alternative, |
|
const std::string& description, |
|
bool required, |
|
bool dominant) |
|
: CmdBase(name, |
|
alternative, |
|
description, |
|
required, |
|
dominant, |
|
ArgumentCountChecker<T>::Variadic) |
|
{} |
|
|
|
virtual bool parse(std::ostream&, std::ostream&) |
|
{ |
|
try |
|
{ |
|
value = Parser::parse(arguments, value); |
|
return true; |
|
} |
|
catch(...) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
virtual std::string print_value() const |
|
{ |
|
return stringify(value); |
|
} |
|
|
|
T value; |
|
}; |
|
|
|
static int parse(const std::vector<std::string>& elements, const int&, int numberBase = 0) |
|
{ |
|
if(elements.size() != 1) |
|
throw std::bad_cast(); |
|
|
|
return std::stoi(elements[0], 0, numberBase); |
|
} |
|
|
|
static bool parse(const std::vector<std::string>& elements, const bool& defval) |
|
{ |
|
if(elements.size() != 0) |
|
throw std::runtime_error("A boolean command line parameter cannot have any arguments."); |
|
|
|
return !defval; |
|
} |
|
|
|
static double parse(const std::vector<std::string>& elements, const double&) |
|
{ |
|
if(elements.size() != 1) |
|
throw std::bad_cast(); |
|
|
|
return std::stod(elements[0]); |
|
} |
|
|
|
static float parse(const std::vector<std::string>& elements, const float&) |
|
{ |
|
if(elements.size() != 1) |
|
throw std::bad_cast(); |
|
|
|
return std::stof(elements[0]); |
|
} |
|
|
|
static long double parse(const std::vector<std::string>& elements, const long double&) |
|
{ |
|
if(elements.size() != 1) |
|
throw std::bad_cast(); |
|
|
|
return std::stold(elements[0]); |
|
} |
|
|
|
static unsigned int |
|
parse(const std::vector<std::string>& elements, const unsigned int&, int numberBase = 0) |
|
{ |
|
if(elements.size() != 1) |
|
throw std::bad_cast(); |
|
|
|
return static_cast<unsigned int>(std::stoul(elements[0], 0, numberBase)); |
|
} |
|
|
|
static unsigned long |
|
parse(const std::vector<std::string>& elements, const unsigned long&, int numberBase = 0) |
|
{ |
|
if(elements.size() != 1) |
|
throw std::bad_cast(); |
|
|
|
return std::stoul(elements[0], 0, numberBase); |
|
} |
|
|
|
static unsigned long long parse(const std::vector<std::string>& elements, |
|
const unsigned long long&, |
|
int numberBase = 0) |
|
{ |
|
if(elements.size() != 1) |
|
throw std::bad_cast(); |
|
|
|
return std::stoull(elements[0], 0, numberBase); |
|
} |
|
|
|
static long long |
|
parse(const std::vector<std::string>& elements, const long long&, int numberBase = 0) |
|
{ |
|
if(elements.size() != 1) |
|
throw std::bad_cast(); |
|
|
|
return std::stoll(elements[0], 0, numberBase); |
|
} |
|
|
|
static long parse(const std::vector<std::string>& elements, const long&, int numberBase = 0) |
|
{ |
|
if(elements.size() != 1) |
|
throw std::bad_cast(); |
|
|
|
return std::stol(elements[0], 0, numberBase); |
|
} |
|
|
|
static std::string parse(const std::vector<std::string>& elements, const std::string&) |
|
{ |
|
if(elements.size() != 1) |
|
throw std::bad_cast(); |
|
|
|
return elements[0]; |
|
} |
|
|
|
template<class T> |
|
static std::vector<T> parse(const std::vector<std::string>& elements, const std::vector<T>&) |
|
{ |
|
const T defval = T(); |
|
std::vector<T> values{}; |
|
std::vector<std::string> buffer(1); |
|
|
|
for(const auto& element : elements) |
|
{ |
|
buffer[0] = element; |
|
values.push_back(parse(buffer, defval)); |
|
} |
|
|
|
return values; |
|
} |
|
|
|
template<typename T> |
|
static T parse(const std::vector<std::string>& elements, const NumericalBase<T>& wrapper) |
|
{ |
|
return parse(elements, wrapper.value, 0); |
|
} |
|
|
|
/// Specialization for number wrapped into numerical base |
|
/// \tparam T base type of the argument |
|
/// \tparam base numerical base |
|
/// \param elements |
|
/// \param wrapper |
|
/// \return parsed number |
|
template<typename T, int base> |
|
static T parse(const std::vector<std::string>& elements, const NumericalBase<T, base>& wrapper) |
|
{ |
|
return parse(elements, wrapper.value, wrapper.base); |
|
} |
|
|
|
template<class T> |
|
static std::string stringify(const T& value) |
|
{ |
|
return std::to_string(value); |
|
} |
|
|
|
template<class T, int base> |
|
static std::string stringify(const NumericalBase<T, base>& wrapper) |
|
{ |
|
return std::to_string(wrapper.value); |
|
} |
|
|
|
template<class T> |
|
static std::string stringify(const std::vector<T>& values) |
|
{ |
|
std::stringstream ss{}; |
|
ss << "[ "; |
|
|
|
for(const auto& value : values) |
|
{ |
|
ss << stringify(value) << " "; |
|
} |
|
|
|
ss << "]"; |
|
return ss.str(); |
|
} |
|
|
|
static std::string stringify(const std::string& str) |
|
{ |
|
return str; |
|
} |
|
|
|
public: |
|
explicit Parser(int argc, const char** argv) : _appname(argv[0]) |
|
{ |
|
for(int i = 1; i < argc; ++i) |
|
{ |
|
_arguments.push_back(argv[i]); |
|
} |
|
enable_help(); |
|
} |
|
|
|
explicit Parser(int argc, char** argv) : _appname(argv[0]) |
|
{ |
|
for(int i = 1; i < argc; ++i) |
|
{ |
|
_arguments.push_back(argv[i]); |
|
} |
|
enable_help(); |
|
} |
|
|
|
Parser(int argc, const char** argv, std::string generalProgramDescriptionForHelpText) |
|
: _appname(argv[0]), _general_help_text(std::move(generalProgramDescriptionForHelpText)) |
|
{ |
|
for(int i = 1; i < argc; ++i) |
|
{ |
|
_arguments.push_back(argv[i]); |
|
} |
|
enable_help(); |
|
} |
|
|
|
Parser(int argc, char** argv, std::string generalProgramDescriptionForHelpText) |
|
: _appname(argv[0]), _general_help_text(std::move(generalProgramDescriptionForHelpText)) |
|
{ |
|
for(int i = 1; i < argc; ++i) |
|
{ |
|
_arguments.push_back(argv[i]); |
|
} |
|
enable_help(); |
|
} |
|
|
|
~Parser() |
|
{ |
|
for(size_t i = 0, n = _commands.size(); i < n; ++i) |
|
{ |
|
delete _commands[i]; |
|
} |
|
} |
|
|
|
bool has_help() const |
|
{ |
|
for(const auto& command : _commands) |
|
{ |
|
if(command->name == "h" && command->alternative == "--help") |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void enable_help() |
|
{ |
|
set_callback("h", |
|
"help", |
|
std::function<bool(CallbackArgs&)>( |
|
[this](CallbackArgs& args) |
|
{ |
|
args.output << this->usage(); |
|
exit(0); |
|
return false; |
|
}), |
|
"", |
|
true); |
|
} |
|
|
|
void disable_help() |
|
{ |
|
for(auto command = _commands.begin(); command != _commands.end(); ++command) |
|
{ |
|
if((*command)->name == "h" && (*command)->alternative == "--help") |
|
{ |
|
_commands.erase(command); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
template<typename T> |
|
void set_default(bool is_required, const std::string& description = "") |
|
{ |
|
auto command = new CmdArgument<T>{"", "", description, is_required, false}; |
|
_commands.push_back(command); |
|
} |
|
|
|
template<typename T> |
|
void set_required(const std::string& name, |
|
const std::string& alternative, |
|
const std::string& description = "", |
|
bool dominant = false) |
|
{ |
|
auto command = new CmdArgument<T>{name, alternative, description, true, dominant}; |
|
_commands.push_back(command); |
|
} |
|
|
|
template<typename T> |
|
void set_optional(const std::string& name, |
|
const std::string& alternative, |
|
T defaultValue, |
|
const std::string& description = "", |
|
bool dominant = false) |
|
{ |
|
auto command = new CmdArgument<T>{name, alternative, description, false, dominant}; |
|
command->value = defaultValue; |
|
_commands.push_back(command); |
|
} |
|
|
|
template<typename T> |
|
void set_callback(const std::string& name, |
|
const std::string& alternative, |
|
std::function<T(CallbackArgs&)> callback, |
|
const std::string& description = "", |
|
bool dominant = false) |
|
{ |
|
auto command = new CmdFunction<T>{name, alternative, description, false, dominant}; |
|
command->callback = callback; |
|
_commands.push_back(command); |
|
} |
|
|
|
inline void run_and_exit_if_error() |
|
{ |
|
if(run() == false) |
|
{ |
|
exit(1); |
|
} |
|
} |
|
|
|
inline bool run() |
|
{ |
|
return run(std::cout, std::cerr); |
|
} |
|
|
|
inline bool run(std::ostream& output) |
|
{ |
|
return run(output, std::cerr); |
|
} |
|
|
|
bool doesArgumentExist(std::string name, std::string altName) |
|
{ |
|
for(const auto& argument : _arguments) |
|
{ |
|
|
|
if(argument == '-' + name || argument == altName) |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
inline bool doesHelpExist() |
|
{ |
|
return doesArgumentExist("h", "--help"); |
|
} |
|
|
|
bool run(std::ostream& output, std::ostream& error) |
|
{ |
|
if(_arguments.size() > 0) |
|
{ |
|
auto current = find_default(); |
|
|
|
for(size_t i = 0, n = _arguments.size(); i < n; ++i) |
|
{ |
|
auto isarg = _arguments[i].size() > 0 && _arguments[i][0] == '-'; |
|
auto associated = isarg ? find(_arguments[i]) : nullptr; |
|
|
|
if(associated != nullptr) |
|
{ |
|
current = associated; |
|
associated->handled = true; |
|
} |
|
else if(current == nullptr) |
|
{ |
|
error << no_default(); |
|
return false; |
|
} |
|
else |
|
{ |
|
current->arguments.push_back(_arguments[i]); |
|
current->handled = true; |
|
if(!current->variadic) |
|
{ |
|
// If the current command is not variadic, then no more arguments |
|
// should be added to it. In this case, switch back to the default |
|
// command. |
|
current = find_default(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// First, parse dominant arguments since they succeed even if required |
|
// arguments are missing. |
|
for(auto command : _commands) |
|
{ |
|
if(command->handled && command->dominant && !command->parse(output, error)) |
|
{ |
|
error << howto_use(command); |
|
return false; |
|
} |
|
} |
|
|
|
// Next, check for any missing arguments. |
|
for(auto command : _commands) |
|
{ |
|
if(command->required && !command->handled) |
|
{ |
|
error << howto_required(command); |
|
return false; |
|
} |
|
} |
|
|
|
// Finally, parse all remaining arguments. |
|
for(auto command : _commands) |
|
{ |
|
if(command->handled && !command->dominant && !command->parse(output, error)) |
|
{ |
|
error << howto_use(command); |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
template<typename T> |
|
T get(const std::string& name) const |
|
{ |
|
for(const auto& command : _commands) |
|
{ |
|
if(command->name == name) |
|
{ |
|
auto cmd = dynamic_cast<CmdArgument<T>*>(command); |
|
|
|
if(cmd == nullptr) |
|
{ |
|
throw std::runtime_error("Invalid usage of the parameter " + name |
|
+ " detected."); |
|
} |
|
|
|
return cmd->value; |
|
} |
|
} |
|
|
|
throw std::runtime_error("The parameter " + name + " could not be found."); |
|
} |
|
|
|
template<typename T> |
|
T get_if(const std::string& name, std::function<T(T)> callback) const |
|
{ |
|
auto value = get<T>(name); |
|
return callback(value); |
|
} |
|
|
|
int requirements() const |
|
{ |
|
int count = 0; |
|
|
|
for(const auto& command : _commands) |
|
{ |
|
if(command->required) |
|
{ |
|
++count; |
|
} |
|
} |
|
|
|
return count; |
|
} |
|
|
|
int commands() const |
|
{ |
|
return static_cast<int>(_commands.size()); |
|
} |
|
|
|
inline const std::string& app_name() const |
|
{ |
|
return _appname; |
|
} |
|
|
|
protected: |
|
CmdBase* find(const std::string& name) |
|
{ |
|
for(auto command : _commands) |
|
{ |
|
if(command->is(name)) |
|
{ |
|
return command; |
|
} |
|
} |
|
|
|
return nullptr; |
|
} |
|
|
|
CmdBase* find_default() |
|
{ |
|
for(auto command : _commands) |
|
{ |
|
if(command->name == "") |
|
{ |
|
return command; |
|
} |
|
} |
|
|
|
return nullptr; |
|
} |
|
|
|
std::string usage() const |
|
{ |
|
std::stringstream ss{}; |
|
ss << _general_help_text << "\n\n"; |
|
ss << "Available parameters:\n\n"; |
|
|
|
for(const auto& command : _commands) |
|
{ |
|
ss << " " << command->command << "\t" << command->alternative; |
|
|
|
if(command->required == true) |
|
{ |
|
ss << "\t(required)"; |
|
} |
|
|
|
ss << "\n " << command->description; |
|
|
|
if(command->required == false) |
|
{ |
|
ss << "\n " |
|
<< "This parameter is optional. The default value is '" + command->print_value() |
|
<< "'."; |
|
} |
|
|
|
ss << "\n\n"; |
|
} |
|
|
|
return ss.str(); |
|
} |
|
|
|
void print_help(std::stringstream& ss) const |
|
{ |
|
if(has_help()) |
|
{ |
|
ss << "For more help use --help or -h.\n"; |
|
} |
|
} |
|
|
|
std::string howto_required(CmdBase* command) const |
|
{ |
|
std::stringstream ss{}; |
|
ss << "The parameter " << command->name << " is required.\n"; |
|
ss << command->description << '\n'; |
|
print_help(ss); |
|
return ss.str(); |
|
} |
|
|
|
std::string howto_use(CmdBase* command) const |
|
{ |
|
std::stringstream ss{}; |
|
ss << "The parameter " << command->name << " has invalid arguments.\n"; |
|
ss << command->description << '\n'; |
|
print_help(ss); |
|
return ss.str(); |
|
} |
|
|
|
std::string no_default() const |
|
{ |
|
std::stringstream ss{}; |
|
ss << "No default parameter has been specified.\n"; |
|
ss << "The given argument must be used with a parameter.\n"; |
|
print_help(ss); |
|
return ss.str(); |
|
} |
|
|
|
const std::string& get_general_help_text() const |
|
{ |
|
return _general_help_text; |
|
} |
|
|
|
void set_general_help_text(const std::string& generalHelpText) |
|
{ |
|
_general_help_text = generalHelpText; |
|
} |
|
|
|
private: |
|
const std::string _appname; |
|
std::string _general_help_text; |
|
std::vector<std::string> _arguments; |
|
std::vector<CmdBase*> _commands; |
|
}; |
|
} // namespace cli
|
|
|