Zen C++ Libraries
Zero-dependency re-usable components for C++
Loading...
Searching...
No Matches
po.hpp
1
2#pragma once
3
4#include <algorithm>
5#include <variant>
6#include <string>
7#include <optional>
8#include <functional>
9#include <unordered_map>
10#include <any>
11#include <vector>
12#include <memory>
13#include <typeindex>
14
15#include "zen/config.hpp"
16#include "zen/clone_ptr.hpp"
17#include "zen/config.hpp"
19#include "zen/either.hpp"
20
21ZEN_NAMESPACE_START
22
23namespace po {
24
25 struct posarg {
26 std::string _name;
27 int _arity;
28 };
29
30 template<typename T>
31 class flag {
32
33 friend class command;
34
35 std::string name;
36 std::optional<std::string> description;
37 std::size_t min_count = 1;
38 std::size_t max_count = 1;
39
40 public:
41
42 flag(std::string name, std::optional<std::string> description = {}):
43 name(name), description(description) {}
44
45 flag& optional() {
46 min_count = 0;
47 return *this;
48 }
49
50 flag& required() {
51 min_count = std::max(static_cast<std::size_t>(1), min_count);
52 return *this;
53 }
54
55 };
56
58
59 std::string flag_name;
60
61 void display(std::ostream& out) const {
62 out << "the flag '" << flag_name << "' was not recognised by any of the (sub)commands.";
63 }
64
65 };
66
68
69 std::string flag_name;
70
71 void display(std::ostream& out) const {
72 out << "trying to store a flag value in a type that cannot be parsed.";
73 }
74
75 };
76
78
79 std::string flag_name;
80
81 void display(std::ostream& out) const {
82 out << "no value provided for flag '" << flag_name << "'.";
83 }
84
85 };
86
88
89 std::size_t i;
90 std::string arg;
91
92 void display(std::ostream& out) const {
93 out << "excess positional argument '" << arg << "' found.";
94 }
95
96 };
97
99
100 std::string actual;
101
102 void display(std::ostream& out) const {
103 out << "the command '" << actual << "' was not found.";
104 }
105
106 };
107
108 struct invalid_argument_error {
109
110 std::string_view actual;
111
112 invalid_argument_error(std::string_view actual):
113 actual(actual) {}
114
115 void display(std::ostream& out) const {
116 out << "the argument '" << actual << "' could not be parsed.";
117 }
118
119 };
120
121 struct missing_pos_arg_error {
122
123 posarg expected;
124
125 missing_pos_arg_error(posarg expected):
126 expected(expected) {}
127
128 void display(std::ostream& out) const {
129 out << "a positional argument for " << expected._name << " was missing.";
130 }
131
132 };
133
134 class error {
135
136 using storage_t = std::variant<
144 >;
145
146 storage_t storage;
147
148 public:
149
150 template<typename T>
151 error(T inner):
152 storage(inner) {}
153
154 void display(std::ostream& out) const {
155 // TODO can probably do some metaprogramming to automate this
156 switch (storage.index()) {
157 case 0:
158 std::get<0>(storage).display(out);
159 break;
160 case 1:
161 std::get<1>(storage).display(out);
162 break;
163 case 2:
164 std::get<2>(storage).display(out);
165 break;
166 case 3:
167 std::get<3>(storage).display(out);
168 break;
169 case 4:
170 std::get<4>(storage).display(out);
171 break;
172 case 5:
173 std::get<5>(storage).display(out);
174 break;
175 case 6:
176 std::get<6>(storage).display(out);
177 break;
178 }
179 }
180
181 template<typename T>
182 bool is() const {
183 return std::holds_alternative<T>(storage);
184 }
185
186 template<typename T>
187 T& as() {
188 return std::get<T>(storage);
189 }
190
191 template<typename T>
192 const T& as() const {
193 return std::get<T>(storage);
194 }
195
196 };
197
198 template<typename T>
199 using result = either<error, T>;
200
201 class match {
202
203 friend class program;
204
205 std::unordered_map<std::string, std::any> _flags;
206 std::vector<std::string> _pos_args;
207 std::optional<std::tuple<std::string, clone_ptr<match>>> _subcommand = {};
208
209 void add_flag(std::string name, std::any value) {
210 _flags.emplace(name, value);
211 }
212
213 void add_pos_arg(std::string arg) {
214 _pos_args.push_back(arg);
215 }
216
217 public:
218
219 inline match():
220 _subcommand({}) {}
221
222 inline match(
223 std::unordered_map<std::string, std::any> _flags,
224 std::vector<std::string> _pos_args,
225 std::optional<std::tuple<std::string, clone_ptr<match>>> _subcommand
226 ): _flags(_flags), _pos_args(_pos_args), _subcommand(_subcommand) {}
227
228 match* clone() const {
229 return new match { _flags, _pos_args, _subcommand };
230 }
231
232 std::size_t count_flags() const {
233 return _flags.size();
234 }
235
236 bool has_flag(const std::string& name) const {
237 return _flags.count(name);
238 }
239
240 template<typename T>
241 std::optional<T> get_flag(const std::string& name) const {
242 auto match = _flags.find(name);
243 if (match == _flags.end()) {
244 return {};
245 }
246 return std::any_cast<T>(match->second);
247 }
248
249 std::size_t count_pos_args() const {
250 return _pos_args.size();
251 }
252
253 std::string get_pos_arg(std::size_t i) {
254 return _pos_args[i];
255 }
256
257 auto get_pos_args() const {
258 return make_iterator_range(_pos_args.cbegin(), _pos_args.cend());
259 }
260
261 bool has_subcommand() const noexcept {
262 return _subcommand.has_value();
263 }
264
265 std::tuple<std::string, clone_ptr<match>>& subcommand() {
266 ZEN_ASSERT(_subcommand);
267 return *_subcommand;
268 }
269
270 };
271
272 using command_callback_t = std::function<int(const match&)>;
273
274 static constexpr const int opt = -3;
275 static constexpr const int some = -2;
276 static constexpr const int many = -1;
277
278 struct _flag_info {
279 std::optional<std::string> description;
280 std::type_index type;
281 std::size_t min_count;
282 std::size_t max_count;
283 };
284
285 class command {
286
287 friend class program;
288
289 protected:
290
291 std::string _name;
292 std::optional<std::string> _description;
293 std::unordered_map<std::string, _flag_info> _flags;
294 std::vector<command> _subcommands;
295 std::vector<posarg> _pos_args;
296 bool _is_fallback = false;
297 std::optional<command_callback_t> _callback;
298
299 public:
300
301 inline command(std::string name, std::optional<std::string> description = {}):
302 _name(name), _description(description) {}
303
304 command& description(std::string description) {
305 _description = description;
306 return *this;
307 }
308
309 template<typename T>
310 command& flag(flag<T> fl) {
311 _flags.emplace(fl.name, _flag_info { fl.description, typeid(T), fl.min_count, fl.max_count });
312 return *this;
313 }
314
315 command& action(command_callback_t callback) {
316 _callback = callback;
317 return *this;
318 }
319
320 command& fallback() {
321 _is_fallback = true;
322 return *this;
323 }
324
325 command& subcommand(command cmd) {
326 _subcommands.push_back(cmd);
327 return *this;
328 }
329
330 command& pos_arg(std::string name, int arity = 1) {
331 _pos_args.push_back(posarg(name, arity));
332 return *this;
333 }
334
335 };
336
337 inline bool starts_with(const std::string_view& str, const std::string_view& needle) {
338 auto it1 = str.begin();
339 auto it2 = needle.begin();
340 for (;;) {
341 if (it2 == needle.end()) {
342 return true;
343 }
344 if (it1 == str.end() || *it1 != *it2) {
345 return false;
346 }
347 ++it1;
348 ++it2;
349 }
350 }
351
352 class program {
353
354 command _command;
355
356 public:
357
358 inline program(std::string name, std::optional<std::string> description = {}):
359 _command(name, description) {}
360
361 program& description(std::string description) {
362 _command.description(description);
363 return *this;
364 }
365
366 program& subcommand(command cmd) {
367 _command.subcommand(cmd);
368 return *this;
369 }
370
371 template<typename T>
372 program& flag(flag<T> fl) {
373 _command.flag(fl);
374 return *this;
375 }
376
377 program& pos_arg(std::string name, int arity = 1) {
378 _command.pos_arg(name, arity);
379 return *this;
380 }
381
382 result<match> parse_args(std::vector<std::string_view> argv);
383
384 result<match> parse_args(int argc, const char** argv);
385
386 };
387
388}
389
390ZEN_NAMESPACE_END
Definition clone_ptr.hpp:46
A type for computations that may fail.
Definition either.hpp:124
Definition po.hpp:285
Definition po.hpp:201
Definition value.hpp:34
Encapsulation for computations that may fail.
Support for creating ranges over iterators.
auto make_iterator_range(IterT &&a, IterT &&b)
Definition iterator_range.hpp:92
Definition po.hpp:278
Definition po.hpp:98
Definition po.hpp:77
Definition po.hpp:108
Definition po.hpp:121
Definition po.hpp:25
Definition po.hpp:57
Definition po.hpp:67