arg parse should be completely functional!

help works
not all functions have been tested!
v1
Brett 2023-08-08 14:06:19 -04:00
parent 4ae730c9bb
commit 7e05fb3d60
3 changed files with 206 additions and 25 deletions

View File

@ -57,6 +57,7 @@ namespace blt
} }
arg_vector_t(const char* str); arg_vector_t(const char* str);
arg_vector_t(const std::string& str); arg_vector_t(const std::string& str);
[[nodiscard]] inline bool isFlag() const [[nodiscard]] inline bool isFlag() const
@ -72,6 +73,9 @@ namespace blt
} }
) || str == name; ) || str == name;
} }
// returns the first flag that starts with '--' otherwise return the first '-' flag
[[nodiscard]] std::string getFirstFullFlag();
}; };
class arg_nargs_t class arg_nargs_t
@ -100,6 +104,11 @@ namespace blt
arg_nargs_t(std::string s); arg_nargs_t(std::string s);
arg_nargs_t(const char* s); arg_nargs_t(const char* s);
[[nodiscard]] bool takesArgs() const
{
return args > 0 || flags > 0;
}
}; };
struct arg_properties_t struct arg_properties_t
@ -239,6 +248,11 @@ namespace blt
friend arg_parse; friend arg_parse;
private: private:
std::vector<arg_properties_t*> arg_properties_storage; 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: public:
std::vector<arg_properties_t*> name_associations; std::vector<arg_properties_t*> name_associations;
HASHMAP<std::string, arg_properties_t*> flag_associations; HASHMAP<std::string, arg_properties_t*> flag_associations;
@ -278,34 +292,50 @@ namespace blt
private: private:
static std::string filename(const std::string& path); 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' // 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); void handlePositionalArgument(arg_tokenizer& tokenizer, size_t& last_pos);
void handleFlagArgument(arg_tokenizer& tokenizer); void handleFlagArgument(arg_tokenizer& tokenizer);
void processFlag(arg_tokenizer& tokenizer, const std::string& flag); void processFlag(arg_tokenizer& tokenizer, const std::string& flag);
template<typename T> 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>) if constexpr (std::is_same_v<T, arg_data_vec_t>)
return std::holds_alternative<T>(v); return std::holds_alternative<T>(v);
else else
return std::holds_alternative<arg_data_internal_t>(v) && std::holds_alternative<T>(std::get<arg_data_internal_t>(v)); return std::holds_alternative<arg_data_internal_t>(v) && std::holds_alternative<T>(std::get<arg_data_internal_t>(v));
} }
template<typename T> 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>) if constexpr (std::is_same_v<T, arg_data_vec_t>)
return std::get<arg_data_vec_t>(v); return std::get<arg_data_vec_t>(v);
else else
return std::get<T>(std::get<arg_data_internal_t>(v)); return std::get<T>(std::get<arg_data_internal_t>(v));
} }
public: 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); void addArgument(const arg_properties_t& args);
@ -315,8 +345,24 @@ namespace blt
arg_results parse_args(const std::vector<std::string>& args); arg_results parse_args(const std::vector<std::string>& args);
void printUsage() const; void printUsage() const;
void printHelp() 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() ~arg_parse()
{ {
for (auto* p : user_args.arg_properties_storage) 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_t& v);
std::string to_string(const blt::arg_data_internal_t& v); std::string to_string(const blt::arg_data_internal_t& v);
} }

View File

@ -68,6 +68,20 @@ namespace blt
name = str; 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) std::string to_string(const arg_data_t& v)
{ {
if (holds_alternative<arg_data_internal_t>(v)) if (holds_alternative<arg_data_internal_t>(v))
@ -119,19 +133,8 @@ namespace blt
if (properties->a_dest.empty()) if (properties->a_dest.empty())
{ {
if (properties->a_flags.isFlag()) if (properties->a_flags.isFlag())
{ properties->a_dest = properties->a_flags.getFirstFullFlag();
// take first arg so a_dest exists, could be - or -- else
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.name; properties->a_dest = properties->a_flags.name;
} }
@ -291,6 +294,7 @@ namespace blt
switch (properties->a_action) switch (properties->a_action)
{ {
case arg_action_t::HELP: case arg_action_t::HELP:
printUsage();
printHelp(); printHelp();
break; break;
case arg_action_t::STORE: case arg_action_t::STORE:
@ -409,20 +413,150 @@ namespace blt
unrec = unrec.substr(0, unrec.size() - 1); unrec = unrec.substr(0, unrec.size() - 1);
printUsage(); printUsage();
std::cout << loaded_args.program_name << ": error: unrecognized args: " << unrec << "\n"; 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 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); std::exit(0);
} }
void arg_parse::printUsage() const 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;
} }
} }

View File

@ -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("--foo").setAction(blt::arg_action_t::STORE_TRUE).setDefault(false).build());
parser.addArgument(blt::arg_builder({"--goo", "-g"}).build()); parser.addArgument(blt::arg_builder({"--goo", "-g"}).build());
parser.addArgument(blt::arg_builder({"--oop", "-o"}).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); auto args = parser.parse_args(argc, argv);
std::vector<std::string> superArgs { std::vector<std::string> superArgs {
"BLT_TESTS", "BLT_TESTS",
"Sexy", "Sexy",
"-p", "I have poop", "-p", "I have poop",
"-f" "--help"
}; };
auto args2 = parser.parse_args(superArgs); auto args2 = parser.parse_args(superArgs);