BLT/src/blt/parse/argparse.cpp

301 lines
10 KiB
C++
Raw Normal View History

/*
* Created by Brett on 28/07/23.
* Licensed under GNU General Public License V3.0
* See LICENSE file for license detail
*/
#include <blt/parse/argparse.h>
2023-07-29 14:04:46 -04:00
#include "blt/std/logging.h"
#include <blt/std/string.h>
namespace blt::parser {
2023-08-01 13:43:00 -04:00
arg_vector_t::arg_vector_t(const std::vector<std::string>& args) {
for (auto& arg : args)
insertAndSort(arg);
}
2023-08-01 13:43:00 -04:00
arg_vector_t::arg_vector_t(std::initializer_list<std::string> args) {
for (auto& arg : args)
insertAndSort(arg);
}
2023-08-01 13:43:00 -04:00
arg_vector_t::arg_vector_t(const std::string& arg) {
insertAndSort(arg);
}
2023-08-01 13:43:00 -04:00
arg_vector_t::arg_vector_t(const char* arg) {
insertAndSort(arg);
}
2023-08-01 13:43:00 -04:00
arg_vector_t& arg_vector_t::operator=(const std::string& arg) {
insertAndSort(arg);
return *this;
}
2023-08-01 13:43:00 -04:00
arg_vector_t& arg_vector_t::operator=(const char* arg) {
insertAndSort(arg);
return *this;
}
2023-08-01 13:43:00 -04:00
arg_vector_t& arg_vector_t::operator=(std::initializer_list<std::string>& args) {
for (auto& arg : args)
insertAndSort(arg);
return *this;
}
2023-08-01 13:43:00 -04:00
arg_vector_t& arg_vector_t::operator=(std::vector<std::string>& args) {
for (auto& arg : args)
insertAndSort(arg);
return *this;
}
2023-08-01 13:43:00 -04:00
void arg_vector_t::insertAndSort(const std::string& str) {
if (str.starts_with('-'))
flags.push_back(str);
else
names.push_back(str);
}
2023-08-01 13:43:00 -04:00
arg_nargs_t::arg_nargs_t(char c) {
decode(c);
}
2023-08-01 13:43:00 -04:00
arg_nargs_t::arg_nargs_t(std::string s) {
decode(s[0]);
}
2023-08-01 13:43:00 -04:00
arg_nargs_t::arg_nargs_t(const char* s) {
decode(*s);
}
2023-08-01 13:43:00 -04:00
arg_nargs_t& arg_nargs_t::operator=(const std::string& s) {
decode(s[0]);
return *this;
}
2023-08-01 13:43:00 -04:00
arg_nargs_t& arg_nargs_t::operator=(char c) {
decode(c);
return *this;
}
2023-08-01 13:43:00 -04:00
arg_nargs_t& arg_nargs_t::operator=(int a) {
args = a;
return *this;
}
2023-08-01 13:43:00 -04:00
arg_nargs_t& arg_nargs_t::operator=(const char* s) {
decode(*s);
return *this;
}
2023-08-01 13:43:00 -04:00
void arg_nargs_t::decode(char c) {
if (c == '?')
2023-08-01 13:43:00 -04:00
flags = UNKNOWN;
else if (c == '+')
flags = ALL_REQUIRED;
else if (c == '*')
flags = ALL;
else
flags = 0;
}
2023-07-29 13:38:19 -04:00
2023-08-01 13:43:00 -04:00
arg_tokenizer_t::arg_tokenizer_t(size_t argc, const char** argv) {
2023-07-29 13:38:19 -04:00
for (size_t i = 0; i < argc; i++)
args.emplace_back(argv[i]);
}
2023-08-01 13:43:00 -04:00
bool argparse::validateArgument(const arg_properties_t& args) {
2023-07-29 14:04:46 -04:00
return !args.a_flags.getFlags().empty() ^ !args.a_flags.getNames().empty();
}
2023-07-29 13:38:19 -04:00
2023-08-01 13:43:00 -04:00
void argparse::addArgument(const arg_properties_t& args) {
2023-07-29 14:04:46 -04:00
if (!validateArgument(args)) {
BLT_WARN("Argument contains both flags and positional names, this is not allowed!");
BLT_WARN("(Discarding argument)");
return;
}
// store one copy of the properties (arg properties could in theory be very large!)
2023-08-01 13:43:00 -04:00
auto pos = user_args.arg_storage.size();
user_args.arg_storage.push_back(args);
auto& arg = user_args.arg_storage[pos];
// associate the arg properties per name and flag to allow for quick access when parsing
auto& names = args.a_flags.getNames();
for (const auto& name : names)
2023-08-01 13:43:00 -04:00
user_args.name_associations.emplace_back(name, &arg);
auto& flags = args.a_flags.getFlags();
for (const auto& flag : flags)
2023-08-01 13:43:00 -04:00
user_args.flag_associations[flag] = &arg;
if (args.a_required)
user_args.required_vars.insert(args.a_flags);
2023-08-01 13:58:08 -04:00
if (args.a_nargs.flags == arg_nargs_t::UNKNOWN) {
if (args.a_flags.getNames().empty())
loaded_args.flag_args[args.a_flags] = args.a_default;
else
loaded_args.positional_args[args.a_flags] = args.a_default;
}
}
const argparse::arg_results& argparse::parse_args(int argc, const char** argv) {
loaded_args = {};
2023-08-01 13:43:00 -04:00
arg_tokenizer_t arg_tokenizer(argc, argv);
loaded_args.program_name = arg_tokenizer.next();
BLT_TRACE("Loading args for %s", loaded_args.program_name.c_str());
2023-08-01 13:58:08 -04:00
size_t last_positional = 0;
2023-08-01 13:43:00 -04:00
while (arg_tokenizer.hasNext()) {
// a token isn't a flag it must be a positional arg as flags will consume nargs
if (!arg_tokenizer.isFlag()){
handlePositionalArgument(arg_tokenizer, last_positional);
2023-07-31 13:53:10 -04:00
continue;
}
2023-08-01 13:43:00 -04:00
handleFlagArgument(arg_tokenizer);
2023-07-31 13:53:10 -04:00
}
return loaded_args;
2023-07-29 13:38:19 -04:00
}
2023-07-29 14:04:46 -04:00
2023-08-01 13:43:00 -04:00
void argparse::printHelp() {
}
void argparse::handlePositionalArgument(arg_tokenizer_t& arg_tokenizer, size_t& last_pos) {
// TODO:
loaded_args.positional_args[user_args.name_associations[last_pos++].first] = arg_tokenizer.next();
}
void argparse::handleFlagArgument(arg_tokenizer_t& arg_tokenizer) {
// token is a flag, find out special information about it
auto flag = arg_tokenizer.next();
if (flag.starts_with("--"))
processFlag(arg_tokenizer, flag);
else {
// handle special args like -vvv
if (!flag.starts_with('-'))
BLT_TRACE("Flag processed but does not start with '-'!");
// size without -
auto len = flag.size()-1;
// get flag type
std::string str = "- ";
str[1] = flag[1];
for (size_t i = 0; i < len; i++)
processFlag(arg_tokenizer, str);
}
}
void argparse::processFlag(arg_tokenizer_t& arg_tokenizer, const std::string& flag) {
2023-08-01 13:43:00 -04:00
auto loc = user_args.flag_associations.find(flag);
if (loc == user_args.flag_associations.end()){
BLT_WARN("Flag '%s' not an option!", flag.c_str());
2023-08-01 13:43:00 -04:00
printHelp();
return;
}
auto flag_properties = loc->second;
2023-08-01 13:58:08 -04:00
auto dest = flag_properties->a_dest.empty() ? flag_properties->a_flags : arg_vector_t{flag_properties->a_dest};
2023-08-01 13:43:00 -04:00
arg_data_t& data = loaded_args.flag_args[dest];
2023-08-01 13:43:00 -04:00
switch(flag_properties->a_action){
case arg_action_t::HELP:
printHelp();
break;
case arg_action_t::STORE: {
std::vector<std::string> v;
if (!consumeArguments(arg_tokenizer, *flag_properties, v))
return;
if (v.size() == 1)
data = v[0];
else
data = v;
2023-08-01 13:43:00 -04:00
break;
}
2023-08-01 13:43:00 -04:00
case arg_action_t::STORE_CONST:
data = flag_properties->a_const;
break;
case arg_action_t::STORE_FALSE:
data = false;
break;
case arg_action_t::STORE_TRUE:
data = true;
break;
case arg_action_t::COUNT: {
if (!std::holds_alternative<int32_t>(data))
data = 0;
data = std::get<int32_t>(data) + 1;
2023-08-01 13:43:00 -04:00
break;
}
case arg_action_t::EXTEND: {
2023-08-01 13:43:00 -04:00
break;
}
case arg_action_t::VERSION: {
auto file = filename(loaded_args.program_name);
BLT_INFO("%s, %s", file.c_str(), flag_properties->a_version.c_str());
2023-08-01 13:43:00 -04:00
break;
}
case arg_action_t::APPEND_CONST: {
if (!holds_alternative<std::vector<std::string>>(data)) {
data = std::vector<std::string>();
}
auto& l = get<std::vector<std::string>>(data);
l.emplace_back(flag_properties->a_const);
2023-08-01 13:43:00 -04:00
break;
}
case arg_action_t::APPEND: {
if (!holds_alternative<std::vector<std::string>>(data))
data = std::vector<std::string>();
auto& l = get<std::vector<std::string>>(data);
consumeArguments(arg_tokenizer, *flag_properties, l);
2023-08-01 13:43:00 -04:00
break;
}
2023-08-01 13:43:00 -04:00
}
}
2023-08-01 13:58:08 -04:00
bool argparse::consumeArguments(arg_tokenizer_t& arg_tokenizer, const arg_properties_t& properties, std::vector<std::string>& v) {
switch (properties.a_nargs.flags) {
case 0:
for (int i = 0; i < properties.a_nargs.args; i++) {
if (arg_tokenizer.isFlag()) {
BLT_WARN("Expected %d arguments, found flag instead!", properties.a_nargs.args);
2023-08-01 13:58:08 -04:00
return false;
}
v.emplace_back(arg_tokenizer.next());
}
return true;
case arg_nargs_t::UNKNOWN:
// no arg next
if (arg_tokenizer.isFlag()) {
if (!properties.a_const.empty())
v.emplace_back(properties.a_const);
return true;
}
v.emplace_back(arg_tokenizer.next());
return true;
case arg_nargs_t::ALL:
while (arg_tokenizer.hasNext() && !arg_tokenizer.isFlag())
v.emplace_back(arg_tokenizer.next());
return true;
2023-08-01 13:58:08 -04:00
case arg_nargs_t::ALL_REQUIRED:
if (arg_tokenizer.isFlag()) {
BLT_WARN("At least one argument is required!");
return false;
}
while (arg_tokenizer.hasNext() && !arg_tokenizer.isFlag())
v.emplace_back(arg_tokenizer.next());
return true;
2023-08-01 13:58:08 -04:00
}
return false;
}
std::string argparse::filename(const std::string& path) {
auto paths = blt::string::split(path, "/");
auto final = paths[paths.size()-1];
if (final == "/")
return paths[paths.size()-2];
return final;
}
2023-07-29 14:04:46 -04:00
}