HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
CommandLineParser.hpp
1// Copyright 2019 Pokitec
2// All rights reserved.
3
4#pragma once
5
6#include "TTauri/Foundation/datum.hpp"
7#include <string>
8#include <vector>
9#include <map>
10#include <functional>
11#include <string_view>
12#include <iostream>
13
14namespace tt {
15
16using namespace std::literals;
17
23 struct option_t {
26 std::string name;
27
30 datum_type_t type;
31
34 std::string help;
35
39 std::function<int(std::string_view)> enum_conversion;
40
41 option_t(std::string name, datum_type_t type, std::string help, std::function<int(std::string_view)> enum_conversion) :
42 name(std::move(name)), type(type), help(std::move(help)), enum_conversion(std::move(enum_conversion)) {}
43 };
44
45 // The synopsis of the application to be printed on --help and error.
46 std::string synopsis;
47
48 // A list of options.
50
52 std::vector<std::string> error_messages;
53
54public:
55 CommandLineParser(std::string synopsis) : synopsis(std::move(synopsis)) {}
56
64 void add(std::string name, datum_type_t type, std::string help, std::function<int(std::string_view)> enum_conversion = {}) noexcept {
65 options.emplace_back(std::move(name), type, std::move(help), std::move(enum_conversion));
66 }
67
70 bool has_error() const noexcept {
71 return error_messages.size() > 0;
72 }
73
77 void print_help() {
78 for (ttlet &error_message: error_messages) {
79 std::cerr << error_message << "\n";
80 }
81 if (has_error()) {
82 std::cerr << "\n";
83 }
84
85 std::cerr << synopsis << "\n";
86
87 for (ttlet &option: options) {
88 ttlet example = fmt::format("--{}=<{}>", option.name, option.type);
89 std::cerr << fmt::format(" {:20s} {}\n", example, option.help);
90 }
91 std::cerr.flush();
92 }
93
103 datum parse(std::vector<std::string> const &arguments) noexcept {
104 auto r = datum{datum::map{}};
105
106 int argumentCount = 0;
107 for (ttlet &argument: arguments) {
108 if (argumentCount++ == 0) {
109 r["executable-path"] = arguments[0];
110
111 } else if (starts_with(argument, "--"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(fmt::format("Unknown option '{}'", option_name));
122
123 } else if (option->type != datum_type_t::Boolean) {
124 error_messages.push_back(fmt::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(fmt::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 fmt::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 fmt::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 fmt::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:20
bool has_error() const noexcept
check if an error has occured during parsing.
Definition CommandLineParser.hpp:70
datum parse(std::vector< std::string > const &arguments) noexcept
Parse the arguments.
Definition CommandLineParser.hpp:103
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:64
void print_help()
Print help text for the command line arguments.
Definition CommandLineParser.hpp:77
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)