arg parse should be completely functional!
help works not all functions have been tested!v1
parent
4ae730c9bb
commit
7e05fb3d60
|
@ -57,6 +57,7 @@ namespace blt
|
|||
}
|
||||
|
||||
arg_vector_t(const char* str);
|
||||
|
||||
arg_vector_t(const std::string& str);
|
||||
|
||||
[[nodiscard]] inline bool isFlag() const
|
||||
|
@ -72,6 +73,9 @@ namespace blt
|
|||
}
|
||||
) || str == name;
|
||||
}
|
||||
|
||||
// returns the first flag that starts with '--' otherwise return the first '-' flag
|
||||
[[nodiscard]] std::string getFirstFullFlag();
|
||||
};
|
||||
|
||||
class arg_nargs_t
|
||||
|
@ -100,6 +104,11 @@ namespace blt
|
|||
arg_nargs_t(std::string s);
|
||||
|
||||
arg_nargs_t(const char* s);
|
||||
|
||||
[[nodiscard]] bool takesArgs() const
|
||||
{
|
||||
return args > 0 || flags > 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct arg_properties_t
|
||||
|
@ -239,6 +248,11 @@ namespace blt
|
|||
friend arg_parse;
|
||||
private:
|
||||
std::vector<arg_properties_t*> arg_properties_storage;
|
||||
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;
|
||||
public:
|
||||
std::vector<arg_properties_t*> name_associations;
|
||||
HASHMAP<std::string, arg_properties_t*> flag_associations;
|
||||
|
@ -278,9 +292,21 @@ namespace blt
|
|||
|
||||
private:
|
||||
static std::string filename(const std::string& path);
|
||||
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;
|
||||
|
||||
// expects that the current flag has already been consumed (advanced past), leaves tokenizer in a state where the next element is 'current'
|
||||
bool consumeArguments(arg_tokenizer& tokenizer, const std::string& flag, const arg_properties_t& properties, std::vector<arg_data_internal_t>& v_out) const;
|
||||
bool consumeArguments(
|
||||
arg_tokenizer& tokenizer, const std::string& flag, const arg_properties_t& properties, std::vector<arg_data_internal_t>& v_out
|
||||
) const;
|
||||
|
||||
void handlePositionalArgument(arg_tokenizer& tokenizer, size_t& last_pos);
|
||||
|
||||
|
@ -289,23 +315,27 @@ namespace blt
|
|||
void processFlag(arg_tokenizer& tokenizer, const std::string& flag);
|
||||
|
||||
template<typename T>
|
||||
static inline bool holds_alternative(const arg_data_t& v){
|
||||
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){
|
||||
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));
|
||||
}
|
||||
|
||||
public:
|
||||
arg_parse()
|
||||
arg_parse(const std::string& helpMessage = "show this help menu and exit")
|
||||
{
|
||||
addArgument(arg_builder({"--help", "-h"}).setAction(arg_action_t::HELP).setHelp("Show this help menu").build());
|
||||
addArgument(arg_builder({"--help", "-h"}).setAction(arg_action_t::HELP).setHelp(helpMessage).build());
|
||||
};
|
||||
|
||||
void addArgument(const arg_properties_t& args);
|
||||
|
@ -315,8 +345,24 @@ namespace blt
|
|||
arg_results parse_args(const std::vector<std::string>& args);
|
||||
|
||||
void printUsage() const;
|
||||
|
||||
void printHelp() const;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
~arg_parse()
|
||||
{
|
||||
for (auto* p : user_args.arg_properties_storage)
|
||||
|
@ -325,6 +371,7 @@ namespace blt
|
|||
};
|
||||
|
||||
std::string to_string(const blt::arg_data_t& v);
|
||||
|
||||
std::string to_string(const blt::arg_data_internal_t& v);
|
||||
|
||||
}
|
||||
|
|
|
@ -68,6 +68,20 @@ namespace blt
|
|||
name = str;
|
||||
}
|
||||
|
||||
std::string arg_vector_t::getFirstFullFlag()
|
||||
{
|
||||
// assign flag so it always exists, will be first '-' flag if we fail to find a '--' flag
|
||||
std::string flag = flags[0];
|
||||
// search for first '--' flag
|
||||
for (const auto& f : flags)
|
||||
if (f.starts_with("--"))
|
||||
{
|
||||
flag = f;
|
||||
break;
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
std::string to_string(const arg_data_t& v)
|
||||
{
|
||||
if (holds_alternative<arg_data_internal_t>(v))
|
||||
|
@ -119,19 +133,8 @@ namespace blt
|
|||
if (properties->a_dest.empty())
|
||||
{
|
||||
if (properties->a_flags.isFlag())
|
||||
{
|
||||
// take first arg so a_dest exists, could be - or --
|
||||
properties->a_dest = properties->a_flags.flags[0];
|
||||
// look for a -- arg (python's behaviour)
|
||||
for (const auto& flag : properties->a_flags.flags)
|
||||
{
|
||||
if (flag.starts_with("--"))
|
||||
{
|
||||
properties->a_dest = flag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else
|
||||
properties->a_dest = properties->a_flags.getFirstFullFlag();
|
||||
else
|
||||
properties->a_dest = properties->a_flags.name;
|
||||
}
|
||||
|
||||
|
@ -291,6 +294,7 @@ namespace blt
|
|||
switch (properties->a_action)
|
||||
{
|
||||
case arg_action_t::HELP:
|
||||
printUsage();
|
||||
printHelp();
|
||||
break;
|
||||
case arg_action_t::STORE:
|
||||
|
@ -409,20 +413,150 @@ namespace blt
|
|||
unrec = unrec.substr(0, unrec.size() - 1);
|
||||
printUsage();
|
||||
std::cout << loaded_args.program_name << ": error: unrecognized args: " << unrec << "\n";
|
||||
printHelp();
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
return loaded_args;
|
||||
bool arg_parse::takesArgs(const arg_properties_t* const& arg)
|
||||
{
|
||||
switch (arg->a_action)
|
||||
{
|
||||
case arg_action_t::STORE_CONST:
|
||||
case arg_action_t::STORE_TRUE:
|
||||
case arg_action_t::STORE_FALSE:
|
||||
case arg_action_t::APPEND_CONST:
|
||||
case arg_action_t::COUNT:
|
||||
case arg_action_t::HELP:
|
||||
case arg_action_t::VERSION:
|
||||
return false;
|
||||
case arg_action_t::STORE:
|
||||
case arg_action_t::APPEND:
|
||||
case arg_action_t::EXTEND:
|
||||
return arg->a_nargs.takesArgs();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void arg_parse::printHelp() const
|
||||
{
|
||||
std::cout << ("I am helpful!\n");
|
||||
std::cout << "\npositional arguments:\n";
|
||||
// spaces per tab
|
||||
const size_t tab_size = 8;
|
||||
size_t max_length = 0;
|
||||
// search for longest pos arg length
|
||||
for (const auto& arg : user_args.arg_properties_storage)
|
||||
{
|
||||
if (!arg->a_flags.isFlag())
|
||||
max_length = std::max(arg->a_flags.name.size(), max_length);
|
||||
else {
|
||||
auto tmp = getFlagHelp(arg);
|
||||
max_length = std::max(tmp.size(), max_length);
|
||||
}
|
||||
}
|
||||
for (const auto& arg : user_args.arg_properties_storage)
|
||||
{
|
||||
if (!arg->a_flags.isFlag())
|
||||
{
|
||||
const auto& name = arg->a_flags.name;
|
||||
std::cout << name;
|
||||
auto size = std::max(static_cast<int64_t>(max_length) - static_cast<int64_t>(name.size()), 0l);
|
||||
size += tab_size;
|
||||
for (int64_t i = 0; i < size; i++)
|
||||
std::cout << " ";
|
||||
std::cout << arg->a_help << "\n";
|
||||
}
|
||||
}
|
||||
std::cout << "\noptions:\n";
|
||||
for (const auto& arg : user_args.arg_properties_storage)
|
||||
{
|
||||
if (arg->a_flags.isFlag())
|
||||
{
|
||||
const auto& name = getFlagHelp(arg);
|
||||
std::cout << name;
|
||||
auto size = std::max(static_cast<int64_t>(max_length) - static_cast<int64_t>(name.size()), 0l);
|
||||
size += tab_size;
|
||||
for (int64_t i = 0; i < size; i++)
|
||||
std::cout << " ";
|
||||
std::cout << arg->a_help << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
void arg_parse::printUsage() const
|
||||
{
|
||||
std::string usage = "Usage: " + loaded_args.program_name + " ";
|
||||
std::cout << usage;
|
||||
size_t current_line_length = 0;
|
||||
|
||||
for (const auto& arg : user_args.arg_properties_storage)
|
||||
{
|
||||
auto meta = getMetavar(arg);
|
||||
|
||||
std::string str = "[";
|
||||
if (arg->a_flags.isFlag())
|
||||
{
|
||||
str += arg->a_flags.getFirstFullFlag();
|
||||
if (takesArgs(arg))
|
||||
{
|
||||
str += " ";
|
||||
str += meta;
|
||||
str += "";
|
||||
}
|
||||
str += "]";
|
||||
str += ' ';
|
||||
} else
|
||||
{
|
||||
str += "<";
|
||||
str += arg->a_flags.name;
|
||||
str += ">] ";
|
||||
}
|
||||
|
||||
current_line_length += str.size();
|
||||
checkAndPrintFormattingLine(current_line_length, usage.size());
|
||||
|
||||
std::cout << str;
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void arg_parse::checkAndPrintFormattingLine(size_t& current_line_length, size_t spacing) const
|
||||
{
|
||||
if (current_line_length > user_args.max_line_length)
|
||||
{
|
||||
std::cout << "\n";
|
||||
for (size_t i = 0; i < spacing; i++)
|
||||
std::cout << " ";
|
||||
current_line_length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string arg_parse::getMetavar(const arg_properties_t* const& arg)
|
||||
{
|
||||
auto meta = arg->a_metavar;
|
||||
|
||||
if (meta.empty())
|
||||
meta = blt::string::toUpperCase(arg->a_dest);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
std::string arg_parse::getFlagHelp(const arg_properties_t* const& arg)
|
||||
{
|
||||
auto meta = getMetavar(arg);
|
||||
// bad that we have to create this twice!
|
||||
std::string tmp;
|
||||
for (const auto& flag : arg->a_flags.flags)
|
||||
{
|
||||
tmp += flag;
|
||||
if (takesArgs(arg))
|
||||
{
|
||||
tmp += ' ';
|
||||
tmp += meta;
|
||||
}
|
||||
tmp += ", ";
|
||||
}
|
||||
tmp = tmp.substr(0, tmp.size()-2);
|
||||
return tmp;
|
||||
}
|
||||
}
|
|
@ -73,14 +73,14 @@ int main(int argc, const char** argv) {
|
|||
parser.addArgument(blt::arg_builder("--foo").setAction(blt::arg_action_t::STORE_TRUE).setDefault(false).build());
|
||||
parser.addArgument(blt::arg_builder({"--goo", "-g"}).build());
|
||||
parser.addArgument(blt::arg_builder({"--oop", "-o"}).build());
|
||||
parser.addArgument(blt::arg_builder("Sexy_pos").build());
|
||||
parser.addArgument(blt::arg_builder("Sexy_pos").setHelp("I am helpful!").build());
|
||||
|
||||
auto args = parser.parse_args(argc, argv);
|
||||
std::vector<std::string> superArgs {
|
||||
"BLT_TESTS",
|
||||
"Sexy",
|
||||
"-p", "I have poop",
|
||||
"-f"
|
||||
"--help"
|
||||
};
|
||||
auto args2 = parser.parse_args(superArgs);
|
||||
|
||||
|
|
Loading…
Reference in New Issue