mirror of
https://github.com/EQEmu/Server.git
synced 2026-04-02 08:12:25 +00:00
- License was intended to be GPLv3 per earlier commit of GPLv3 LICENSE FILE - This is confirmed by the inclusion of libraries that are incompatible with GPLv2 - This is also confirmed by KLS and the agreement of KLS's predecessors - Added GPLv3 license headers to the compilable source files - Removed Folly licensing in strings.h since the string functions do not match the Folly functions and are standard functions - this must have been left over from previous implementations - Removed individual contributor license headers since the project has been under the "developer" mantle for many years - Removed comments on files that were previously automatically generated since they've been manually modified multiple times and there are no automatic scripts referencing them (removed in 2023)
451 lines
14 KiB
C++
451 lines
14 KiB
C++
/* EQEmu: EQEmulator
|
|
|
|
Copyright (C) 2001-2026 EQEmu Development Team
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <set>
|
|
#include <map>
|
|
#include <cassert>
|
|
|
|
namespace argh
|
|
{
|
|
// Terminology:
|
|
// A command line is composed of 2 types of args:
|
|
// 1. Positional args, i.e. free standing values
|
|
// 2. Options: args beginning with '-'. We identify two kinds:
|
|
// 2.1: Flags: boolean options => (exist ? true : false)
|
|
// 2.2: Parameters: a name followed by a non-option value
|
|
|
|
#if !defined(__GNUC__) || (__GNUC__ >= 5)
|
|
using string_stream = std::istringstream;
|
|
#else
|
|
// Until GCC 5, istringstream did not have a move constructor.
|
|
// stringstream_proxy is used instead, as a workaround.
|
|
class stringstream_proxy
|
|
{
|
|
public:
|
|
stringstream_proxy() = default;
|
|
|
|
// Construct with a value.
|
|
stringstream_proxy(std::string const& value) :
|
|
stream_(value)
|
|
{}
|
|
|
|
// Copy constructor.
|
|
stringstream_proxy(const stringstream_proxy& other) :
|
|
stream_(other.stream_.str())
|
|
{
|
|
stream_.setstate(other.stream_.rdstate());
|
|
}
|
|
|
|
void setstate(std::ios_base::iostate state) { stream_.setstate(state); }
|
|
|
|
// Stream out the value of the parameter.
|
|
// If the conversion was not possible, the stream will enter the fail state,
|
|
// and operator bool will return false.
|
|
template<typename T>
|
|
stringstream_proxy& operator >> (T& thing)
|
|
{
|
|
stream_ >> thing;
|
|
return *this;
|
|
}
|
|
|
|
|
|
// Get the string value.
|
|
std::string str() const { return stream_.str(); }
|
|
|
|
std::stringbuf* rdbuf() const { return stream_.rdbuf(); }
|
|
|
|
// Check the state of the stream.
|
|
// False when the most recent stream operation failed
|
|
operator bool() const { return !!stream_; }
|
|
|
|
~stringstream_proxy() = default;
|
|
private:
|
|
std::istringstream stream_;
|
|
};
|
|
using string_stream = stringstream_proxy;
|
|
#endif
|
|
|
|
class parser
|
|
{
|
|
public:
|
|
enum Mode { PREFER_FLAG_FOR_UNREG_OPTION = 1 << 0,
|
|
PREFER_PARAM_FOR_UNREG_OPTION = 1 << 1,
|
|
NO_SPLIT_ON_EQUALSIGN = 1 << 2,
|
|
SINGLE_DASH_IS_MULTIFLAG = 1 << 3,
|
|
};
|
|
|
|
parser() = default;
|
|
|
|
parser(std::initializer_list<char const* const> pre_reg_names)
|
|
{ add_params(pre_reg_names); }
|
|
|
|
parser(const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION)
|
|
{ parse(argv, mode); }
|
|
|
|
parser(int argc, const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION)
|
|
{ parse(argc, argv, mode); }
|
|
|
|
void add_param(std::string const& name);
|
|
void add_params(std::initializer_list<char const* const> init_list);
|
|
|
|
void parse(const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION);
|
|
void parse(int argc, const char* const argv[], int mode = PREFER_FLAG_FOR_UNREG_OPTION);
|
|
|
|
std::multiset<std::string> const& flags() const { return flags_; }
|
|
std::map<std::string, std::string> const& params() const { return params_; }
|
|
std::vector<std::string> const& pos_args() const { return pos_args_; }
|
|
|
|
// begin() and end() for using range-for over positional args.
|
|
std::vector<std::string>::const_iterator begin() const { return pos_args_.cbegin(); }
|
|
std::vector<std::string>::const_iterator end() const { return pos_args_.cend(); }
|
|
size_t size() const { return pos_args_.size(); }
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Accessors
|
|
|
|
// flag (boolean) accessors: return true if the flag appeared, otherwise false.
|
|
bool operator[](std::string const& name) const;
|
|
|
|
// multiple flag (boolean) accessors: return true if at least one of the flag appeared, otherwise false.
|
|
bool operator[](std::initializer_list<char const* const> init_list) const;
|
|
|
|
// returns positional arg string by order. Like argv[] but without the options
|
|
std::string const& operator[](size_t ind) const;
|
|
|
|
// returns a std::istream that can be used to convert a positional arg to a typed value.
|
|
string_stream operator()(size_t ind) const;
|
|
|
|
// same as above, but with a default value in case the arg is missing (index out of range).
|
|
template<typename T>
|
|
string_stream operator()(size_t ind, T&& def_val) const;
|
|
|
|
// parameter accessors, give a name get an std::istream that can be used to convert to a typed value.
|
|
// call .str() on result to get as string
|
|
string_stream operator()(std::string const& name) const;
|
|
|
|
// accessor for a parameter with multiple names, give a list of names, get an std::istream that can be used to convert to a typed value.
|
|
// call .str() on result to get as string
|
|
// returns the first value in the list to be found.
|
|
string_stream operator()(std::initializer_list<char const* const> init_list) const;
|
|
|
|
// same as above, but with a default value in case the param was missing.
|
|
// Non-string def_val types must have an operator<<() (output stream operator)
|
|
// If T only has an input stream operator, pass the string version of the type as in "3" instead of 3.
|
|
template<typename T>
|
|
string_stream operator()(std::string const& name, T&& def_val) const;
|
|
|
|
// same as above but for a list of names. returns the first value to be found.
|
|
template<typename T>
|
|
string_stream operator()(std::initializer_list<char const* const> init_list, T&& def_val) const;
|
|
|
|
private:
|
|
string_stream bad_stream() const;
|
|
std::string trim_leading_dashes(std::string const& name) const;
|
|
bool is_number(std::string const& arg) const;
|
|
bool is_option(std::string const& arg) const;
|
|
bool got_flag(std::string const& name) const;
|
|
bool is_param(std::string const& name) const;
|
|
|
|
private:
|
|
std::vector<std::string> args_;
|
|
std::map<std::string, std::string> params_;
|
|
std::vector<std::string> pos_args_;
|
|
std::multiset<std::string> flags_;
|
|
std::set<std::string> registeredParams_;
|
|
std::string empty_;
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline void parser::parse(const char * const argv[], int mode)
|
|
{
|
|
int argc = 0;
|
|
for (auto argvp = argv; *argvp; ++argc, ++argvp);
|
|
parse(argc, argv, mode);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline void parser::parse(int argc, const char* const argv[], int mode /*= PREFER_FLAG_FOR_UNREG_OPTION*/)
|
|
{
|
|
// convert to strings
|
|
args_.resize(argc);
|
|
std::transform(argv, argv + argc, args_.begin(), [](const char* const arg) { return arg; });
|
|
|
|
// parse line
|
|
for (auto i = 0u; i < args_.size(); ++i)
|
|
{
|
|
if (!is_option(args_[i]))
|
|
{
|
|
pos_args_.emplace_back(args_[i]);
|
|
continue;
|
|
}
|
|
|
|
auto name = trim_leading_dashes(args_[i]);
|
|
|
|
if (!(mode & NO_SPLIT_ON_EQUALSIGN))
|
|
{
|
|
auto equalPos = name.find('=');
|
|
if (equalPos != std::string::npos)
|
|
{
|
|
params_.insert({ name.substr(0, equalPos), name.substr(equalPos + 1) });
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// if the option is unregistered and should be a multi-flag
|
|
if (1 == (args_[i].size() - name.size()) && // single dash
|
|
argh::parser::SINGLE_DASH_IS_MULTIFLAG & mode && // multi-flag mode
|
|
!is_param(name)) // unregistered
|
|
{
|
|
std::string keep_param;
|
|
|
|
if (!name.empty() && is_param(std::string(1ul, name.back()))) // last char is param
|
|
{
|
|
keep_param += name.back();
|
|
name.resize(name.size() - 1);
|
|
}
|
|
|
|
for (auto const& c : name)
|
|
{
|
|
flags_.emplace(std::string{ c });
|
|
}
|
|
|
|
if (!keep_param.empty())
|
|
{
|
|
name = keep_param;
|
|
}
|
|
else
|
|
{
|
|
continue; // do not consider other options for this arg
|
|
}
|
|
}
|
|
|
|
// any potential option will get as its value the next arg, unless that arg is an option too
|
|
// in that case it will be determined a flag.
|
|
if (i == args_.size() - 1 || is_option(args_[i + 1]))
|
|
{
|
|
flags_.emplace(name);
|
|
continue;
|
|
}
|
|
|
|
// if 'name' is a pre-registered option, then the next arg cannot be a free parameter to it is skipped
|
|
// otherwise we have 2 modes:
|
|
// PREFER_FLAG_FOR_UNREG_OPTION: a non-registered 'name' is determined a flag.
|
|
// The following value (the next arg) will be a free parameter.
|
|
//
|
|
// PREFER_PARAM_FOR_UNREG_OPTION: a non-registered 'name' is determined a parameter, the next arg
|
|
// will be the value of that option.
|
|
|
|
assert(!(mode & argh::parser::PREFER_FLAG_FOR_UNREG_OPTION)
|
|
|| !(mode & argh::parser::PREFER_PARAM_FOR_UNREG_OPTION));
|
|
|
|
bool preferParam = mode & argh::parser::PREFER_PARAM_FOR_UNREG_OPTION;
|
|
|
|
if (is_param(name) || preferParam)
|
|
{
|
|
params_.insert({ name, args_[i + 1] });
|
|
++i; // skip next value, it is not a free parameter
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
flags_.emplace(name);
|
|
}
|
|
};
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline string_stream parser::bad_stream() const
|
|
{
|
|
string_stream bad;
|
|
bad.setstate(std::ios_base::failbit);
|
|
return bad;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline bool parser::is_number(std::string const& arg) const
|
|
{
|
|
// inefficient but simple way to determine if a string is a number (which can start with a '-')
|
|
std::istringstream istr(arg);
|
|
double number;
|
|
istr >> number;
|
|
return !(istr.fail() || istr.bad());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline bool parser::is_option(std::string const& arg) const
|
|
{
|
|
assert(0 != arg.size());
|
|
if (is_number(arg))
|
|
return false;
|
|
return '-' == arg[0];
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline std::string parser::trim_leading_dashes(std::string const& name) const
|
|
{
|
|
auto pos = name.find_first_not_of('-');
|
|
return std::string::npos != pos ? name.substr(pos) : name;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline bool argh::parser::got_flag(std::string const& name) const
|
|
{
|
|
return flags_.end() != flags_.find(trim_leading_dashes(name));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline bool argh::parser::is_param(std::string const& name) const
|
|
{
|
|
return registeredParams_.count(name);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline bool parser::operator[](std::string const& name) const
|
|
{
|
|
return got_flag(name);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline bool parser::operator[](std::initializer_list<char const* const> init_list) const
|
|
{
|
|
return std::any_of(init_list.begin(), init_list.end(), [&](char const* const name) { return got_flag(name); });
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline std::string const& parser::operator[](size_t ind) const
|
|
{
|
|
if (ind < pos_args_.size())
|
|
return pos_args_[ind];
|
|
return empty_;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline string_stream parser::operator()(std::string const& name) const
|
|
{
|
|
auto optIt = params_.find(trim_leading_dashes(name));
|
|
if (params_.end() != optIt)
|
|
return string_stream(optIt->second);
|
|
return bad_stream();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline string_stream parser::operator()(std::initializer_list<char const* const> init_list) const
|
|
{
|
|
for (auto& name : init_list)
|
|
{
|
|
auto optIt = params_.find(trim_leading_dashes(name));
|
|
if (params_.end() != optIt)
|
|
return string_stream(optIt->second);
|
|
}
|
|
return bad_stream();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
template<typename T>
|
|
string_stream parser::operator()(std::string const& name, T&& def_val) const
|
|
{
|
|
auto optIt = params_.find(trim_leading_dashes(name));
|
|
if (params_.end() != optIt)
|
|
return string_stream(optIt->second);
|
|
|
|
std::ostringstream ostr;
|
|
ostr.precision(std::numeric_limits<long double>::max_digits10);
|
|
ostr << def_val;
|
|
return string_stream(ostr.str()); // use default
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// same as above but for a list of names. returns the first value to be found.
|
|
template<typename T>
|
|
string_stream parser::operator()(std::initializer_list<char const* const> init_list, T&& def_val) const
|
|
{
|
|
for (auto& name : init_list)
|
|
{
|
|
auto optIt = params_.find(trim_leading_dashes(name));
|
|
if (params_.end() != optIt)
|
|
return string_stream(optIt->second);
|
|
}
|
|
std::ostringstream ostr;
|
|
ostr.precision(std::numeric_limits<long double>::max_digits10);
|
|
ostr << def_val;
|
|
return string_stream(ostr.str()); // use default
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline string_stream parser::operator()(size_t ind) const
|
|
{
|
|
if (pos_args_.size() <= ind)
|
|
return bad_stream();
|
|
|
|
return string_stream(pos_args_[ind]);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
template<typename T>
|
|
string_stream parser::operator()(size_t ind, T&& def_val) const
|
|
{
|
|
if (pos_args_.size() <= ind)
|
|
{
|
|
std::ostringstream ostr;
|
|
ostr.precision(std::numeric_limits<long double>::max_digits10);
|
|
ostr << def_val;
|
|
return string_stream(ostr.str());
|
|
}
|
|
|
|
return string_stream(pos_args_[ind]);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline void parser::add_param(std::string const& name)
|
|
{
|
|
registeredParams_.insert(trim_leading_dashes(name));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
inline void parser::add_params(std::initializer_list<char const* const> init_list)
|
|
{
|
|
for (auto& name : init_list)
|
|
registeredParams_.insert(trim_leading_dashes(name));
|
|
}
|
|
} |