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 hi::inline v1 {
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 };
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
66 add(std::string name, datum_type_t type, std::string help, std::function<int(std::string_view)> enum_conversion = {}) noexcept
67 {
68 options.emplace_back(std::move(name), type, std::move(help), std::move(enum_conversion));
69 }
70
73 bool has_error() const noexcept
74 {
75 return error_messages.size() > 0;
76 }
77
82 {
83 for (hilet &error_message : error_messages) {
84 std::cerr << error_message << "\n";
85 }
86 if (has_error()) {
87 std::cerr << "\n";
88 }
89
90 std::cerr << synopsis << "\n";
91
92 for (hilet &option : options) {
93 hilet example = std::format("--{}=<{}>", option.name, option.type);
94 std::cerr << std::format(" {:20s} {}\n", example, option.help);
95 }
96 std::cerr.flush();
97 }
98
108 datum parse(int argc, char const *const argv[]) noexcept
109 {
110 auto r = datum{datum::map{}};
111
112 for (int arg_index = 0; arg_index != argc; ++arg_index) {
113 auto argument = std::string(argv[arg_index]);
114
115 if (arg_index == 0) {
116 r["executable-path"] = argument;
117
118 } else if (argument.starts_with("--"s)) {
119 hilet i = argument.find('=');
120 if (i == argument.npos) {
121 hilet option_name = argument.substr(2);
122
123 hilet &option = std::find_if(options.begin(), options.end(), [&](auto x) {
124 return x.name == option_name;
125 });
126
127 if (option == options.end()) {
128 error_messages.push_back(std::format("Unknown option '{}'", option_name));
129
130 } else if (option->type != datum_type_t::Boolean) {
131 error_messages.push_back(std::format("Option '{}' requires an argument", option_name));
132
133 } else {
134 r[option_name] = true;
135 }
136
137 } else {
138 hilet option_name = argument.substr(2, i - 2);
139 hilet option_value_string = argument.substr(i + 1);
140
141 hilet &option = std::find_if(options.begin(), options.end(), [&](auto x) {
142 return x.name == option_name;
143 });
144
145 if (option == options.end()) {
146 error_messages.push_back(std::format("Unknown option '{}'", option_name));
147
148 } else {
149 switch (option->type) {
150 case datum_type_t::Boolean:
151 if (option_value_string == "true") {
152 r[option_name] = true;
153 } else if (option_value_string == "false") {
154 r[option_name] = false;
155 } else {
156 error_messages.push_back(std::format(
157 "Expected a boolean value ('true' or 'false') for option '{}' got '{}'",
158 option_name,
159 option_value_string));
160 }
161 break;
162
163 case datum_type_t::Integer:
164 if (option->enum_conversion) {
165 hilet option_value_int = option->enum_conversion(option_value_string);
166 if (option_value_int >= 0) {
167 r[option_name] = option_value_int;
168 } else {
169 error_messages.push_back(
170 std::format("Unknown value '{}' for option '{}'", option_value_string, option_name));
171 }
172
173 } else {
174 try {
175 hilet option_value_int = std::stoll(option_value_string);
176 r[option_name] = option_value_int;
177 } catch (...) {
178 error_messages.push_back(std::format(
179 "Expected a integer value for option '{}' got '{}'", option_name, option_value_string));
180 }
181 }
182 break;
183
184 case datum_type_t::String: r[option_name] = option_value_string; break;
185
186 case datum_type_t::Vector: r[option_name].push_back(datum{option_value_string}); break;
187
188 case datum_type_t::URL:
189 r[option_name] = URL::urlFromCurrentWorkingDirectory().urlByAppendingPath(option_value_string);
190 break;
191
192 default: hi_no_default();
193 }
194 }
195 }
196
197 } else {
198 r["arguments"].push_back(argument);
199 }
200 }
201 return r;
202 }
203};
204
205} // namespace hi::inline v1
#define hilet
Invariant should be the default for variables.
Definition required.hpp:23
A parser to parse command line arguments.
Definition CommandLineParser.hpp:19
datum parse(int argc, char const *const argv[]) noexcept
Parse the arguments.
Definition CommandLineParser.hpp:108
bool has_error() const noexcept
check if an error has occured during parsing.
Definition CommandLineParser.hpp:73
void print_help()
Print help text for the command line arguments.
Definition CommandLineParser.hpp:81
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:66
A dynamic data type.
Definition datum.hpp:209
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)