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
22 struct option_t {
25 std::string name;
26
29 datum_type_t type;
30
33 std::string help;
34
38 std::function<int(std::string_view)> enum_conversion;
39
40 option_t(std::string name, datum_type_t type, std::string help, std::function<int(std::string_view)> enum_conversion) :
41 name(std::move(name)), type(type), help(std::move(help)), enum_conversion(std::move(enum_conversion)) {}
42 };
43
44 // The synopsis of the application to be printed on --help and error.
45 std::string synopsis;
46
47 // A list of options.
49
51 std::vector<std::string> error_messages;
52
53public:
54 CommandLineParser(std::string synopsis) : synopsis(std::move(synopsis)) {}
55
63 void add(std::string name, datum_type_t type, std::string help, std::function<int(std::string_view)> enum_conversion = {}) noexcept {
64 options.emplace_back(std::move(name), type, std::move(help), std::move(enum_conversion));
65 }
66
69 bool has_error() const noexcept {
70 return error_messages.size() > 0;
71 }
72
76 void print_help() {
77 for (ttlet &error_message: error_messages) {
78 std::cerr << error_message << "\n";
79 }
80 if (has_error()) {
81 std::cerr << "\n";
82 }
83
84 std::cerr << synopsis << "\n";
85
86 for (ttlet &option: options) {
87 ttlet example = std::format("--{}=<{}>", option.name, option.type);
88 std::cerr << std::format(" {:20s} {}\n", example, option.help);
89 }
90 std::cerr.flush();
91 }
92
102 datum parse(int argc, char const * const argv[]) noexcept {
103 auto r = datum{datum::map{}};
104
105 for (int arg_index = 0; arg_index != argc; ++arg_index) {
106 auto argument = std::string(argv[arg_index]);
107
108 if (arg_index == 0) {
109 r["executable-path"] = argument;
110
111 } else if (argument.starts_with("--"s)) {
112 ttlet i = argument.find('=');
113 if (i == argument.npos) {
114 ttlet option_name = argument.substr(2);
115
116 ttlet &option = std::find_if(options.begin(), options.end(), [&](auto x) {
117 return x.name == option_name;
118 });
119
120 if (option == options.end()) {
121 error_messages.push_back(std::format("Unknown option '{}'", option_name));
122
123 } else if (option->type != datum_type_t::Boolean) {
124 error_messages.push_back(std::format("Option '{}' requires an argument", option_name));
125
126 } else {
127 r[option_name] = true;
128 }
129
130 } else {
131 ttlet option_name = argument.substr(2, i-2);
132 ttlet option_value_string = argument.substr(i+1);
133
134 ttlet &option = std::find_if(options.begin(), options.end(), [&](auto x) {
135 return x.name == option_name;
136 });
137
138 if (option == options.end()) {
139 error_messages.push_back(std::format("Unknown option '{}'", option_name));
140
141 } else {
142 switch (option->type) {
143 case datum_type_t::Boolean:
144 if (option_value_string == "true") {
145 r[option_name] = true;
146 } else if (option_value_string == "false") {
147 r[option_name] = false;
148 } else {
149 error_messages.push_back(
150 std::format("Expected a boolean value ('true' or 'false') for option '{}' got '{}'", option_name, option_value_string)
151 );
152 }
153 break;
154
155 case datum_type_t::Integer:
156 if (option->enum_conversion) {
157 ttlet option_value_int = option->enum_conversion(option_value_string);
158 if (option_value_int >= 0) {
159 r[option_name] = option_value_int;
160 } else {
161 error_messages.push_back(
162 std::format("Unknown value '{}' for option '{}'", option_value_string, option_name)
163 );
164 }
165
166 } else {
167 try {
168 ttlet option_value_int = std::stoll(option_value_string);
169 r[option_name] = option_value_int;
170 } catch (...) {
171 error_messages.push_back(
172 std::format("Expected a integer value for option '{}' got '{}'", option_name, option_value_string)
173 );
174 }
175 }
176 break;
177
178 case datum_type_t::String:
179 r[option_name] = option_value_string;
180 break;
181
182 case datum_type_t::Vector:
183 r[option_name].push_back(datum{option_value_string});
184 break;
185
186 case datum_type_t::URL:
187 r[option_name] = URL::urlFromCurrentWorkingDirectory().urlByAppendingPath(option_value_string);
188 break;
189
190 default:
191 tt_no_default();
192 }
193 }
194 }
195
196 } else {
197 r["arguments"].push_back(argument);
198 }
199 }
200 return r;
201 }
202};
203
204}
A parser to parse command line arguments.
Definition CommandLineParser.hpp:19
bool has_error() const noexcept
check if an error has occured during parsing.
Definition CommandLineParser.hpp:69
datum parse(int argc, char const *const argv[]) noexcept
Parse the arguments.
Definition CommandLineParser.hpp:102
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:63
void print_help()
Print help text for the command line arguments.
Definition CommandLineParser.hpp:76
A dynamic data type.
Definition datum.hpp:213
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)