Skip to content

Commit

Permalink
configurable argument parser implementation (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
neri14 authored Aug 19, 2024
1 parent b9b3a70 commit 3dd7633
Show file tree
Hide file tree
Showing 11 changed files with 535 additions and 142 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"typeinfo": "cpp",
"valarray": "cpp",
"variant": "cpp",
"stdfloat": "cpp"
"stdfloat": "cpp",
"span_ext": "cpp"
}
}
22 changes: 13 additions & 9 deletions src/main.cpp
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;
}
4 changes: 2 additions & 2 deletions src/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.20.0)

target_sources(vgraph_lib
PRIVATE
args.cpp
argument_parser.cpp
PUBLIC
args.h
argument_parser.h
)

add_subdirectory(logging)
34 changes: 0 additions & 34 deletions src/utils/args.cpp

This file was deleted.

18 changes: 0 additions & 18 deletions src/utils/args.h

This file was deleted.

215 changes: 215 additions & 0 deletions src/utils/argument_parser.cpp
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
68 changes: 68 additions & 0 deletions src/utils/argument_parser.h
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
Loading

0 comments on commit 3dd7633

Please sign in to comment.