bux API Reference 1.9.0
Static library of whatever are seen required in general purpose but not directly supported from Modern C++. Or whatever reusable originated from my side projects.
Loading...
Searching...
No Matches
EZArgs.h
Go to the documentation of this file.
1#pragma once
2
3#include "XException.h" // RUNTIME_ERROR()
4#include <algorithm> // std::sort()
5#include <concepts> // std::integral<>, std::invocable<>
6#include <cstring> // std::strcmp()
7#include <deque> // std::deque<>
8#include <functional> // std::function<>
9#include <list> // std::list<>
10#include <optional> // std::optional<>
11#include <ranges> // std::ranges::forward_range<>, std::ranges::views::empty<>
12#include <string> // std::to_string()
13#include <string_view> // std::string_view
14#include <variant> // std::variant<>, std::monostate
15#include <vector> // std::vector<>
16
17namespace bux {
18
19//
20// Types
21//
23{
24 // Data
25 std::string m_message;
26 std::optional<size_t> m_optIndex;
27
28 // Nonvirtuals
29 C_ErrorOrIndex(const std::string &error, auto flagErrInd): m_message(error), m_optIndex(flagErrInd) {}
30 C_ErrorOrIndex(const std::string &help): m_message(help) {}
31 C_ErrorOrIndex(auto flagStartInd): m_optIndex(flagStartInd) {}
32 operator bool() const { return m_message.empty(); }
33 auto index() const { return m_optIndex.value(); }
34 std::string message() const;
35};
36
50{
51public:
52
53 // Ctors
54 explicit C_EZArgs(const std::string &description = {}): m_desc(description) {}
55 C_EZArgs(const C_EZArgs&) = delete;
56 C_EZArgs &operator=(const C_EZArgs&) = delete;
57
58 // Nonvirtuals - for syntax & help layout
59 C_EZArgs &add_flag(std::string_view name, char short_name,
60 std::string_view description,
61 std::invocable<> auto &&trigger,
62 std::invocable<std::string_view> auto &&parse){
63 auto &def = create_flag_def(name, short_name, description);
64 def.m_trigger = std::move(trigger);
65 def.m_parse = std::move(parse);
66 return *this;
67 }
68 C_EZArgs &add_flag(std::string_view name, char short_name,
69 std::string_view description,
70 std::invocable<> auto &&trigger){
71 create_flag_def(name, short_name, description).m_trigger = std::move(trigger);
72 return *this;
73 }
74 C_EZArgs &add_flag(std::string_view name, char short_name,
75 std::string_view description,
76 std::invocable<std::string_view> auto &&parse){
77 create_flag_def(name, short_name, description).m_parse = std::move(parse);
78 return *this;
79 }
80 C_EZArgs &add_flag(std::string_view name,
81 std::string_view description,
82 std::invocable<> auto trigger,
83 std::invocable<std::string_view> auto &&parse) {
84 auto &def = create_flag_def(name, char(), description);
85 def.m_trigger = std::move(trigger);
86 def.m_parse = std::move(parse);
87 return *this;
88 }
89 C_EZArgs &add_flag(std::string_view name,
90 std::string_view description,
91 std::invocable<> auto &&trigger) {
92 create_flag_def(name, char(), description).m_trigger = std::move(trigger);
93 return *this;
94 }
95 C_EZArgs &add_flag(std::string_view name,
96 std::string_view description,
97 std::invocable<std::string_view> auto &&parse){
98 create_flag_def(name, char(), description).m_parse = std::move(parse);
99 return *this;
100 }
101 C_EZArgs &add_flag(char short_name,
102 std::string_view description,
103 std::invocable<> auto &&trigger,
104 std::invocable<std::string_view> auto &&parse) {
105 auto &def = create_flag_def({}, short_name, description);
106 def.m_trigger = std::move(trigger);
107 def.m_parse = std::move(parse);
108 return *this;
109 }
110 C_EZArgs &add_flag(char short_name,
111 std::string_view description,
112 std::invocable<> auto &&trigger) {
113 create_flag_def({}, short_name, description).m_trigger = std::move(trigger);
114 return *this;
115 }
116 C_EZArgs &add_flag(char short_name,
117 std::string_view description,
118 std::invocable<std::string_view> auto &&parse) {
119 create_flag_def({}, short_name, description).m_parse = std::move(parse);
120 return *this;
121 }
122 //
123 C_EZArgs &add_subcommand(const std::string &name, std::invocable<> auto onParsed, const std::string &description = {});
124 void details(std::string_view s) { m_details = s; }
125 //
126 C_EZArgs &position_args(const std::ranges::forward_range auto &arg_names,
127 const std::ranges::forward_range auto &count_optionals, bool unlimited = false);
128 C_EZArgs &position_args(const std::ranges::forward_range auto &arg_names, bool unlimited = false)
129 { return position_args(arg_names, std::ranges::views::empty<size_t>, unlimited); }
130
131 // Nonvirtuals - for parsing
132 [[nodiscard]]C_ErrorOrIndex parse(std::integral auto argc, const char *const argv[]) const;
133 auto parsed_position_argc() const { return m_totoalPositionArgs; }
134
135private:
136
137 // Types
138 struct C_FlagDef
139 {
140 std::string m_name, m_descOneLiner;
141 char m_shortName{};
142 std::function<void()> m_trigger;
143 std::function<void(std::string_view)> m_parse;
144 };
145
146 struct C_ArgLayout
147 {
148 std::vector<std::string> m_posArgs;
149 std::vector<size_t> m_posCounts;
150 bool m_unlimited;
151 };
152 using C_ArgLayoutMap = std::list<std::pair<std::string,C_EZArgs>>;
153
154 using C_UP2U = std::variant<std::monostate,C_ArgLayout,C_ArgLayoutMap>;
155 enum
156 {
157 UP2U_NULL,
158 UP2U_LAYOUT,
159 UP2U_SUBCMD
160 };
161
162 // Data
163 const std::string m_desc;
164 std::string m_details;
165 std::deque<C_FlagDef> m_flags;
166 C_UP2U m_up2u;
167 C_EZArgs *m_owner{};
168 std::function<void()> m_onParsed;
169 size_t mutable m_totoalPositionArgs{};
170 bool m_helpShielded{false};
171 bool m_hShielded{false};
172
173 // Compile-time assertions
174 static_assert(std::variant_size_v<C_UP2U> == 3);
175 static_assert(std::is_same_v<std::monostate, std::variant_alternative_t<UP2U_NULL, C_UP2U>>);
176 static_assert(std::is_same_v<C_ArgLayout, std::variant_alternative_t<UP2U_LAYOUT, C_UP2U>>);
177 static_assert(std::is_same_v<C_ArgLayoutMap, std::variant_alternative_t<UP2U_SUBCMD, C_UP2U>>);
178
179 // Nonvirtuals
180 C_FlagDef &create_flag_def(std::string_view name, char short_name, std::string_view description);
181 const C_FlagDef *find_shortname_def(char sname) const;
182 const C_FlagDef* find_longname_def(std::string_view name) const;
183 bool is_valid_flag(const char *const *argv_rest, int argc_rest) const;
184 std::string help_flags() const;
185 C_ErrorOrIndex help_full(const char *const argv[]) const;
186 std::string help_tip(const std::string &error, const char *const argv[]) const;
187 std::string retro_path(const char *const argv[]) const;
188};
189
190//
191// Implement Member Templates
192//
193C_EZArgs &C_EZArgs::add_subcommand(const std::string &name, std::invocable<> auto onParsed, const std::string &description)
200{
201 switch (m_up2u.index())
202 {
203 case UP2U_NULL:
204 m_up2u.emplace<UP2U_SUBCMD>(); // become UP2U_SUBCMD
205 break;
206 case UP2U_SUBCMD:
207 break;
208 case UP2U_LAYOUT:
209 RUNTIME_ERROR("Already set as positional arguments");
210 }
211 auto &ret = std::get<UP2U_SUBCMD>(m_up2u).emplace_back(name, description).second;
212
213 ret.m_helpShielded = m_helpShielded;
214 ret.m_hShielded = m_hShielded;
215 ret.m_owner = this;
216 ret.m_onParsed = onParsed;
217 return ret;
218}
219
221 const std::ranges::forward_range auto &arg_names,
222 const std::ranges::forward_range auto &count_optionals,
223 bool unlimited )
234{
235 if (!std::empty(arg_names) || unlimited)
236 // Non-trivial case
237 {
238 switch (m_up2u.index())
239 {
240 case 0: // turn case 1
241 m_up2u.emplace<C_ArgLayout>();
242 break;
243 case 1:
244 break;
245 case 2:
246 RUNTIME_ERROR("Already added subcommands");
247 }
248 auto &dst = std::get<1>(m_up2u);
249 dst.m_posArgs.assign(std::begin(arg_names), std::end(arg_names));
250 dst.m_unlimited = unlimited;
251
252 // Assign & adjust m_posCounts
253 dst.m_posCounts.assign(std::begin(count_optionals), std::end(count_optionals));
254 sort(dst.m_posCounts.begin(), dst.m_posCounts.end());
255 const auto n_args = dst.m_posArgs.size();
256 while (!dst.m_posCounts.empty() && dst.m_posCounts.back() >= n_args)
257 dst.m_posCounts.pop_back();
258
259 dst.m_posCounts.emplace_back(n_args);
260 }
261 return *this;
262}
263
264C_ErrorOrIndex C_EZArgs::parse(std::integral auto argc, const char *const argv[]) const
271{
272 decltype(argc) ind = 1;
273 switch (m_up2u.index())
274 {
275 case 0: // trivial case
276 break;
277 case 1: // with positional arguments
278 for (; ind < argc && argv[ind][0] != '-'; ++ind);
279 m_totoalPositionArgs = ind;
280 if (ind < argc && (argv[ind][1] == 'h' || !strcmp(argv[ind], "--help")))
281 return help_full(argv);
282 else
283 {
284 auto &lo = std::get<1>(m_up2u);
285 decltype(argc) lower_bound = 0;
286 for (auto i: lo.m_posCounts)
287 {
288 if (lower_bound < ind && ind <= static_cast<decltype(argc)>(i))
289 {
290 std::string message;
291 if (!lower_bound)
292 {
293 const bool plural = i > 1;
294 message = "Positional argument";
295 if (plural)
296 message += 's';
297 for (size_t j = 0; j < i; ++j)
298 message.append(" <").append(lo.m_posArgs[j]) += '>';
299
300 message += plural? " are required": " is required";
301 }
302 else
303 message = std::to_string(ind-1).append(ind > 2? " positonal arguments are": " positonal argument is").append(" not allowed");
304
305 return {help_tip(message,argv), ind};
306 }
307 lower_bound = static_cast<decltype(argc)>(i + 1);
308 }
309 if (!lo.m_unlimited && ind > static_cast<decltype(argc)>(lo.m_posArgs.size() + 1))
310 {
311 std::string message = "Too many positional arguments:";
312 for (auto &i: lo.m_posArgs)
313 message.append(" <").append(i) += '>';
314 return {help_tip(message,argv), ind};
315 }
316 }
317 break;
318 case 2: // into subcommands
319 if (ind >= argc || !strcmp(argv[ind],"-h") || !strcmp(argv[ind],"--help"))
320 return help_full(argv);
321 else
322 {
323 for (auto &i: std::get<2>(m_up2u))
324 if (i.first == argv[ind])
325 {
326 auto ret = i.second.parse(argc-ind, argv+ind);
327 if (ret)
328 {
329 m_totoalPositionArgs = i.second.m_totoalPositionArgs + ind;
330 return ret.m_optIndex.value() + ind;
331 }
332 return ret;
333 }
334
335 return {help_tip(std::string("Unknown subcommand: ")+argv[ind], argv), ind};
336 }
337 }
338 const auto flagStartInd = ind;
339
340 // Parse un-ordered flags
341 for (; ind < argc; ++ind)
342 {
343 auto arg = argv[ind];
344 if (arg[0] != '-')
345 // Not flag initial
346 return {help_tip(std::string("Unexpected argument: ")+arg, argv), ind};
347
348 if (arg[1] == '-')
349 // Match full flag name
350 {
351 const auto flag = arg + 2;
352 const auto eqsign = strchr(flag, '=');
353 const auto flag_name = eqsign? std::string_view(flag, size_t(eqsign-flag)): std::string_view(flag);
354 if (auto def = find_longname_def(flag_name))
355 {
356 if (eqsign)
357 // (=)-connected flag value
358 {
359 if (def->m_parse)
360 {
361 def->m_parse(eqsign+1);
362 goto NextInd;
363 }
364 return {help_tip(std::string("Value parser absent: ")+arg, argv), ind};
365 }
366 else if (ind+1 < argc && !is_valid_flag(&argv[ind+1], int(argc-(ind+1))))
367 // Flag with value
368 {
369 if (def->m_parse)
370 {
371 def->m_parse(argv[++ind]);
372 goto NextInd;
373 }
374 return {help_tip(std::string("Value parser absent: ")+arg, argv), ind};
375 }
376 else
377 // Flag without value
378 {
379 if (def->m_trigger)
380 {
381 def->m_trigger();
382 goto NextInd;
383 }
384 return {help_tip(std::string(def->m_parse?"Missing flag value: ":"Triggerless flag: ")+arg, argv), ind};
385 }
386 }
387 if ("help" == flag_name)
388 return help_full(argv);
389 else
390 return {help_tip(std::string("Unknown flag: --")+=flag_name, argv), ind};
391 } // if (arg[1] == '-')
392 else
393 // Match continous short flag names
394 while (char sname = *++arg)
395 {
396 if (auto def = find_shortname_def(sname))
397 {
398 if (!arg[1] && ind+1 < argc && !is_valid_flag(&argv[ind+1], int(argc-(ind+1))))
399 // Flag with value
400 {
401 if (def->m_parse)
402 {
403 def->m_parse(argv[++ind]);
404 goto NextInd;
405 }
406 return {help_tip(std::string("Value parser absent: -")+sname, argv), ind};
407 }
408 else
409 // Flag without value
410 {
411 if (def->m_trigger)
412 {
413 def->m_trigger();
414 goto NextInd;
415 }
416 return {help_tip(std::string(def->m_parse?"Missing flag value: -":"Triggerless flag: -")+sname, argv), ind};
417 }
418 }
419 if ('h' == sname)
420 return help_full(argv);
421 else
422 return {help_tip(std::string("Unknown flag: -")+=sname, argv), ind};
423 } // while (char sname = *++arg)
424 NextInd:;
425 } // for (; ind < argc; ++ind)
426
427 if (m_onParsed)
428 m_onParsed();
429
430 return flagStartInd;
431}
432
433} //namespace bux
#define RUNTIME_ERROR(fmtStr,...)
Wrap FILE(DATE)#__LINE__ FUNCTION: msg into std::runtime_error.
Definition XException.h:32
C_EZArgs & add_subcommand(const std::string &name, std::invocable<> auto onParsed, const std::string &description={})
Definition EZArgs.h:193
C_EZArgs & add_flag(char short_name, std::string_view description, std::invocable<> auto &&trigger)
Definition EZArgs.h:110
C_EZArgs & add_flag(std::string_view name, char short_name, std::string_view description, std::invocable<> auto &&trigger)
Definition EZArgs.h:68
C_EZArgs & add_flag(char short_name, std::string_view description, std::invocable<> auto &&trigger, std::invocable< std::string_view > auto &&parse)
Definition EZArgs.h:101
auto parsed_position_argc() const
Definition EZArgs.h:133
C_EZArgs(const C_EZArgs &)=delete
C_EZArgs & add_flag(std::string_view name, std::string_view description, std::invocable<> auto trigger, std::invocable< std::string_view > auto &&parse)
Definition EZArgs.h:80
C_EZArgs & add_flag(std::string_view name, char short_name, std::string_view description, std::invocable< std::string_view > auto &&parse)
Definition EZArgs.h:74
C_ErrorOrIndex parse(std::integral auto argc, const char *const argv[]) const
Definition EZArgs.h:264
C_EZArgs & add_flag(char short_name, std::string_view description, std::invocable< std::string_view > auto &&parse)
Definition EZArgs.h:116
C_EZArgs & add_flag(std::string_view name, std::string_view description, std::invocable< std::string_view > auto &&parse)
Definition EZArgs.h:95
C_EZArgs & operator=(const C_EZArgs &)=delete
C_EZArgs & add_flag(std::string_view name, std::string_view description, std::invocable<> auto &&trigger)
Definition EZArgs.h:89
C_EZArgs(const std::string &description={})
Definition EZArgs.h:54
C_EZArgs & add_flag(std::string_view name, char short_name, std::string_view description, std::invocable<> auto &&trigger, std::invocable< std::string_view > auto &&parse)
Definition EZArgs.h:59
C_EZArgs & position_args(const std::ranges::forward_range auto &arg_names, const std::ranges::forward_range auto &count_optionals, bool unlimited=false)
Definition EZArgs.h:220
C_EZArgs & position_args(const std::ranges::forward_range auto &arg_names, bool unlimited=false)
Definition EZArgs.h:128
void details(std::string_view s)
Definition EZArgs.h:124
THE common namespace of bux library.
Definition AtomiX.cpp:3
std::string m_message
Definition EZArgs.h:25
C_ErrorOrIndex(const std::string &error, auto flagErrInd)
Definition EZArgs.h:29
std::string message() const
Definition EZArgs.cpp:10
C_ErrorOrIndex(const std::string &help)
Definition EZArgs.h:30
auto index() const
Definition EZArgs.h:33
std::optional< size_t > m_optIndex
Definition EZArgs.h:26
C_ErrorOrIndex(auto flagStartInd)
Definition EZArgs.h:31