#pragma once
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <iostream>
#include <string>
#include <variant>
#include <vector>
class CommandLine
{
public:
// the possible variables the options may point to
typedef std::variant<uint32_t*, bool*, std::string*> Value;
struct Argument
{
std::vector<std::string> options_;
Value value_;
std::string desc_;
};
public:
explicit CommandLine() = default;
// adds a possible option
void Add(std::vector<std::string> const& opts, Value const& value, std::string const& desc)
{
args_.emplace_back( Argument{ opts, value, desc } );
}
void PrintHelp(std::ostream& os = std::cout) const
{
const auto maxOptionLength = MaxOptionsLength();
for (auto const& arg : args_)
{
std::string opts;
for (auto const& opt : arg.options_)
opts += opt + ", ";
// Remove last comma and space and add padding for alignment
std::stringstream sstr;
sstr << std::left << std::setw(maxOptionLength) << opts.substr(0, opts.size() - 2);
// Print each argument. Do line wrapping for long descriptions.
size_t spacePos = 0;
size_t lineWidth = 0;
while (spacePos != std::string::npos)
{
size_t nextspacePos = arg.desc_.find_first_of(' ', spacePos + 1);
sstr << arg.desc_.substr(spacePos, nextspacePos - spacePos);
lineWidth += nextspacePos - spacePos;
spacePos = nextspacePos;
if (lineWidth > 60)
{
os << sstr.str() << std::endl;
sstr = std::stringstream();
sstr << std::left << std::setw(maxOptionLength - 1) << " ";
lineWidth = 0;
}
}
}
}
// if an option is set multiple times, the last one will be finally used.
// Unknown flags will cause a warning on std::cerr.
void Parse(int argc, char* argv[]) const
{
// Skip the first argument (name of the program).
int i = 1;
while (i < argc)
{
// First we have to identify whether the value is separated by a space or a '='.
std::string flag(argv[i]);
std::string value;
bool valueIsSeparate = false;
// If there is an '=' in the flag, the part after the '=' is actually the value.
size_t equalPos = flag.find('=');
if (equalPos != std::string::npos)
{
value = flag.substr(equalPos + 1);
flag = flag.substr(0, equalPos);
}
else if (i + 1 < argc) // Else the following argument is the value.
{
value = argv[i + 1];
valueIsSeparate = true;
}
// Search for an argument with the provided flag.
bool foundArgument = false;
for (auto const& argument : args_)
{
if (std::find(argument.options_.begin(), argument.options_.end(), flag) != std::end(argument.options_))
{
foundArgument = true;
// In the case of booleans, there must not be a value present
if (std::holds_alternative<bool*>(argument.value_))
{
if (!value.empty() && value != "true" && value != "false")
{
valueIsSeparate = false;
}
*std::get<bool*>(argument.value_) = (value != "false");
}
// In all other cases there must be a value.
else if (value.empty())
{
throw std::runtime_error("Failed to parse command line arguments: Missing value for argument \"" + flag + "\"!");
}
// For a std::string, we take the entire value.
else if (std::holds_alternative<std::string*>(argument.value_))
{
*std::get<std::string*>(argument.value_) = value;
}
// In all other cases we use a std::stringstream to convert the value.
else
{
std::visit([&value](auto&& arg) {
std::stringstream sstr(value);
sstr >> *arg;
}, argument.value_);
}
break;
}
}
// Print a warning if there was an unknown argument.
if (!foundArgument)
std::cerr << "Ignoring unknown command line argument \"" << flag << "\"." << std::endl;
// Advance to the next flag.
++i;
// If the value was separated, we have to advance our index once more.
if (foundArgument && valueIsSeparate)
++i;
}
}
private:
// for alignment : Find the argument with the longest combined option length
uint32_t MaxOptionsLength(void) const
{
constexpr auto COMMA_LENGTH = 1U;
constexpr auto SPACE_LENTH = 1U;
uint32_t max_len = 0;
for (auto const& arg : args_)
{
uint32_t len = 0;
for (auto const& opt : arg.options_)
{
len += static_cast<uint32_t>(opt.size()) + COMMA_LENGTH + SPACE_LENTH;
}
max_len = std::max(max_len, len);
}
return max_len;
}
private:
std::vector<Argument> args_;
使用
public:
void Test(int argc, char* argv[]) const
{
std::string usr = "";
std::string pwd = "";
std::string code = "";
bool do_auth = false;
bool print_help = false;
CommandLine args;
args.Add({ "-u", "--user" }, &usr, "<apple ID>");
args.Add({ "-p", "--password" }, &pwd, "<password>");
args.Add({ "-c", "--code" }, &code, "security code");
args.Add({ "-a", "--auth" }, &do_auth, "authenticate");
args.Add({ "-h", "--help" }, &print_help, "Help");
try
{
args.Parse(argc, argv);
}
catch (std::runtime_error const& e)
{
std::cout << e.what() << std::endl;
return;
}
if (print_help)
{
args.PrintHelp();
return;
}
std::cout
<< "usr : " << usr << std::endl
<< "pwd : " << pwd << std::endl
<< "code : " << code << std::endl
<< "auth: " << do_auth << std::endl
<< "print help : " << print_help << std::endl;
}
};