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 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,9 +292,21 @@ 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);
|
||||||
|
|
||||||
|
@ -289,23 +315,27 @@ namespace blt
|
||||||
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue