-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
configurable argument parser implementation (#2)
- Loading branch information
Showing
11 changed files
with
535 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,24 @@ | ||
#include <iostream> | ||
|
||
#include "utils/args.h" | ||
#include "utils/argument_parser.h" | ||
|
||
int main(int argc, char* argv[]) | ||
{ | ||
vgraph::utils::args a = vgraph::utils::args::parse(argc, argv); | ||
vgraph::utils::argument_parser parser("vgraph"); | ||
|
||
if (a.help) { | ||
vgraph::utils::args::print_help(); | ||
return 0; | ||
{ | ||
using vgraph::utils::argument; | ||
parser.add_argument("help", argument().option("-h").option("--help").flag().description("Print this help message")); | ||
parser.add_argument("debug", argument().option("-d").option("--debug").flag().description("Enable debug logs")); | ||
parser.add_argument("telemetry", argument().option("-t").option("--telemetry").description("Telemetry file path")); | ||
} | ||
|
||
if (a.debug) | ||
std::cout << "DEBUG" << std::endl; | ||
else | ||
std::cout << "NO DEBUG" << std::endl; | ||
parser.parse(argc, argv); | ||
|
||
if (parser.get<bool>("help")) { | ||
parser.print_help(); | ||
return 0; | ||
} | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
#include "argument_parser.h" | ||
|
||
#include <format> | ||
#include <iostream> | ||
#include <numeric> | ||
#include <algorithm> | ||
|
||
namespace vgraph { | ||
namespace utils { | ||
namespace helper { | ||
|
||
std::string to_upper(std::string str) | ||
{ | ||
std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c){ return std::toupper(c); }); | ||
return std::move(str); | ||
} | ||
|
||
} // namespace helper | ||
|
||
argument_exception::argument_exception( | ||
const std::string& msg): msg_(msg) {} | ||
|
||
const char* argument_exception::what() const noexcept | ||
{ | ||
return std::format("Argument exception: {}", msg_).c_str(); | ||
} | ||
|
||
argument& argument::option(const std::string& opt) | ||
{ | ||
if (opt.rfind('-', 0) != 0) { | ||
throw argument_exception("Option does not start with a dash"); | ||
} | ||
if (opt.find(' ') != std::string::npos) { | ||
throw argument_exception("Option contains spaces"); | ||
} | ||
if (std::find(options_.begin(), options_.end(), opt) != options_.end()) { | ||
throw argument_exception("Duplicate option in argument definition"); | ||
} | ||
|
||
options_.push_back(opt); | ||
return *this; | ||
} | ||
|
||
argument& argument::flag() | ||
{ | ||
is_flag_ = true; | ||
return *this; | ||
} | ||
|
||
argument& argument::description(const std::string& desc) | ||
{ | ||
description_ = desc; | ||
return *this; | ||
}; | ||
|
||
argument_parser::argument_parser(const std::string binary_name) : | ||
binary_name_(binary_name) | ||
{} | ||
|
||
void argument_parser::add_argument(const std::string& key, const argument& arg) | ||
{ | ||
if (arg.options_.empty()) | ||
throw argument_exception("No option defined for argument"); | ||
|
||
if (arguments_.contains(key)) | ||
throw argument_exception("Duplicate argument key"); | ||
|
||
for (const std::string& opt : arg.options_) | ||
if (options_.contains(opt)) | ||
throw argument_exception("Argument with already defined option added"); | ||
|
||
keys_.push_back(key); | ||
arguments_[key] = arg; | ||
|
||
for (const std::string& opt : arg.options_) | ||
options_[opt] = key; | ||
} | ||
|
||
void argument_parser::parse(int argc, char* argv[]) | ||
{ | ||
for (int i=1; i<argc; i++) { | ||
std::string opt(argv[i]); | ||
|
||
if (!options_.contains(opt)) | ||
throw argument_exception(std::format("Undefined option {}", opt)); | ||
|
||
const std::string& key = options_.at(opt); | ||
const argument& arg = arguments_.at(key); | ||
|
||
if (arg.is_flag_) { | ||
values_[key] = std::vector<std::string>(); | ||
continue; | ||
} | ||
|
||
if (++i >= argc) | ||
throw argument_exception(std::format("Missing value for {}", opt)); | ||
|
||
std::string val(argv[i]); | ||
|
||
if (!values_.contains(key)) { | ||
values_[key] = std::vector<std::string>(); | ||
} | ||
values_[key].push_back(val); | ||
} | ||
} | ||
|
||
bool argument_parser::has(const std::string& key) const | ||
{ | ||
if (!arguments_.contains(key)) | ||
throw argument_exception("Undefined argument key retrieval attempt"); | ||
|
||
return values_.contains(key); | ||
} | ||
|
||
template <> | ||
std::string argument_parser::get(const std::string& key) const | ||
{ | ||
if (!arguments_.contains(key)) | ||
throw argument_exception(std::format("Undefined argument \"{}\" retrieval attempt", key)); | ||
|
||
if (!has(key)) | ||
throw argument_exception(std::format("Retrieval of not provided argument \"{}\" value", key)); | ||
|
||
const auto& val = values_.at(key); | ||
if (val.size() < 1) | ||
throw argument_exception(std::format("Retrieval of argument \"{}\" value that has no associated value", key)); | ||
if (val.size() > 1) | ||
throw argument_exception(std::format("Retrieval of singular argument \"{}\" value that has more values", key)); | ||
|
||
return val[0]; | ||
} | ||
|
||
template <> | ||
std::vector<std::string> argument_parser::get(const std::string& key) const | ||
{ | ||
if (!arguments_.contains(key)) | ||
throw argument_exception(std::format("Undefined argument \"{}\" retrieval attempt", key)); | ||
|
||
if (!has(key)) | ||
throw argument_exception(std::format("Retrieval of not provided argument \"{}\" value", key)); | ||
|
||
const auto& val = values_.at(key); | ||
if (val.size() < 1) | ||
throw argument_exception(std::format("Retrieval of argument \"{}\" value that has no associated value", key)); | ||
|
||
return val; | ||
} | ||
|
||
template <> | ||
bool argument_parser::get(const std::string& key) const | ||
{ | ||
return has(key); | ||
} | ||
|
||
void argument_parser::print_help() const | ||
{ | ||
print_help_usage(); | ||
print_help_details(); | ||
} | ||
|
||
void argument_parser::print_help_usage() const | ||
{ | ||
std::string opts; | ||
|
||
for (const std::string& key : keys_) { | ||
const auto& arg = arguments_.at(key); | ||
|
||
std::string str = *std::min_element( | ||
arg.options_.begin(), | ||
arg.options_.end(), | ||
[](const std::string& a, const std::string& b){ | ||
return a.length() < b.length(); | ||
}); | ||
|
||
if (!arg.is_flag_) { | ||
str = std::format("{} {}", str, helper::to_upper(key)); | ||
} | ||
|
||
opts += std::format("[{}]", str); | ||
} | ||
|
||
std::cout << std::format("Usage: {} {}", binary_name_, opts) << std::endl; | ||
} | ||
|
||
void argument_parser::print_help_details() const | ||
{ | ||
std::cout << "Arguments:" << std::endl; | ||
|
||
|
||
for (const std::string& key : keys_) { | ||
const auto& arg = arguments_.at(key); | ||
|
||
std::string str = std::accumulate( | ||
arg.options_.begin(), | ||
arg.options_.end(), | ||
std::string{}, | ||
[](const std::string& prev, const std::string& add){ | ||
return prev.empty() ? add : std::format("{},{}", prev, add); | ||
}); | ||
|
||
if (!arg.is_flag_) { | ||
str = std::format("{} {}", str, helper::to_upper(key)); | ||
} | ||
|
||
if (str.length() > 20) { | ||
std::cout << std::format(" {}", str) << std::endl; | ||
std::cout << std::format(" {}", arg.description_) << std::endl; | ||
} else { | ||
std::cout << std::format(" {:<16} {}", str, arg.description_) << std::endl; | ||
} | ||
} | ||
} | ||
|
||
} // namespace utils | ||
} // namespace vgraph |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#ifndef ARGUMENT_PARSER_H | ||
#define ARGUMENT_PARSER_H | ||
|
||
#include <string> | ||
#include <format> | ||
#include <vector> | ||
#include <map> | ||
#include <optional> | ||
|
||
namespace vgraph { | ||
namespace utils { | ||
|
||
class argument_exception : public std::exception { | ||
public: | ||
argument_exception(const std::string& msg); | ||
const char* what() const noexcept override; | ||
|
||
private: | ||
std::string msg_; | ||
}; | ||
|
||
struct argument { | ||
argument& option(const std::string& opt); | ||
argument& flag(); | ||
argument& description(const std::string& desc); | ||
|
||
std::vector<std::string> options_ = {}; | ||
bool is_flag_ = false; | ||
std::string description_ = ""; | ||
}; | ||
|
||
class argument_parser { | ||
public: | ||
argument_parser(const std::string binary_name); | ||
~argument_parser() = default; | ||
argument_parser(const argument_parser&) = delete; | ||
|
||
void add_argument(const std::string& key, const argument& arg); | ||
|
||
void parse(int argc, char* argv[]); | ||
|
||
bool has(const std::string& key) const; | ||
|
||
template <typename T> | ||
T get(const std::string& key) const | ||
{ | ||
throw argument_exception(std::format("Unsupported argument type requested for {} argument", key)); | ||
} | ||
|
||
void print_help() const; | ||
|
||
private: | ||
void print_help_usage() const; | ||
void print_help_details() const; | ||
|
||
const std::string binary_name_; | ||
|
||
std::vector<std::string> keys_; // vector of all supported keys in order of addition | ||
std::map<std::string, argument> arguments_; // mapping argument keys to argument objects | ||
std::map<std::string, std::string> options_; // mapping options to argument keys | ||
|
||
std::map<std::string, std::vector<std::string>> values_; // parsed values of parsed keys mapped to optional raw value | ||
}; | ||
|
||
} // namespace utils | ||
} // namespace vgraph | ||
|
||
#endif // ARGUMENT_PARSER_H |
Oops, something went wrong.