HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
CommandLineParser.hpp
1// Copyright Take Vos 2019-2020.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "datum.hpp"
8#include <string>
9#include <vector>
10#include <map>
11#include <functional>
12#include <string_view>
13#include <iostream>
14
15namespace tt {
16
17using namespace std::literals;
18
24 struct option_t {
27 std::string name;
28
31 datum_type_t type;
32
35 std::string help;
36
40 std::function<int(std::string_view)> enum_conversion;
41
42 option_t(std::string name, datum_type_t type, std::string help, std::function<int(std::string_view)> enum_conversion) :
43 name(std::move(name)), type(type), help(std::move(help)), enum_conversion(std::move(enum_conversion)) {}
44 };
45
46 // The synopsis of the application to be printed on --help and error.
47 std::string synopsis;
48
49 // A list of options.
51
53 std::vector<std::string> error_messages;
54
55public:
56 CommandLineParser(std::string synopsis) : synopsis(std::move(synopsis)) {}
57
65 void add(std::string name, datum_type_t type, std::string help, std::function<int(std::string_view)> enum_conversion = {}) noexcept {
66 options.emplace_back(std::move(name), type, std::move(help), std::move(enum_conversion));
67 }
68
71 bool has_error() const noexcept {
72 return error_messages.size() > 0;
73 }
74
78 void print_help() {
79 for (ttlet &error_message: error_messages) {
80 std::cerr << error_message << "\n";
81 }
82 if (has_error()) {
83 std::cerr << "\n";
84 }
85
86 std::cerr << synopsis << "\n";
87
88 for (ttlet &option: options) {
89 ttlet example = fmt::format("--{}=<{}>", option.name, option.type);
90 std::cerr << fmt::format(" {:20s} {}\n", example, option.help);
91 }
92 std::cerr.flush();
93 }
94
104 datum parse(std::vector<std::string> const &arguments) noexcept {
105 auto r = datum{datum::map{}};
106
107 int argumentCount = 0;
108 for (ttlet &argument: arguments) {
109 if (argumentCount++ == 0) {
110 r["executable-path"] = arguments[0];
111
112 } else if (argument.starts_with("--"s)) {
113 ttlet i = argument.find('=');
114 if (i == argument.npos) {
115 ttlet option_name = argument.substr(2);
116
117 ttlet &option = std::find_if(options.begin(), options.end(), [&](auto x) {
118 return x.name == option_name;
119 });
120
121 if (option == options.end()) {
122 error_messages.push_back(fmt::format("Unknown option '{}'", option_name));
123
124 } else if (option->type != datum_type_t::Boolean) {
125 error_messages.push_back(fmt::format("Option '{}' requires an argument", option_name));
126
127 } else {
128 r[option_name] = true;
129 }
130
131 } else {
132 ttlet option_name = argument.substr(2, i-2);
133 ttlet option_value_string = argument.substr(i+1);
134
135 ttlet &option = std::find_if(options.begin(), options.end(), [&](auto x) {
136 return x.name == option_name;
137 });
138
139 if (option == options.end()) {
140 error_messages.push_back(fmt::format("Unknown option '{}'", option_name));
141
142 } else {
143 switch (option->type) {
144 case datum_type_t::Boolean:
145 if (option_value_string == "true") {
146 r[option_name] = true;
147 } else if (option_value_string == "false") {
148 r[option_name] = false;
149 } else {
150 error_messages.push_back(
151 fmt::format("Expected a boolean value ('true' or 'false') for option '{}' got '{}'", option_name, option_value_string)
152 );
153 }
154 break;
155
156 case datum_type_t::Integer:
157 if (option->enum_conversion) {
158 ttlet option_value_int = option->enum_conversion(option_value_string);
159 if (option_value_int >= 0) {
160 r[option_name] = option_value_int;
161 } else {
162 error_messages.push_back(
163 fmt::format("Unknown value '{}' for option '{}'", option_value_string, option_name)
164 );
165 }
166
167 } else {
168 try {
169 ttlet option_value_int = std::stoll(option_value_string);
170 r[option_name] = option_value_int;
171 } catch (...) {
172 error_messages.push_back(
173 fmt::format("Expected a integer value for option '{}' got '{}'", option_name, option_value_string)
174 );
175 }
176 }
177 break;
178
179 case datum_type_t::String:
180 r[option_name] = option_value_string;
181 break;
182
183 case datum_type_t::Vector:
184 r[option_name].push_back(datum{option_value_string});
185 break;
186
187 case datum_type_t::URL:
188 r[option_name] = URL::urlFromCurrentWorkingDirectory().urlByAppendingPath(option_value_string);
189 break;
190
191 default:
192 tt_no_default();
193 }
194 }
195 }
196
197 } else {
198 r["arguments"].push_back(argument);
199 }
200 }
201 return r;
202 }
203};
204
205}
A parser to parse command line arguments.
Definition CommandLineParser.hpp:21
bool has_error() const noexcept
check if an error has occured during parsing.
Definition CommandLineParser.hpp:71
datum parse(std::vector< std::string > const &arguments) noexcept
Parse the arguments.
Definition CommandLineParser.hpp:104
void add(std::string name, datum_type_t type, std::string help, std::function< int(std::string_view)> enum_conversion={}) noexcept
Add a configuration option.
Definition CommandLineParser.hpp:65
void print_help()
Print help text for the command line arguments.
Definition CommandLineParser.hpp:78
T begin(T... args)
T emplace_back(T... args)
T end(T... args)
T find_if(T... args)
T move(T... args)
T push_back(T... args)
T size(T... args)
T stoll(T... args)