2023-07-29 02:03:28 -04:00
|
|
|
/*
|
2023-08-07 17:42:59 -04:00
|
|
|
* Created by Brett on 06/08/23.
|
2023-07-29 02:03:28 -04:00
|
|
|
* Licensed under GNU General Public License V3.0
|
|
|
|
* See LICENSE file for license detail
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef BLT_TESTS_ARGPARSE_H
|
|
|
|
#define BLT_TESTS_ARGPARSE_H
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
#include <utility>
|
2023-07-29 02:03:28 -04:00
|
|
|
#include <vector>
|
|
|
|
#include <string>
|
|
|
|
#include <initializer_list>
|
|
|
|
#include <optional>
|
2023-07-29 17:41:45 -04:00
|
|
|
#include <blt/std/hashmap.h>
|
2023-08-01 13:43:00 -04:00
|
|
|
#include <variant>
|
2023-08-09 21:48:30 -04:00
|
|
|
#include <algorithm>
|
2023-07-29 02:03:28 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
namespace blt
|
|
|
|
{
|
|
|
|
typedef std::variant<std::string, bool, int32_t> arg_data_internal_t;
|
|
|
|
typedef std::vector<arg_data_internal_t> arg_data_vec_t;
|
|
|
|
typedef std::variant<arg_data_internal_t, arg_data_vec_t> arg_data_t;
|
2023-07-29 13:38:19 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
enum class arg_action_t
|
|
|
|
{
|
2023-07-29 13:38:19 -04:00
|
|
|
STORE,
|
|
|
|
STORE_CONST,
|
|
|
|
STORE_TRUE,
|
|
|
|
STORE_FALSE,
|
|
|
|
APPEND,
|
|
|
|
APPEND_CONST,
|
|
|
|
COUNT,
|
|
|
|
HELP,
|
|
|
|
VERSION,
|
|
|
|
EXTEND
|
2023-07-29 02:03:28 -04:00
|
|
|
};
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
class arg_vector_t
|
|
|
|
{
|
|
|
|
friend class arg_parse;
|
|
|
|
|
2023-07-29 02:03:28 -04:00
|
|
|
private:
|
|
|
|
std::vector<std::string> flags;
|
2023-08-07 17:42:59 -04:00
|
|
|
std::string name;
|
2023-07-29 02:03:28 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
void validateFlags();
|
|
|
|
|
2023-07-29 02:03:28 -04:00
|
|
|
public:
|
2023-08-07 17:42:59 -04:00
|
|
|
arg_vector_t(std::vector<std::string> flags): flags(std::move(flags))
|
|
|
|
{
|
|
|
|
validateFlags();
|
2023-07-29 02:03:28 -04:00
|
|
|
}
|
2023-07-29 13:38:19 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
arg_vector_t(std::initializer_list<std::string> flags): flags(flags)
|
|
|
|
{
|
|
|
|
validateFlags();
|
2023-07-29 02:03:28 -04:00
|
|
|
}
|
2023-07-29 14:04:46 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
arg_vector_t(const char* str);
|
2023-08-08 14:06:19 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
arg_vector_t(const std::string& str);
|
2023-08-01 13:43:00 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
[[nodiscard]] inline bool isFlag() const
|
|
|
|
{
|
|
|
|
return !flags.empty();
|
2023-08-07 16:12:43 -04:00
|
|
|
}
|
2023-08-01 13:43:00 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
[[nodiscard]] inline bool contains(const std::string& str)
|
|
|
|
{
|
|
|
|
return std::any_of(
|
|
|
|
flags.begin(), flags.end(), [&str](const std::string& flag) -> bool {
|
|
|
|
return flag == str;
|
|
|
|
}
|
|
|
|
) || str == name;
|
2023-08-07 16:12:43 -04:00
|
|
|
}
|
2023-08-08 14:06:19 -04:00
|
|
|
|
|
|
|
// returns the first flag that starts with '--' otherwise return the first '-' flag
|
|
|
|
[[nodiscard]] std::string getFirstFullFlag();
|
2023-07-29 02:03:28 -04:00
|
|
|
};
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
class arg_nargs_t
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
friend class arg_parse;
|
2023-08-07 17:42:59 -04:00
|
|
|
|
|
|
|
private:
|
2023-07-29 02:03:28 -04:00
|
|
|
static constexpr int UNKNOWN = 0x1;
|
|
|
|
static constexpr int ALL = 0x2;
|
|
|
|
static constexpr int ALL_REQUIRED = 0x4;
|
2023-08-07 16:12:43 -04:00
|
|
|
// 0 means ignore
|
|
|
|
int args = 1;
|
2023-08-01 13:43:00 -04:00
|
|
|
// 0 indicates args is used
|
2023-07-29 02:03:28 -04:00
|
|
|
int flags = 0;
|
2023-07-29 13:38:19 -04:00
|
|
|
|
2023-07-29 02:03:28 -04:00
|
|
|
void decode(char c);
|
2023-07-29 13:38:19 -04:00
|
|
|
|
2023-07-29 02:03:28 -04:00
|
|
|
public:
|
2023-08-01 13:43:00 -04:00
|
|
|
arg_nargs_t() = default;
|
2023-07-29 13:38:19 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
arg_nargs_t(int args): args(args)
|
|
|
|
{}
|
2023-07-29 13:38:19 -04:00
|
|
|
|
2023-08-01 13:43:00 -04:00
|
|
|
arg_nargs_t(char c);
|
2023-07-29 13:38:19 -04:00
|
|
|
|
2023-08-01 13:43:00 -04:00
|
|
|
arg_nargs_t(std::string s);
|
2023-07-29 13:38:19 -04:00
|
|
|
|
2023-08-01 13:43:00 -04:00
|
|
|
arg_nargs_t(const char* s);
|
2023-08-08 14:06:19 -04:00
|
|
|
|
|
|
|
[[nodiscard]] bool takesArgs() const
|
|
|
|
{
|
|
|
|
return args > 0 || flags > 0;
|
|
|
|
}
|
2023-07-29 02:03:28 -04:00
|
|
|
};
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
struct arg_properties_t
|
|
|
|
{
|
2023-07-29 02:03:28 -04:00
|
|
|
private:
|
|
|
|
public:
|
2023-08-01 13:43:00 -04:00
|
|
|
arg_vector_t a_flags;
|
|
|
|
arg_action_t a_action = arg_action_t::STORE;
|
2023-08-07 16:12:43 -04:00
|
|
|
arg_nargs_t a_nargs = 1;
|
2023-08-01 13:58:08 -04:00
|
|
|
std::string a_const{};
|
2023-08-07 17:42:59 -04:00
|
|
|
arg_data_internal_t a_default{};
|
2023-08-01 13:58:08 -04:00
|
|
|
std::string a_dest{};
|
2023-07-30 14:04:59 -04:00
|
|
|
std::string a_help{};
|
2023-08-02 14:00:11 -04:00
|
|
|
std::string a_version{};
|
2023-07-30 14:04:59 -04:00
|
|
|
std::string a_metavar{};
|
2023-08-07 17:42:59 -04:00
|
|
|
bool a_required = true;
|
2023-07-29 02:03:28 -04:00
|
|
|
};
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
class arg_builder
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
arg_properties_t properties;
|
|
|
|
public:
|
2023-08-07 17:42:59 -04:00
|
|
|
arg_builder(const arg_vector_t& flags): properties(flags)
|
|
|
|
{}
|
2023-08-07 16:12:43 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
inline arg_properties_t build()
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
return properties;
|
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
inline arg_builder& setAction(arg_action_t action)
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
properties.a_action = action;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
inline arg_builder& setNArgs(const arg_nargs_t& nargs)
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
properties.a_nargs = nargs;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
inline arg_builder& setConst(const std::string& a_const)
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
properties.a_const = a_const;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
inline arg_builder& setDefault(const arg_data_internal_t& def)
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
properties.a_default = def;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
inline arg_builder& setDest(const std::string& dest)
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
properties.a_dest = dest;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
inline arg_builder& setHelp(const std::string& help)
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
properties.a_help = help;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
inline arg_builder& setVersion(const std::string& version)
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
properties.a_version = version;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
inline arg_builder& setMetavar(const std::string& metavar)
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
properties.a_metavar = metavar;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
inline arg_builder& setRequired()
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
properties.a_required = true;
|
|
|
|
return *this;
|
|
|
|
}
|
2023-08-07 17:42:59 -04:00
|
|
|
|
2023-08-07 16:12:43 -04:00
|
|
|
};
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
class arg_tokenizer
|
|
|
|
{
|
2023-07-29 13:38:19 -04:00
|
|
|
private:
|
|
|
|
std::vector<std::string> args;
|
2023-08-07 17:42:59 -04:00
|
|
|
size_t currentIndex = 0;
|
2023-07-29 13:38:19 -04:00
|
|
|
public:
|
2023-08-07 17:42:59 -04:00
|
|
|
arg_tokenizer(std::vector<std::string> args): args(std::move(args))
|
|
|
|
{}
|
2023-07-29 13:38:19 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
// returns the current arg
|
|
|
|
inline const std::string& get()
|
|
|
|
{
|
|
|
|
return args[currentIndex];
|
2023-07-29 13:38:19 -04:00
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
// returns if we have next arg to process
|
|
|
|
inline bool hasNext()
|
|
|
|
{
|
|
|
|
return currentIndex + 1 < args.size();
|
2023-07-29 13:38:19 -04:00
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
inline bool hasCurrent()
|
|
|
|
{
|
|
|
|
return currentIndex < args.size();
|
2023-07-29 13:38:19 -04:00
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
// returns true if the current arg is a flag
|
|
|
|
inline bool isFlag()
|
|
|
|
{
|
|
|
|
return args[currentIndex].starts_with('-');
|
2023-07-29 13:38:19 -04:00
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
// returns true if we have next and the next arg is a flag
|
|
|
|
inline bool isNextFlag()
|
|
|
|
{
|
|
|
|
return hasNext() && args[currentIndex + 1].starts_with('-');
|
2023-07-31 13:53:10 -04:00
|
|
|
}
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
// advances to the next flag
|
|
|
|
inline size_t advance()
|
|
|
|
{
|
|
|
|
return currentIndex++;
|
2023-07-29 13:38:19 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
class arg_parse
|
|
|
|
{
|
2023-07-29 13:38:19 -04:00
|
|
|
private:
|
2023-08-07 17:42:59 -04:00
|
|
|
struct
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
friend arg_parse;
|
2023-08-01 13:43:00 -04:00
|
|
|
private:
|
2023-08-07 17:42:59 -04:00
|
|
|
std::vector<arg_properties_t*> arg_properties_storage;
|
2023-08-08 14:06:19 -04:00
|
|
|
size_t max_line_length = 80;
|
|
|
|
// TODO: grouping like git's help
|
|
|
|
// pre/postfix applied to the help message
|
|
|
|
std::string prefix;
|
|
|
|
std::string postfix;
|
2023-08-01 13:43:00 -04:00
|
|
|
public:
|
2023-08-07 17:42:59 -04:00
|
|
|
std::vector<arg_properties_t*> name_associations;
|
2023-08-01 13:43:00 -04:00
|
|
|
HASHMAP<std::string, arg_properties_t*> flag_associations;
|
2023-07-30 14:04:59 -04:00
|
|
|
} user_args;
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
struct arg_results
|
|
|
|
{
|
2023-08-07 16:12:43 -04:00
|
|
|
friend arg_parse;
|
2023-08-01 13:43:00 -04:00
|
|
|
private:
|
2023-08-07 17:42:59 -04:00
|
|
|
// stores dest value not the flag/name!
|
|
|
|
HASHSET<std::string> found_args;
|
2023-08-07 16:12:43 -04:00
|
|
|
std::vector<std::string> unrecognized_args;
|
2023-08-01 13:43:00 -04:00
|
|
|
public:
|
|
|
|
std::string program_name;
|
2023-08-07 17:42:59 -04:00
|
|
|
HASHMAP<std::string, arg_data_t> data;
|
|
|
|
|
|
|
|
inline arg_data_t& operator[](const std::string& key)
|
|
|
|
{
|
|
|
|
return data[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
inline auto begin()
|
|
|
|
{
|
|
|
|
return data.begin();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline auto end()
|
|
|
|
{
|
|
|
|
return data.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
inline bool contains(const std::string& key)
|
|
|
|
{
|
2023-08-11 12:10:00 -04:00
|
|
|
if (key.starts_with("--"))
|
|
|
|
return data.find(key.substr(2)) != data.end();
|
|
|
|
if (key.starts_with('-'))
|
|
|
|
return data.find(key.substr(1)) != data.end();
|
2023-08-07 17:42:59 -04:00
|
|
|
return data.find(key) != data.end();
|
|
|
|
}
|
2023-07-30 14:04:59 -04:00
|
|
|
} loaded_args;
|
2023-08-07 17:42:59 -04:00
|
|
|
|
2023-08-01 13:43:00 -04:00
|
|
|
private:
|
2023-08-08 14:06:19 -04:00
|
|
|
static std::string getMetavar(const arg_properties_t* const& arg);
|
|
|
|
static std::string getFlagHelp(const arg_properties_t* const& arg);
|
|
|
|
|
|
|
|
static bool takesArgs(const arg_properties_t* const& arg);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* prints out a new line if current line length is greater than max line length, using spacing to generate the next line's
|
|
|
|
* beginning spaces.
|
|
|
|
*/
|
|
|
|
void checkAndPrintFormattingLine(size_t& current_line_length, size_t spacing) const;
|
2023-08-07 17:42:59 -04:00
|
|
|
|
|
|
|
// expects that the current flag has already been consumed (advanced past), leaves tokenizer in a state where the next element is 'current'
|
2023-08-08 14:06:19 -04:00
|
|
|
bool consumeArguments(
|
|
|
|
arg_tokenizer& tokenizer, const std::string& flag, const arg_properties_t& properties, std::vector<arg_data_internal_t>& v_out
|
|
|
|
) const;
|
2023-08-07 17:42:59 -04:00
|
|
|
|
|
|
|
void handlePositionalArgument(arg_tokenizer& tokenizer, size_t& last_pos);
|
|
|
|
|
|
|
|
void handleFlagArgument(arg_tokenizer& tokenizer);
|
|
|
|
|
|
|
|
void processFlag(arg_tokenizer& tokenizer, const std::string& flag);
|
2023-08-08 14:06:19 -04:00
|
|
|
|
2023-08-09 21:48:30 -04:00
|
|
|
public:
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
template<typename T>
|
2023-08-08 14:06:19 -04:00
|
|
|
static inline bool holds_alternative(const arg_data_t& v)
|
|
|
|
{
|
2023-08-07 17:42:59 -04:00
|
|
|
if constexpr (std::is_same_v<T, arg_data_vec_t>)
|
|
|
|
return std::holds_alternative<T>(v);
|
|
|
|
else
|
|
|
|
return std::holds_alternative<arg_data_internal_t>(v) && std::holds_alternative<T>(std::get<arg_data_internal_t>(v));
|
|
|
|
}
|
2023-08-08 14:06:19 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
template<typename T>
|
2023-08-08 14:06:19 -04:00
|
|
|
static inline T& get(arg_data_t& v)
|
|
|
|
{
|
2023-08-07 17:42:59 -04:00
|
|
|
if constexpr (std::is_same_v<T, arg_data_vec_t>)
|
|
|
|
return std::get<arg_data_vec_t>(v);
|
|
|
|
else
|
|
|
|
return std::get<T>(std::get<arg_data_internal_t>(v));
|
|
|
|
}
|
2023-08-08 14:06:19 -04:00
|
|
|
|
2023-07-29 13:38:19 -04:00
|
|
|
public:
|
2023-08-08 14:06:19 -04:00
|
|
|
arg_parse(const std::string& helpMessage = "show this help menu and exit")
|
2023-08-07 17:42:59 -04:00
|
|
|
{
|
2023-08-08 14:06:19 -04:00
|
|
|
addArgument(arg_builder({"--help", "-h"}).setAction(arg_action_t::HELP).setHelp(helpMessage).build());
|
2023-08-07 16:12:43 -04:00
|
|
|
};
|
2023-07-29 02:03:28 -04:00
|
|
|
|
2023-08-01 13:43:00 -04:00
|
|
|
void addArgument(const arg_properties_t& args);
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
arg_results parse_args(int argc, const char** argv);
|
|
|
|
|
|
|
|
arg_results parse_args(const std::vector<std::string>& args);
|
2023-08-01 13:43:00 -04:00
|
|
|
|
2023-08-07 22:39:25 -04:00
|
|
|
void printUsage() const;
|
2023-08-08 14:06:19 -04:00
|
|
|
|
2023-08-07 22:39:25 -04:00
|
|
|
void printHelp() const;
|
2023-08-07 17:42:59 -04:00
|
|
|
|
2023-08-08 14:06:19 -04:00
|
|
|
inline void setHelpPrefix(const std::string& str)
|
|
|
|
{
|
|
|
|
user_args.prefix = str;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void setHelpPostfix(const std::string& str)
|
|
|
|
{
|
|
|
|
user_args.postfix = str;
|
|
|
|
}
|
|
|
|
|
|
|
|
inline void setMaxLineLength(size_t size)
|
|
|
|
{
|
|
|
|
user_args.max_line_length = size;
|
|
|
|
}
|
|
|
|
|
2023-08-10 02:30:09 -04:00
|
|
|
static std::string filename(const std::string& path);
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
~arg_parse()
|
|
|
|
{
|
|
|
|
for (auto* p : user_args.arg_properties_storage)
|
|
|
|
delete p;
|
|
|
|
}
|
2023-07-29 02:03:28 -04:00
|
|
|
};
|
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
std::string to_string(const blt::arg_data_t& v);
|
2023-08-08 14:06:19 -04:00
|
|
|
|
2023-08-07 17:42:59 -04:00
|
|
|
std::string to_string(const blt::arg_data_internal_t& v);
|
2023-08-07 16:12:43 -04:00
|
|
|
|
2023-07-29 02:03:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif //BLT_TESTS_ARGPARSE_H
|