BLT/include/blt/parse/argparse.h

332 lines
9.8 KiB
C
Raw Normal View History

/*
* Created by Brett on 06/08/23.
* Licensed under GNU General Public License V3.0
* See LICENSE file for license detail
*/
#ifndef BLT_TESTS_ARGPARSE_H
#define BLT_TESTS_ARGPARSE_H
#include <utility>
#include <vector>
#include <string>
#include <initializer_list>
#include <optional>
#include <blt/std/hashmap.h>
2023-08-01 13:43:00 -04:00
#include <variant>
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
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
};
class arg_vector_t
{
friend class arg_parse;
private:
std::vector<std::string> flags;
std::string name;
void validateFlags();
public:
arg_vector_t(std::vector<std::string> flags): flags(std::move(flags))
{
validateFlags();
}
2023-07-29 13:38:19 -04:00
arg_vector_t(std::initializer_list<std::string> flags): flags(flags)
{
validateFlags();
}
2023-07-29 14:04:46 -04:00
arg_vector_t(const char* str);
arg_vector_t(const std::string& str);
2023-08-01 13:43:00 -04:00
[[nodiscard]] inline bool isFlag() const
{
return !flags.empty();
}
2023-08-01 13:43:00 -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;
}
};
class arg_nargs_t
{
friend class arg_parse;
private:
static constexpr int UNKNOWN = 0x1;
static constexpr int ALL = 0x2;
static constexpr int ALL_REQUIRED = 0x4;
// 0 means ignore
int args = 1;
2023-08-01 13:43:00 -04:00
// 0 indicates args is used
int flags = 0;
2023-07-29 13:38:19 -04:00
void decode(char c);
2023-07-29 13:38:19 -04:00
public:
2023-08-01 13:43:00 -04:00
arg_nargs_t() = default;
2023-07-29 13:38:19 -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);
};
struct arg_properties_t
{
private:
public:
2023-08-01 13:43:00 -04:00
arg_vector_t a_flags;
arg_action_t a_action = arg_action_t::STORE;
arg_nargs_t a_nargs = 1;
2023-08-01 13:58:08 -04:00
std::string a_const{};
arg_data_internal_t a_default{};
2023-08-01 13:58:08 -04:00
std::string a_dest{};
std::string a_help{};
std::string a_version{};
std::string a_metavar{};
bool a_required = true;
};
class arg_builder
{
arg_properties_t properties;
public:
arg_builder(const arg_vector_t& flags): properties(flags)
{}
inline arg_properties_t build()
{
return properties;
}
inline arg_builder& setAction(arg_action_t action)
{
properties.a_action = action;
return *this;
}
inline arg_builder& setNArgs(const arg_nargs_t& nargs)
{
properties.a_nargs = nargs;
return *this;
}
inline arg_builder& setConst(const std::string& a_const)
{
properties.a_const = a_const;
return *this;
}
inline arg_builder& setDefault(const arg_data_internal_t& def)
{
properties.a_default = def;
return *this;
}
inline arg_builder& setDest(const std::string& dest)
{
properties.a_dest = dest;
return *this;
}
inline arg_builder& setHelp(const std::string& help)
{
properties.a_help = help;
return *this;
}
inline arg_builder& setVersion(const std::string& version)
{
properties.a_version = version;
return *this;
}
inline arg_builder& setMetavar(const std::string& metavar)
{
properties.a_metavar = metavar;
return *this;
}
inline arg_builder& setRequired()
{
properties.a_required = true;
return *this;
}
};
class arg_tokenizer
{
2023-07-29 13:38:19 -04:00
private:
std::vector<std::string> args;
size_t currentIndex = 0;
2023-07-29 13:38:19 -04:00
public:
arg_tokenizer(std::vector<std::string> args): args(std::move(args))
{}
2023-07-29 13:38:19 -04:00
// returns the current arg
inline const std::string& get()
{
return args[currentIndex];
2023-07-29 13:38:19 -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
}
inline bool hasCurrent()
{
return currentIndex < args.size();
2023-07-29 13:38:19 -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
}
// 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
}
// advances to the next flag
inline size_t advance()
{
return currentIndex++;
2023-07-29 13:38:19 -04:00
}
};
class arg_parse
{
2023-07-29 13:38:19 -04:00
private:
struct
{
friend arg_parse;
2023-08-01 13:43:00 -04:00
private:
std::vector<arg_properties_t*> arg_properties_storage;
2023-08-01 13:43:00 -04:00
public:
std::vector<arg_properties_t*> name_associations;
2023-08-01 13:43:00 -04:00
HASHMAP<std::string, arg_properties_t*> flag_associations;
} user_args;
struct arg_results
{
friend arg_parse;
2023-08-01 13:43:00 -04:00
private:
// stores dest value not the flag/name!
HASHSET<std::string> found_args;
std::vector<std::string> unrecognized_args;
2023-08-01 13:43:00 -04:00
public:
std::string program_name;
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)
{
return data.find(key) != data.end();
}
} loaded_args;
2023-08-01 13:43:00 -04:00
private:
static std::string filename(const std::string& path);
// expects that the current flag has already been consumed (advanced past), leaves tokenizer in a state where the next element is 'current'
static bool consumeArguments(arg_tokenizer& tokenizer, const arg_properties_t& properties, std::vector<arg_data_internal_t>& v_out);
void handlePositionalArgument(arg_tokenizer& tokenizer, size_t& last_pos);
void handleFlagArgument(arg_tokenizer& tokenizer);
void processFlag(arg_tokenizer& tokenizer, const std::string& flag);
template<typename T>
static inline bool holds_alternative(const arg_data_t& v){
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));
}
template<typename T>
static inline T& get(arg_data_t& v){
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-07-29 13:38:19 -04:00
public:
arg_parse()
{
addArgument(arg_builder({"--help", "-h"}).setAction(arg_action_t::HELP).setHelp("Show this help menu").build());
};
2023-08-01 13:43:00 -04:00
void addArgument(const arg_properties_t& args);
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
void printHelp();
~arg_parse()
{
for (auto* p : user_args.arg_properties_storage)
delete p;
}
};
std::string to_string(const blt::arg_data_t& v);
std::string to_string(const blt::arg_data_internal_t& v);
}
#endif //BLT_TESTS_ARGPARSE_H