poafloc

Parser 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
109 m_func(record, value);
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
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
139 };
141 template<class Record, class Type>
142 requires(!based::SameAs<bool, Type>)
143 class argument_list : public detail::option
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
157 };
159 template<class Record, class Type>
160 requires(!based::SameAs<bool, Type>)
161 class direct : public detail::option
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
172 : base(
173 base::type::direct,
174 opts,
175 base::template create<Record, Type>(member),
176 help
180 };
182 template<class Record, class Type>
183 class boolean : public detail::option
185 using base = detail::option;
186 using member_type = Type Record::*;
188 static auto create(member_type member)
190 return [member](void* record_raw, std::string_view value)
192 auto* record = static_cast<Record*>(record_raw);
193 if constexpr (std::is_invocable_v<member_type, Record, std::string_view>)
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;
203 };
206 public:
207 using rec_type = Record;
209 explicit boolean(
210 std::string_view opts, member_type member, std::string_view help
212 : base(base::type::boolean, opts, create(member), help)
215 };
217 template<class Record, class Type>
218 requires(!based::SameAs<bool, Type>)
219 class list : public detail::option
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
230 : base(
231 base::type::list,
232 opts,
233 base::template create<Record, Type>(member),
234 help
238 };
240 namespace detail
243 template<class T>
244 struct is_positional : based::false_type
246 };
248 template<class Record, class Type>
249 struct is_positional<argument_list<Record, Type>> : based::true_type
251 };
253 template<class Record, class Type>
254 struct is_positional<argument<Record, Type>> : based::true_type
256 };
258 template<class T>
259 concept IsPositional = is_positional<T>::value;
261 class positional_base : public based::vector<detail::option, based::u64>
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 })
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");
280 public:
281 positional_base() = default;
283 [[nodiscard]] bool is_list() const
285 return !empty() && back().get_type() == option::type::list;
287 };
289 } // namespace detail
291 template<class Record>
292 struct positional : detail::positional_base
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)...)
302 };
304 template<detail::IsPositional Arg, detail::IsPositional... Args>
305 positional(Arg&& arg, Args&&... args) -> positional<detail::rec_type<Arg>>;
307 namespace detail
310 template<class T>
311 struct is_option : based::false_type
313 };
315 template<class Record, class Type>
316 struct is_option<direct<Record, Type>> : based::true_type
318 };
320 template<class Record, class Type>
321 struct is_option<boolean<Record, Type>> : based::true_type
323 };
325 template<class Record, class Type>
326 struct is_option<list<Record, Type>> : based::true_type
328 };
330 template<class T>
331 concept IsOption = is_option<T>::value;
333 class group_base : public based::vector<detail::option, based::u64>
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)
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
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)...
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
380 class option_short
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
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)
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
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
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)...)
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))
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)
498 for (const auto& option : group) {
499 this->process(option);
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 });
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
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> && ...)
535 : parser_base(
536 based::forward<detail::group_base>(grp),
537 based::forward<detail::group_base>(groups)...
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)...
552 void operator()(Record& record, int argc, const char** argv)
554 try {
555 parser_base::operator()(&record, argc, argv);
556 } catch (const error<error_code::help>& err) {
557 (void)err;
561 void operator()(Record& record, std::span<const std::string_view> args)
563 try {
564 parser_base::operator()(&record, args);
565 } catch (const error<error_code::help>& err) {
566 (void)err;
569 };
571 } // namespace poafloc