poaflocParser Of Arguments For Lines Of Commands |
git clone git://git.dimitrijedobrota.com/poafloc.git |
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING |
poafloc.hpp (14155B)
0 #pragma once
2 #include <functional>
3 #include <initializer_list>
4 #include <memory>
5 #include <optional>
6 #include <span>
7 #include <sstream>
8 #include <string>
9 #include <string_view>
11 #include <based/concepts/is/same.hpp>
12 #include <based/container/array.hpp>
13 #include <based/container/vector.hpp>
14 #include <based/trait/integral_constant.hpp>
15 #include <based/trait/remove/cvref.hpp>
16 #include <based/types/literals.hpp>
17 #include <based/types/types.hpp>
18 #include <based/utility/forward.hpp>
19 #include <based/utility/move.hpp>
21 #include "poafloc/error.hpp"
23 namespace poafloc
24 {
26 using namespace based::literals; // NOLINT(*namespace*)
28 namespace detail
29 {
31 class option
32 {
33 public:
34 enum class type : based::bu8
35 {
36 argument,
37 direct,
38 optional,
39 boolean,
40 list,
41 };
43 private:
44 using func_type = std::function<void(void*, std::string_view)>;
46 using size_type = based::u64;
48 type m_type;
49 func_type m_func;
51 based::character m_opt_short;
52 std::string m_opt_long;
54 std::string m_name;
55 std::string m_message;
57 protected:
58 // used for args
59 explicit option(type opt_type, func_type func, std::string_view help);
61 // used for options
62 explicit option(
63 type opt_type,
64 std::string_view opts,
65 func_type func,
66 std::string_view help
67 );
69 template<class Record, class Type, class Member = Type Record::*>
70 static auto create(Member member)
71 {
72 return [member](void* record_raw, std::string_view value)
73 {
74 auto* record = static_cast<Record*>(record_raw);
75 if constexpr (std::is_invocable_v<Member, Record, std::string_view>) {
76 std::invoke(member, record, value);
77 } else if constexpr (std::is_invocable_v<Member, Record, Type>) {
78 std::invoke(member, record, convert<Type>(value));
79 } else if constexpr (std::is_assignable_v<Type, std::string_view>) {
80 std::invoke(member, record) = value;
81 } else {
82 std::invoke(member, record) = convert<Type>(value);
83 }
84 };
85 }
87 template<class T>
88 static T convert(std::string_view value)
89 {
90 T tmp;
91 auto istr = std::istringstream(std::string(value));
92 istr >> tmp;
93 return tmp;
94 }
96 public:
97 [[nodiscard]] bool has_opt_long() const { return !m_opt_long.empty(); }
98 [[nodiscard]] bool has_opt_short() const { return m_opt_short != '\0'; }
100 [[nodiscard]] const std::string& opt_long() const { return m_opt_long; }
101 [[nodiscard]] based::character opt_short() const { return m_opt_short; }
103 [[nodiscard]] const std::string& name() const { return m_name; }
104 [[nodiscard]] const std::string& message() const { return m_message; }
105 [[nodiscard]] type get_type() const { return m_type; }
107 void operator()(void* record, std::string_view value) const
108 {
109 m_func(record, value);
110 }
111 };
113 template<class T>
114 using rec_type = typename based::remove_cvref_t<T>::rec_type;
116 template<class T, class... Rest>
117 concept SameRec = (based::SameAs<rec_type<T>, rec_type<Rest>> && ...);
119 } // namespace detail
121 template<class Record, class Type>
122 requires(!based::SameAs<bool, Type>)
123 class argument : public detail::option
124 {
125 using base = detail::option;
126 using member_type = Type Record::*;
128 public:
129 using rec_type = Record;
131 explicit argument(std::string_view name, member_type member)
132 : base(
133 base::type::argument,
134 base::template create<Record, Type>(member),
135 name
136 )
137 {
138 }
139 };
141 template<class Record, class Type>
142 requires(!based::SameAs<bool, Type>)
143 class argument_list : public detail::option
144 {
145 using base = detail::option;
146 using member_type = Type Record::*;
148 public:
149 using rec_type = Record;
151 explicit argument_list(std::string_view name, member_type member)
152 : base(
153 base::type::list, base::template create<Record, Type>(member), name
154 )
155 {
156 }
157 };
159 template<class Record, class Type>
160 requires(!based::SameAs<bool, Type>)
161 class direct : public detail::option
162 {
163 using base = detail::option;
164 using member_type = Type Record::*;
166 public:
167 using rec_type = Record;
169 explicit direct(
170 std::string_view opts, member_type member, std::string_view help
171 )
172 : base(
173 base::type::direct,
174 opts,
175 base::template create<Record, Type>(member),
176 help
177 )
178 {
179 }
180 };
182 template<class Record, class Type>
183 class boolean : public detail::option
184 {
185 using base = detail::option;
186 using member_type = Type Record::*;
188 static auto create(member_type member)
189 {
190 return [member](void* record_raw, std::string_view value)
191 {
192 auto* record = static_cast<Record*>(record_raw);
193 if constexpr (std::is_invocable_v<member_type, Record, std::string_view>)
194 {
195 std::invoke(member, record, value);
196 } else if constexpr (std::is_invocable_v<member_type, Record, bool>) {
197 std::invoke(member, record, true);
198 } else if constexpr (std::is_assignable_v<Type, std::string_view>) {
199 std::invoke(member, record) = value;
200 } else {
201 std::invoke(member, record) = true;
202 }
203 };
204 }
206 public:
207 using rec_type = Record;
209 explicit boolean(
210 std::string_view opts, member_type member, std::string_view help
211 )
212 : base(base::type::boolean, opts, create(member), help)
213 {
214 }
215 };
217 template<class Record, class Type>
218 requires(!based::SameAs<bool, Type>)
219 class list : public detail::option
220 {
221 using base = detail::option;
222 using member_type = Type Record::*;
224 public:
225 using rec_type = Record;
227 explicit list(
228 std::string_view opts, member_type member, std::string_view help
229 )
230 : base(
231 base::type::list,
232 opts,
233 base::template create<Record, Type>(member),
234 help
235 )
236 {
237 }
238 };
240 namespace detail
241 {
243 template<class T>
244 struct is_positional : based::false_type
245 {
246 };
248 template<class Record, class Type>
249 struct is_positional<argument_list<Record, Type>> : based::true_type
250 {
251 };
253 template<class Record, class Type>
254 struct is_positional<argument<Record, Type>> : based::true_type
255 {
256 };
258 template<class T>
259 concept IsPositional = is_positional<T>::value;
261 class positional_base : public based::vector<detail::option, based::u64>
262 {
263 using base = based::vector<option, based::u64>;
265 protected:
266 template<detail::IsPositional Arg, detail::IsPositional... Args>
267 explicit positional_base(Arg&& arg, Args&&... args)
268 : base(std::initializer_list<option> {
269 based::forward<Arg>(arg),
270 based::forward<Args>(args)...,
271 })
272 {
273 for (size_type i = 0_u; i + 1_u8 < base::size(); i++) {
274 if (base::operator[](i).get_type() == option::type::list) {
275 throw runtime_error("invalid positional constructor");
276 }
277 }
278 }
280 public:
281 positional_base() = default;
283 [[nodiscard]] bool is_list() const
284 {
285 return !empty() && back().get_type() == option::type::list;
286 }
287 };
289 } // namespace detail
291 template<class Record>
292 struct positional : detail::positional_base
293 {
294 using rec_type = Record;
296 template<detail::IsPositional Arg, detail::IsPositional... Args>
297 explicit positional(Arg&& arg, Args&&... args)
298 requires detail::SameRec<Arg, Args...>
299 : positional_base(based::forward<Arg>(arg), based::forward<Args>(args)...)
300 {
301 }
302 };
304 template<detail::IsPositional Arg, detail::IsPositional... Args>
305 positional(Arg&& arg, Args&&... args) -> positional<detail::rec_type<Arg>>;
307 namespace detail
308 {
310 template<class T>
311 struct is_option : based::false_type
312 {
313 };
315 template<class Record, class Type>
316 struct is_option<direct<Record, Type>> : based::true_type
317 {
318 };
320 template<class Record, class Type>
321 struct is_option<boolean<Record, Type>> : based::true_type
322 {
323 };
325 template<class Record, class Type>
326 struct is_option<list<Record, Type>> : based::true_type
327 {
328 };
330 template<class T>
331 concept IsOption = is_option<T>::value;
333 class group_base : public based::vector<detail::option, based::u64>
334 {
335 using base = based::vector<option, based::u64>;
337 std::string m_name;
339 protected:
340 template<detail::IsOption Opt, detail::IsOption... Opts>
341 explicit group_base(std::string_view name, Opt&& opt, Opts&&... opts)
342 : base(std::initializer_list<option> {
343 based::forward<Opt>(opt),
344 based::forward<Opts>(opts)...,
345 })
346 , m_name(name)
347 {
348 }
350 public:
351 group_base() = default;
353 [[nodiscard]] const auto& name() const { return m_name; }
354 };
356 } // namespace detail
358 template<class Record>
359 struct group : detail::group_base
360 {
361 using rec_type = Record;
363 template<detail::IsOption Opt, detail::IsOption... Opts>
364 explicit group(std::string_view name, Opt&& opt, Opts&&... opts)
365 requires detail::SameRec<Opt, Opts...>
366 : group_base(
367 name, based::forward<Opt>(opt), based::forward<Opts>(opts)...
368 )
369 {
370 }
371 };
373 template<detail::IsOption Opt, detail::IsOption... Opts>
374 group(std::string_view name, Opt&& opt, Opts&&... opts)
375 -> group<typename Opt::rec_type>;
377 namespace detail
378 {
380 class option_short
381 {
382 using size_type = based::u8;
383 using value_type = based::u64;
384 using opt_type = std::optional<value_type>;
386 static constexpr auto size = size_type(2_u * 26_u);
387 static constexpr const auto sentinel = based::limits<value_type>::max;
389 using array_type = based::array<value_type, size_type, size>;
390 array_type m_opts = {};
392 [[nodiscard]] bool has(based::character chr) const;
394 public:
395 option_short() { m_opts.fill(sentinel); }
397 static constexpr bool is_valid(based::character chr);
398 [[nodiscard]] bool set(based::character chr, value_type value);
399 [[nodiscard]] opt_type get(based::character chr) const;
400 };
402 class trie_t
403 {
404 using size_type = based::u8;
405 using value_type = based::u64;
406 using opt_type = std::optional<value_type>;
408 static constexpr auto size = size_type(26_u + 10_u);
409 static constexpr const auto sentinel = based::limits<value_type>::max;
411 using ptr_type = std::unique_ptr<trie_t>;
412 using array_type = based::array<ptr_type, size_type, size>;
413 array_type m_children = {};
415 value_type m_value = sentinel;
416 based::u8 m_count = 0_u8;
418 bool m_terminal = false;
419 trie_t* m_parent = nullptr;
421 static constexpr auto map(based::character chr);
423 public:
424 explicit trie_t(trie_t* parent = nullptr)
425 : m_parent(parent)
426 {
427 }
429 static bool set(trie_t& trie, std::string_view key, value_type value);
430 static opt_type get(const trie_t& trie, std::string_view key);
431 };
433 class option_long
434 {
435 using value_type = based::u64;
436 using opt_type = std::optional<value_type>;
438 trie_t m_trie;
440 public:
441 static constexpr bool is_valid(std::string_view opt);
442 [[nodiscard]] bool set(std::string_view opt, value_type idx);
443 [[nodiscard]] opt_type get(std::string_view opt) const;
444 };
446 class parser_base
447 {
448 using size_type = based::u64;
450 based::vector<option, size_type> m_options;
452 using group_type = std::pair<size_type, std::string>;
453 based::vector<group_type, size_type> m_groups;
455 positional_base m_pos;
456 option_short m_opt_short;
457 option_long m_opt_long;
459 void process(const option& option);
461 [[nodiscard]] const option& get_option(based::character opt) const;
462 [[nodiscard]] const option& get_option(std::string_view opt) const;
464 using next_t = std::span<const std::string_view>;
466 next_t hdl_long_opt(
467 std::string_view program, void* record, std::string_view arg, next_t next
468 ) const;
469 next_t hdl_short_opts(
470 std::string_view program, void* record, std::string_view arg, next_t next
471 ) const;
472 next_t hdl_short_opt(
473 void* record, based::character opt, std::string_view rest, next_t next
474 ) const;
476 void help_usage(std::string_view program) const;
477 [[nodiscard]] bool help_long(std::string_view program) const;
478 [[nodiscard]] bool help_short(std::string_view program) const;
480 protected:
481 template<class... Groups>
482 explicit parser_base(Groups&&... groups)
483 requires(based::SameAs<group_base, Groups> && ...)
484 : parser_base({}, based::forward<group_base>(groups)...)
485 {
486 }
488 template<class... Groups>
489 explicit parser_base(positional_base&& positional, Groups&&... groups)
490 requires(based::SameAs<group_base, Groups> && ...)
491 : m_pos(based::forward<decltype(positional)>(positional))
492 {
493 m_options.reserve(m_options.size() + (groups.size() + ...));
494 m_groups.reserve(size_type::underlying_cast(sizeof...(groups)));
496 const auto process = [&](const auto& group)
497 {
498 for (const auto& option : group) {
499 this->process(option);
500 }
501 m_groups.emplace_back(m_options.size(), group.name());
502 };
503 (process(groups), ...);
505 process(group<parser_base> {
506 "Informational Options",
507 boolean {
508 "? help",
509 &parser_base::help_long,
510 "Give this help list",
511 },
512 boolean {
513 "usage",
514 &parser_base::help_short,
515 "Give a short usage message",
516 },
517 });
518 }
520 void operator()(void* record, int argc, const char** argv);
521 void operator()(void* record, std::span<const std::string_view> args);
522 };
524 } // namespace detail
526 template<class Record>
527 struct parser : detail::parser_base
528 {
529 template<class Group, class... Groups>
530 explicit parser(Group&& grp, Groups&&... groups)
531 requires(
532 based::SameAs<group<Record>, Group>
533 && (based::SameAs<group<Record>, Groups> && ...)
534 )
535 : parser_base(
536 based::forward<detail::group_base>(grp),
537 based::forward<detail::group_base>(groups)...
538 )
539 {
540 }
542 template<class... Groups>
543 explicit parser(positional<Record>&& positional, Groups&&... groups)
544 requires(based::SameAs<group<Record>, Groups> && ...)
545 : parser_base(
546 based::move(positional),
547 based::forward<detail::group_base>(groups)...
548 )
549 {
550 }
552 void operator()(Record& record, int argc, const char** argv)
553 {
554 try {
555 parser_base::operator()(&record, argc, argv);
556 } catch (const error<error_code::help>& err) {
557 (void)err;
558 }
559 }
561 void operator()(Record& record, std::span<const std::string_view> args)
562 {
563 try {
564 parser_base::operator()(&record, args);
565 } catch (const error<error_code::help>& err) {
566 (void)err;
567 }
568 }
569 };
571 } // namespace poafloc