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.
 
 
 
 
 
 

768 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();
#pragma warning(push)
#pragma warning(disable : 4702)
exit(0);
return false;
#pragma warning(pop)
}),
"",
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