1//! @file command.cpp
2//! @author ryftchen
3//! @brief The definitions (command) in the application module.
4//! @version 0.1.0
5//! @copyright Copyright (c) 2022-2026 ryftchen. All rights reserved.
6
7#include "command.hpp"
8#include "log.hpp"
9#include "view.hpp"
10
11#ifndef _PRECOMPILED_HEADER
12#include <barrier>
13#include <latch>
14#include <numeric>
15#include <ranges>
16#else
17#include "application/pch/precompiled_header.hpp"
18#endif
19
20#include "utility/include/benchmark.hpp"
21#include "utility/include/currying.hpp"
22#include "utility/include/time.hpp"
23
24namespace application::command
25{
26//! @brief Anonymous namespace.
27inline namespace
28{
29//! @brief The semaphore that controls the maximum access limit.
30std::counting_semaphore<1> cliSem(1); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
31} // namespace
32
33//! @brief Manage external helpers.
34namespace help
35{
36//! @brief Constraint for external helpers.
37//! @tparam Type - type of helper
38template <typename Type>
39concept ExtHelper =
40 !std::is_constructible_v<Type> && !std::is_copy_constructible_v<Type> && !std::is_copy_assignable_v<Type>
41 && !std::is_move_constructible_v<Type> && !std::is_move_assignable_v<Type> && requires (const Type& /*helper*/) {
42 { Type::getInstance() } -> std::same_as<std::shared_ptr<Type>>;
43 };
44
45//! @brief Enumerate specific events to control external helpers.
46enum class ExtEvent : std::uint8_t
47{
48 //! @brief Startup.
49 startup,
50 //! @brief Shutdown.
51 shutdown,
52 //! @brief Reload.
53 reload
54};
55
56//! @brief Trigger the external helper with event.
57//! @tparam Helper - type of helper
58//! @param event - target event
59template <ExtHelper Helper>
60requires std::derived_from<Helper, utility::fsm::FSM<Helper>>
61static void triggerEvent(const ExtEvent event)
62{
63 if (!configure::detail::activateHelper())
64 {
65 return;
66 }
67
68 switch (event)
69 {
70 case ExtEvent::startup:
71 typename Helper::Access().startup();
72 return;
73 case ExtEvent::shutdown:
74 typename Helper::Access().shutdown();
75 return;
76 case ExtEvent::reload:
77 typename Helper::Access().reload();
78 return;
79 default:
80 break;
81 }
82}
83
84//! @brief Helper daemon service.
85//! @tparam Helpers - type of arguments of helper
86template <ExtHelper... Helpers>
87requires (std::derived_from<Helpers, utility::fsm::FSM<Helpers>> && ...)
88static void daemonService()
89{
90 utility::thread::Thread extendedJob(sizeof...(Helpers));
91 (extendedJob.enqueue(Helpers::name, &Helpers::service, Helpers::getInstance()), ...);
92}
93
94// NOLINTBEGIN(readability-static-accessed-through-instance)
95//! @brief Coroutine for managing the lifecycle of helper components.
96//! @tparam Hs - type of helpers
97//! @return awaitable instance
98template <typename... Hs>
99static action::Awaitable launchLifecycle()
100{
101 if (!configure::detail::activateHelper())
102 {
103 co_return;
104 }
105
106 std::latch waitPoint(1);
107 const std::jthread daemon(
108 [&waitPoint]()
109 {
110 daemonService<Hs...>();
111 waitPoint.count_down();
112 });
113 std::barrier syncPoint(sizeof...(Hs) + 1);
114 static constexpr auto publish = [](std::barrier<>& phase, const ExtEvent event) constexpr
115 {
116 std::vector<std::jthread> senders{};
117 senders.reserve(n: sizeof...(Hs));
118 (senders.emplace_back(args: std::jthread{[&phase, event]()
119 {
120 triggerEvent<Hs>(event);
121 phase.arrive_and_wait();
122 }}),
123 ...);
124 phase.arrive_and_wait();
125 };
126
127 co_await std::suspend_always{};
128 publish(syncPoint, ExtEvent::startup);
129 co_await std::suspend_always{};
130 publish(syncPoint, ExtEvent::shutdown);
131
132 waitPoint.wait();
133}
134// NOLINTEND(readability-static-accessed-through-instance)
135
136//! @brief Enter the next phase of the coroutine of helpers.
137//! @param awaitable - awaitable instance
138static void enterNextPhase(action::Awaitable& awaitable)
139{
140 if (!awaitable.done())
141 {
142 awaitable.resume();
143 }
144}
145} // namespace help
146
147//! @brief Convert category enumeration to string.
148//! @param cat - native category
149//! @return category name
150static constexpr std::string_view toString(const Category cat)
151{
152 constexpr std::array<std::string_view, Bottom<Category>::value> stringify = {
153 MACRO_STRINGIFY(console), MACRO_STRINGIFY(dump), MACRO_STRINGIFY(help), MACRO_STRINGIFY(version)};
154 return stringify.at(n: static_cast<std::underlying_type_t<Category>>(cat));
155}
156
157//! @brief Get the Command instance.
158//! @return reference of the Command object
159Command& getInstance()
160{
161 static Command commander{};
162 return commander;
163}
164
165template <typename Key, typename Subject>
166void Notifier<Key, Subject>::attach(const Key key, std::shared_ptr<Operation> handler)
167{
168 handlers[key] = std::move(handler);
169}
170
171template <typename Key, typename Subject>
172void Notifier<Key, Subject>::notify(const Key key) const
173{
174 if (handlers.contains(key))
175 {
176 handlers.at(key)->execute();
177 }
178}
179
180// clang-format off
181//! @brief Mapping table for enum and attribute about command categories. X macro.
182#define COMMAND_CATEGORY_X_MACRO_MAPPING \
183 X(Category::console, "c", "run options in console mode and exit\n" \
184 "separate with quotes" ) \
185 X(Category::dump , "d", "dump default configuration and exit" ) \
186 X(Category::help , "h", "show this help message and exit" ) \
187 X(Category::version, "v", "show version information and exit" )
188// clang-format on
189consteval std::string_view Command::mappedAlias(const Category cat)
190{
191//! @cond
192#define X(enum, descr, alias) {descr, alias},
193 constexpr std::string_view table[][2] = {COMMAND_CATEGORY_X_MACRO_MAPPING};
194 static_assert((sizeof(table) / sizeof(table[0])) == Bottom<Category>::value);
195 return table[static_cast<std::underlying_type_t<Category>>(cat)][0];
196//! @endcond
197#undef X
198}
199
200consteval std::string_view Command::mappedDescr(const Category cat)
201{
202//! @cond
203#define X(enum, descr, alias) {descr, alias},
204 constexpr std::string_view table[][2] = {COMMAND_CATEGORY_X_MACRO_MAPPING};
205 static_assert((sizeof(table) / sizeof(table[0])) == Bottom<Category>::value);
206 return table[static_cast<std::underlying_type_t<Category>>(cat)][1];
207//! @endcond
208#undef X
209}
210#undef COMMAND_CATEGORY_X_MACRO_MAPPING
211
212Command::Command()
213{
214 initializeNativeCLI();
215 initializeExtraCLI();
216}
217
218Command::~Command()
219{
220 clearSelected();
221}
222
223bool Command::execute(const int argc, const char* const argv[])
224try
225{
226 isFaulty.store(i: false);
227 isParsed.store(i: false);
228 auto helpCtrl = help::launchLifecycle<log::Log, view::View>();
229 help::enterNextPhase(awaitable&: helpCtrl);
230
231 if (argc > 1)
232 {
233 constexpr std::uint8_t endNum = 2;
234 utility::thread::Thread scheduledJob(endNum);
235 scheduledJob.enqueue(name: title + "-front", func: &Command::frontEndHandler, args: this, args: argc, args&: argv);
236 scheduledJob.enqueue(name: title + "-back", func: &Command::backEndHandler, args: this);
237 }
238 else
239 {
240 enterConsoleMode();
241 }
242
243 help::enterNextPhase(awaitable&: helpCtrl);
244 return !isFaulty.load();
245}
246catch (const std::exception& err)
247{
248 isFaulty.store(i: true);
249 LOG_ERR << err.what();
250 return !isFaulty.load();
251}
252
253void Command::setupMainCLI()
254{
255 using ArgsNumPattern = utility::argument::ArgsNumPattern;
256 mainCLI
257 .addArgument(
258 fewArgs: shortPrefix + std::string{mappedAlias(cat: Category::help)}, fewArgs: longPrefix + std::string{toString(cat: Category::help)})
259 .argsNum(num: 0)
260 .implicitValue(value: true)
261 .help(message: mappedDescr(cat: Category::help));
262 builtInNotifier.attach(key: Category::help, handler: std::make_shared<LocalNotifier::Handler<Category::help>>(args&: *this));
263 mainCLI
264 .addArgument(
265 fewArgs: shortPrefix + std::string{mappedAlias(cat: Category::version)},
266 fewArgs: longPrefix + std::string{toString(cat: Category::version)})
267 .argsNum(num: 0)
268 .implicitValue(value: true)
269 .help(message: mappedDescr(cat: Category::version));
270 builtInNotifier.attach(key: Category::version, handler: std::make_shared<LocalNotifier::Handler<Category::version>>(args&: *this));
271 mainCLI
272 .addArgument(
273 fewArgs: shortPrefix + std::string{mappedAlias(cat: Category::dump)}, fewArgs: longPrefix + std::string{toString(cat: Category::dump)})
274 .argsNum(num: 0)
275 .implicitValue(value: true)
276 .help(message: mappedDescr(cat: Category::dump));
277 builtInNotifier.attach(key: Category::dump, handler: std::make_shared<LocalNotifier::Handler<Category::dump>>(args&: *this));
278 mainCLI
279 .addArgument(
280 fewArgs: shortPrefix + std::string{mappedAlias(cat: Category::console)},
281 fewArgs: longPrefix + std::string{toString(cat: Category::console)})
282 .argsNum(pattern: ArgsNumPattern::any)
283 .defaultValue<std::vector<std::string>>(value: {"usage"})
284 .appending()
285 .action(
286 callable: [](const std::string& input)
287 {
288 if (std::ranges::all_of(input, [l = std::locale{}](const auto c) { return std::isspace(c, l); }))
289 {
290 throw std::runtime_error{"Invalid " + std::string{toString(cat: Category::console)} + " command."};
291 }
292 return input;
293 })
294 .metaVariable(variable: "CMD")
295 .help(message: mappedDescr(cat: Category::console));
296 builtInNotifier.attach(key: Category::console, handler: std::make_shared<LocalNotifier::Handler<Category::console>>(args&: *this));
297}
298
299// NOLINTBEGIN(google-build-using-namespace, readability-function-size)
300//! @brief Setup the sub-command line interface (algorithm module).
301template <>
302void Command::setupSubCLI<reg_algo::ApplyAlgorithm>()
303{
304 using namespace reg_algo;
305 using Intf = ExtraManager::Intf;
306 using Attr = ExtraManager::Attr;
307 using action::name, action::alias, action::descr;
308 constexpr std::string_view helpDescr = mappedDescr(cat: Category::help);
309 const std::string helpArg1 = shortPrefix + std::string{mappedAlias(cat: Category::help)};
310 const std::string helpArg2 = longPrefix + std::string{toString(cat: Category::help)};
311 auto& registry = taskDispatcher.extraChoiceRegistry[subCLIAppAlgo.title()];
312 std::vector<std::string> candidates{};
313
314 taskDispatcher.extraChecklist.emplace(args: subCLIAppAlgo.title(), args: Intf{manage::present, manage::clear});
315 subCLIAppAlgo.addDescription(text: descr<ApplyAlgorithm>());
316 subCLIAppAlgo.addArgument(fewArgs: helpArg1, fewArgs: helpArg2).argsNum(num: 0).implicitValue(value: true).help(message: helpDescr);
317
318 candidates = extractChoices<MatchMethod>();
319 registry.emplace(args: name<MatchMethod>(), args: Attr{.choices: candidates, .event: MatchMethod{}});
320 subCLIAppAlgo
321 .addArgument(fewArgs: shortPrefix + std::string{alias<MatchMethod>()}, fewArgs: longPrefix + std::string{name<MatchMethod>()})
322 .argsNum(numMin: 0, numMax: candidates.size())
323 .defaultValue<decltype(candidates)>(value: std::move(candidates))
324 .remaining()
325 .metaVariable(variable: metaVar)
326 .help(message: descr<MatchMethod>());
327 applyingForwarder.registerHandler(handling: [](const action::SetChoice<MatchMethod>& msg)
328 { setChoice<MatchMethod>(msg.choice); });
329 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<MatchMethod>& msg)
330 { runCandidates<MatchMethod>(candidates: msg.candidates); });
331 versionLinks.emplace(args: VerLinkKey{name<ApplyAlgorithm>(), match::version()}, args: name<MatchMethod>());
332 candidates = extractChoices<NotationMethod>();
333 registry.emplace(args: name<NotationMethod>(), args: Attr{.choices: candidates, .event: NotationMethod{}});
334 subCLIAppAlgo
335 .addArgument(
336 fewArgs: shortPrefix + std::string{alias<NotationMethod>()}, fewArgs: longPrefix + std::string{name<NotationMethod>()})
337 .argsNum(numMin: 0, numMax: candidates.size())
338 .defaultValue<decltype(candidates)>(value: std::move(candidates))
339 .remaining()
340 .metaVariable(variable: metaVar)
341 .help(message: descr<NotationMethod>());
342 applyingForwarder.registerHandler(handling: [](const action::SetChoice<NotationMethod>& msg)
343 { setChoice<NotationMethod>(msg.choice); });
344 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<NotationMethod>& msg)
345 { runCandidates<NotationMethod>(candidates: msg.candidates); });
346 versionLinks.emplace(args: VerLinkKey{name<ApplyAlgorithm>(), notation::version()}, args: name<NotationMethod>());
347 candidates = extractChoices<OptimalMethod>();
348 registry.emplace(args: name<OptimalMethod>(), args: Attr{.choices: candidates, .event: OptimalMethod{}});
349 subCLIAppAlgo
350 .addArgument(fewArgs: shortPrefix + std::string{alias<OptimalMethod>()}, fewArgs: longPrefix + std::string{name<OptimalMethod>()})
351 .argsNum(numMin: 0, numMax: candidates.size())
352 .defaultValue<decltype(candidates)>(value: std::move(candidates))
353 .remaining()
354 .metaVariable(variable: metaVar)
355 .help(message: descr<OptimalMethod>());
356 applyingForwarder.registerHandler(handling: [](const action::SetChoice<OptimalMethod>& msg)
357 { setChoice<OptimalMethod>(msg.choice); });
358 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<OptimalMethod>& msg)
359 { runCandidates<OptimalMethod>(candidates: msg.candidates); });
360 versionLinks.emplace(args: VerLinkKey{name<ApplyAlgorithm>(), optimal::version()}, args: name<OptimalMethod>());
361 candidates = extractChoices<SearchMethod>();
362 registry.emplace(args: name<SearchMethod>(), args: Attr{.choices: candidates, .event: SearchMethod{}});
363 subCLIAppAlgo
364 .addArgument(fewArgs: shortPrefix + std::string{alias<SearchMethod>()}, fewArgs: longPrefix + std::string{name<SearchMethod>()})
365 .argsNum(numMin: 0, numMax: candidates.size())
366 .defaultValue<decltype(candidates)>(value: std::move(candidates))
367 .remaining()
368 .metaVariable(variable: metaVar)
369 .help(message: descr<SearchMethod>());
370 applyingForwarder.registerHandler(handling: [](const action::SetChoice<SearchMethod>& msg)
371 { setChoice<SearchMethod>(msg.choice); });
372 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<SearchMethod>& msg)
373 { runCandidates<SearchMethod>(candidates: msg.candidates); });
374 versionLinks.emplace(args: VerLinkKey{name<ApplyAlgorithm>(), search::version()}, args: name<SearchMethod>());
375 candidates = extractChoices<SortMethod>();
376 registry.emplace(args: name<SortMethod>(), args: Attr{.choices: candidates, .event: SortMethod{}});
377 subCLIAppAlgo
378 .addArgument(fewArgs: shortPrefix + std::string{alias<SortMethod>()}, fewArgs: longPrefix + std::string{name<SortMethod>()})
379 .argsNum(numMin: 0, numMax: candidates.size())
380 .defaultValue<decltype(candidates)>(value: std::move(candidates))
381 .remaining()
382 .metaVariable(variable: metaVar)
383 .help(message: descr<SortMethod>());
384 applyingForwarder.registerHandler(handling: [](const action::SetChoice<SortMethod>& msg)
385 { setChoice<SortMethod>(msg.choice); });
386 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<SortMethod>& msg)
387 { runCandidates<SortMethod>(candidates: msg.candidates); });
388 versionLinks.emplace(args: VerLinkKey{name<ApplyAlgorithm>(), sort::version()}, args: name<SortMethod>());
389
390 mainCLI.addSubParser(parser&: subCLIAppAlgo);
391}
392
393//! @brief Setup the sub-command line interface (design pattern module).
394template <>
395void Command::setupSubCLI<reg_dp::ApplyDesignPattern>()
396{
397 using namespace reg_dp;
398 using Intf = ExtraManager::Intf;
399 using Attr = ExtraManager::Attr;
400 using action::name, action::alias, action::descr;
401 constexpr std::string_view helpDescr = mappedDescr(cat: Category::help);
402 const std::string helpArg1 = shortPrefix + std::string{mappedAlias(cat: Category::help)};
403 const std::string helpArg2 = longPrefix + std::string{toString(cat: Category::help)};
404 auto& registry = taskDispatcher.extraChoiceRegistry[subCLIAppDp.title()];
405 std::vector<std::string> candidates{};
406
407 taskDispatcher.extraChecklist.emplace(args: subCLIAppDp.title(), args: Intf{manage::present, manage::clear});
408 subCLIAppDp.addDescription(text: descr<ApplyDesignPattern>());
409 subCLIAppDp.addArgument(fewArgs: helpArg1, fewArgs: helpArg2).argsNum(num: 0).implicitValue(value: true).help(message: helpDescr);
410
411 candidates = extractChoices<BehavioralInstance>();
412 registry.emplace(args: name<BehavioralInstance>(), args: Attr{.choices: candidates, .event: BehavioralInstance{}});
413 subCLIAppDp
414 .addArgument(
415 fewArgs: shortPrefix + std::string{alias<BehavioralInstance>()},
416 fewArgs: longPrefix + std::string{name<BehavioralInstance>()})
417 .argsNum(numMin: 0, numMax: candidates.size())
418 .defaultValue<decltype(candidates)>(value: std::move(candidates))
419 .remaining()
420 .metaVariable(variable: metaVar)
421 .help(message: descr<BehavioralInstance>());
422 applyingForwarder.registerHandler(handling: [](const action::SetChoice<BehavioralInstance>& msg)
423 { setChoice<BehavioralInstance>(msg.choice); });
424 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<BehavioralInstance>& msg)
425 { runCandidates<BehavioralInstance>(candidates: msg.candidates); });
426 versionLinks.emplace(args: VerLinkKey{name<ApplyDesignPattern>(), behavioral::version()}, args: name<BehavioralInstance>());
427 candidates = extractChoices<CreationalInstance>();
428 registry.emplace(args: name<CreationalInstance>(), args: Attr{.choices: candidates, .event: CreationalInstance{}});
429 subCLIAppDp
430 .addArgument(
431 fewArgs: shortPrefix + std::string{alias<CreationalInstance>()},
432 fewArgs: longPrefix + std::string{name<CreationalInstance>()})
433 .argsNum(numMin: 0, numMax: candidates.size())
434 .defaultValue<decltype(candidates)>(value: std::move(candidates))
435 .remaining()
436 .metaVariable(variable: metaVar)
437 .help(message: descr<CreationalInstance>());
438 applyingForwarder.registerHandler(handling: [](const action::SetChoice<CreationalInstance>& msg)
439 { setChoice<CreationalInstance>(msg.choice); });
440 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<CreationalInstance>& msg)
441 { runCandidates<CreationalInstance>(candidates: msg.candidates); });
442 versionLinks.emplace(args: VerLinkKey{name<ApplyDesignPattern>(), creational::version()}, args: name<CreationalInstance>());
443 candidates = extractChoices<StructuralInstance>();
444 registry.emplace(args: name<StructuralInstance>(), args: Attr{.choices: candidates, .event: StructuralInstance{}});
445 subCLIAppDp
446 .addArgument(
447 fewArgs: shortPrefix + std::string{alias<StructuralInstance>()},
448 fewArgs: longPrefix + std::string{name<StructuralInstance>()})
449 .argsNum(numMin: 0, numMax: candidates.size())
450 .defaultValue<decltype(candidates)>(value: std::move(candidates))
451 .remaining()
452 .metaVariable(variable: metaVar)
453 .help(message: descr<StructuralInstance>());
454 applyingForwarder.registerHandler(handling: [](const action::SetChoice<StructuralInstance>& msg)
455 { setChoice<StructuralInstance>(msg.choice); });
456 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<StructuralInstance>& msg)
457 { runCandidates<StructuralInstance>(candidates: msg.candidates); });
458 versionLinks.emplace(args: VerLinkKey{name<ApplyDesignPattern>(), structural::version()}, args: name<StructuralInstance>());
459
460 mainCLI.addSubParser(parser&: subCLIAppDp);
461}
462
463//! @brief Setup the sub-command line interface (data structure module).
464template <>
465void Command::setupSubCLI<reg_ds::ApplyDataStructure>()
466{
467 using namespace reg_ds;
468 using Intf = ExtraManager::Intf;
469 using Attr = ExtraManager::Attr;
470 using action::name, action::alias, action::descr;
471 constexpr std::string_view helpDescr = mappedDescr(cat: Category::help);
472 const std::string helpArg1 = shortPrefix + std::string{mappedAlias(cat: Category::help)};
473 const std::string helpArg2 = longPrefix + std::string{toString(cat: Category::help)};
474 auto& registry = taskDispatcher.extraChoiceRegistry[subCLIAppDs.title()];
475 std::vector<std::string> candidates{};
476
477 taskDispatcher.extraChecklist.emplace(args: subCLIAppDs.title(), args: Intf{manage::present, manage::clear});
478 subCLIAppDs.addDescription(text: descr<ApplyDataStructure>());
479 subCLIAppDs.addArgument(fewArgs: helpArg1, fewArgs: helpArg2).argsNum(num: 0).implicitValue(value: true).help(message: helpDescr);
480
481 candidates = extractChoices<CacheInstance>();
482 registry.emplace(args: name<CacheInstance>(), args: Attr{.choices: candidates, .event: CacheInstance{}});
483 subCLIAppDs
484 .addArgument(fewArgs: shortPrefix + std::string{alias<CacheInstance>()}, fewArgs: longPrefix + std::string{name<CacheInstance>()})
485 .argsNum(numMin: 0, numMax: candidates.size())
486 .defaultValue<decltype(candidates)>(value: std::move(candidates))
487 .remaining()
488 .metaVariable(variable: metaVar)
489 .help(message: descr<CacheInstance>());
490 applyingForwarder.registerHandler(handling: [](const action::SetChoice<CacheInstance>& msg)
491 { setChoice<CacheInstance>(msg.choice); });
492 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<CacheInstance>& msg)
493 { runCandidates<CacheInstance>(candidates: msg.candidates); });
494 versionLinks.emplace(args: VerLinkKey{name<ApplyDataStructure>(), cache::version()}, args: name<CacheInstance>());
495 candidates = extractChoices<FilterInstance>();
496 registry.emplace(args: name<FilterInstance>(), args: Attr{.choices: candidates, .event: FilterInstance{}});
497 subCLIAppDs
498 .addArgument(
499 fewArgs: shortPrefix + std::string{alias<FilterInstance>()}, fewArgs: longPrefix + std::string{name<FilterInstance>()})
500 .argsNum(numMin: 0, numMax: candidates.size())
501 .defaultValue<decltype(candidates)>(value: std::move(candidates))
502 .remaining()
503 .metaVariable(variable: metaVar)
504 .help(message: descr<FilterInstance>());
505 applyingForwarder.registerHandler(handling: [](const action::SetChoice<FilterInstance>& msg)
506 { setChoice<FilterInstance>(msg.choice); });
507 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<FilterInstance>& msg)
508 { runCandidates<FilterInstance>(candidates: msg.candidates); });
509 versionLinks.emplace(args: VerLinkKey{name<ApplyDataStructure>(), filter::version()}, args: name<FilterInstance>());
510 candidates = extractChoices<GraphInstance>();
511 registry.emplace(args: name<GraphInstance>(), args: Attr{.choices: candidates, .event: GraphInstance{}});
512 subCLIAppDs
513 .addArgument(fewArgs: shortPrefix + std::string{alias<GraphInstance>()}, fewArgs: longPrefix + std::string{name<GraphInstance>()})
514 .argsNum(numMin: 0, numMax: candidates.size())
515 .defaultValue<decltype(candidates)>(value: std::move(candidates))
516 .remaining()
517 .metaVariable(variable: metaVar)
518 .help(message: descr<GraphInstance>());
519 applyingForwarder.registerHandler(handling: [](const action::SetChoice<GraphInstance>& msg)
520 { setChoice<GraphInstance>(msg.choice); });
521 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<GraphInstance>& msg)
522 { runCandidates<GraphInstance>(candidates: msg.candidates); });
523 versionLinks.emplace(args: VerLinkKey{name<ApplyDataStructure>(), graph::version()}, args: name<GraphInstance>());
524 candidates = extractChoices<HeapInstance>();
525 registry.emplace(args: name<HeapInstance>(), args: Attr{.choices: candidates, .event: HeapInstance{}});
526 subCLIAppDs
527 .addArgument(fewArgs: shortPrefix + std::string{alias<HeapInstance>()}, fewArgs: longPrefix + std::string{name<HeapInstance>()})
528 .argsNum(numMin: 0, numMax: candidates.size())
529 .defaultValue<decltype(candidates)>(value: std::move(candidates))
530 .remaining()
531 .metaVariable(variable: metaVar)
532 .help(message: descr<HeapInstance>());
533 applyingForwarder.registerHandler(handling: [](const action::SetChoice<HeapInstance>& msg)
534 { setChoice<HeapInstance>(msg.choice); });
535 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<HeapInstance>& msg)
536 { runCandidates<HeapInstance>(candidates: msg.candidates); });
537 versionLinks.emplace(args: VerLinkKey{name<ApplyDataStructure>(), heap::version()}, args: name<HeapInstance>());
538 candidates = extractChoices<LinearInstance>();
539 registry.emplace(args: name<LinearInstance>(), args: Attr{.choices: candidates, .event: LinearInstance{}});
540 subCLIAppDs
541 .addArgument(
542 fewArgs: shortPrefix + std::string{alias<LinearInstance>()}, fewArgs: longPrefix + std::string{name<LinearInstance>()})
543 .argsNum(numMin: 0, numMax: candidates.size())
544 .defaultValue<decltype(candidates)>(value: std::move(candidates))
545 .remaining()
546 .metaVariable(variable: metaVar)
547 .help(message: descr<LinearInstance>());
548 applyingForwarder.registerHandler(handling: [](const action::SetChoice<LinearInstance>& msg)
549 { setChoice<LinearInstance>(msg.choice); });
550 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<LinearInstance>& msg)
551 { runCandidates<LinearInstance>(candidates: msg.candidates); });
552 versionLinks.emplace(args: VerLinkKey{name<ApplyDataStructure>(), linear::version()}, args: name<LinearInstance>());
553 candidates = extractChoices<TreeInstance>();
554 registry.emplace(args: name<TreeInstance>(), args: Attr{.choices: candidates, .event: TreeInstance{}});
555 subCLIAppDs
556 .addArgument(fewArgs: shortPrefix + std::string{alias<TreeInstance>()}, fewArgs: longPrefix + std::string{name<TreeInstance>()})
557 .argsNum(numMin: 0, numMax: candidates.size())
558 .defaultValue<decltype(candidates)>(value: std::move(candidates))
559 .remaining()
560 .metaVariable(variable: metaVar)
561 .help(message: descr<TreeInstance>());
562 applyingForwarder.registerHandler(handling: [](const action::SetChoice<TreeInstance>& msg)
563 { setChoice<TreeInstance>(msg.choice); });
564 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<TreeInstance>& msg)
565 { runCandidates<TreeInstance>(candidates: msg.candidates); });
566 versionLinks.emplace(args: VerLinkKey{name<ApplyDataStructure>(), tree::version()}, args: name<TreeInstance>());
567
568 mainCLI.addSubParser(parser&: subCLIAppDs);
569}
570
571//! @brief Setup the sub-command line interface (numeric module).
572template <>
573void Command::setupSubCLI<reg_num::ApplyNumeric>()
574{
575 using namespace reg_num;
576 using Intf = ExtraManager::Intf;
577 using Attr = ExtraManager::Attr;
578 using action::name, action::alias, action::descr;
579 constexpr std::string_view helpDescr = mappedDescr(cat: Category::help);
580 const std::string helpArg1 = shortPrefix + std::string{mappedAlias(cat: Category::help)};
581 const std::string helpArg2 = longPrefix + std::string{toString(cat: Category::help)};
582 auto& registry = taskDispatcher.extraChoiceRegistry[subCLIAppNum.title()];
583 std::vector<std::string> candidates{};
584
585 taskDispatcher.extraChecklist.emplace(args: subCLIAppNum.title(), args: Intf{manage::present, manage::clear});
586 subCLIAppNum.addDescription(text: descr<ApplyNumeric>());
587 subCLIAppNum.addArgument(fewArgs: helpArg1, fewArgs: helpArg2).argsNum(num: 0).implicitValue(value: true).help(message: helpDescr);
588
589 candidates = extractChoices<ArithmeticMethod>();
590 registry.emplace(args: name<ArithmeticMethod>(), args: Attr{.choices: candidates, .event: ArithmeticMethod{}});
591 subCLIAppNum
592 .addArgument(
593 fewArgs: shortPrefix + std::string{alias<ArithmeticMethod>()}, fewArgs: longPrefix + std::string{name<ArithmeticMethod>()})
594 .argsNum(numMin: 0, numMax: candidates.size())
595 .defaultValue<decltype(candidates)>(value: std::move(candidates))
596 .remaining()
597 .metaVariable(variable: metaVar)
598 .help(message: descr<ArithmeticMethod>());
599 applyingForwarder.registerHandler(handling: [](const action::SetChoice<ArithmeticMethod>& msg)
600 { setChoice<ArithmeticMethod>(msg.choice); });
601 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<ArithmeticMethod>& msg)
602 { runCandidates<ArithmeticMethod>(candidates: msg.candidates); });
603 versionLinks.emplace(args: VerLinkKey{name<ApplyNumeric>(), arithmetic::version()}, args: name<ArithmeticMethod>());
604 candidates = extractChoices<DivisorMethod>();
605 registry.emplace(args: name<DivisorMethod>(), args: Attr{.choices: candidates, .event: DivisorMethod{}});
606 subCLIAppNum
607 .addArgument(fewArgs: shortPrefix + std::string{alias<DivisorMethod>()}, fewArgs: longPrefix + std::string{name<DivisorMethod>()})
608 .argsNum(numMin: 0, numMax: candidates.size())
609 .defaultValue<decltype(candidates)>(value: std::move(candidates))
610 .remaining()
611 .metaVariable(variable: metaVar)
612 .help(message: descr<DivisorMethod>());
613 applyingForwarder.registerHandler(handling: [](const action::SetChoice<DivisorMethod>& msg)
614 { setChoice<DivisorMethod>(msg.choice); });
615 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<DivisorMethod>& msg)
616 { runCandidates<DivisorMethod>(candidates: msg.candidates); });
617 versionLinks.emplace(args: VerLinkKey{name<ApplyNumeric>(), divisor::version()}, args: name<DivisorMethod>());
618 candidates = extractChoices<IntegralMethod>();
619 registry.emplace(args: name<IntegralMethod>(), args: Attr{.choices: candidates, .event: IntegralMethod{}});
620 subCLIAppNum
621 .addArgument(
622 fewArgs: shortPrefix + std::string{alias<IntegralMethod>()}, fewArgs: longPrefix + std::string{name<IntegralMethod>()})
623 .argsNum(numMin: 0, numMax: candidates.size())
624 .defaultValue<decltype(candidates)>(value: std::move(candidates))
625 .remaining()
626 .metaVariable(variable: metaVar)
627 .help(message: descr<IntegralMethod>());
628 applyingForwarder.registerHandler(handling: [](const action::SetChoice<IntegralMethod>& msg)
629 { setChoice<IntegralMethod>(msg.choice); });
630 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<IntegralMethod>& msg)
631 { runCandidates<IntegralMethod>(candidates: msg.candidates); });
632 versionLinks.emplace(args: VerLinkKey{name<ApplyNumeric>(), integral::version()}, args: name<IntegralMethod>());
633 candidates = extractChoices<PrimeMethod>();
634 registry.emplace(args: name<PrimeMethod>(), args: Attr{.choices: candidates, .event: PrimeMethod{}});
635 subCLIAppNum
636 .addArgument(fewArgs: shortPrefix + std::string{alias<PrimeMethod>()}, fewArgs: longPrefix + std::string{name<PrimeMethod>()})
637 .argsNum(numMin: 0, numMax: candidates.size())
638 .defaultValue<decltype(candidates)>(value: std::move(candidates))
639 .remaining()
640 .metaVariable(variable: metaVar)
641 .help(message: descr<PrimeMethod>());
642 applyingForwarder.registerHandler(handling: [](const action::SetChoice<PrimeMethod>& msg)
643 { setChoice<PrimeMethod>(msg.choice); });
644 applyingForwarder.registerHandler(handling: [](const action::RunCandidates<PrimeMethod>& msg)
645 { runCandidates<PrimeMethod>(candidates: msg.candidates); });
646 versionLinks.emplace(args: VerLinkKey{name<ApplyNumeric>(), prime::version()}, args: name<PrimeMethod>());
647
648 mainCLI.addSubParser(parser&: subCLIAppNum);
649}
650// NOLINTEND(google-build-using-namespace, readability-function-size)
651
652void Command::initializeNativeCLI()
653{
654 setupMainCLI();
655}
656
657void Command::initializeExtraCLI()
658{
659 setupSubCLI<reg_algo::ApplyAlgorithm>();
660 setupSubCLI<reg_dp::ApplyDesignPattern>();
661 setupSubCLI<reg_ds::ApplyDataStructure>();
662 setupSubCLI<reg_num::ApplyNumeric>();
663}
664
665void Command::frontEndHandler(const int argc, const char* const argv[])
666try
667{
668 std::unique_lock<std::mutex> parserLock(parserMtx);
669 mainCLI.clearUsed();
670 mainCLI.parseArgs(argc, argv);
671 precheck();
672
673 isParsed.store(i: true);
674 parserLock.unlock();
675 parserCond.notify_one();
676}
677catch (const std::exception& err)
678{
679 isParsed.store(i: true);
680 parserCond.notify_one();
681 isFaulty.store(i: true);
682 LOG_WRN << err.what();
683}
684
685void Command::backEndHandler()
686try
687{
688 if (std::unique_lock<std::mutex> parserLock(parserMtx); true)
689 {
690 parserCond.wait(lock&: parserLock, p: [this]() { return isParsed.load(); });
691 }
692
693 if (anySelected())
694 {
695 dispatchAll();
696 clearSelected();
697 }
698}
699catch (const std::exception& err)
700{
701 clearSelected();
702 isFaulty.store(i: true);
703 LOG_WRN << err.what();
704}
705
706void Command::precheck()
707{
708 for (auto& spec = taskDispatcher.nativeCategories;
709 const auto index : std::views::iota(0U, spec.size())
710 | std::views::filter([this](const auto i) { return mainCLI.isUsed(argName: toString(cat: static_cast<Category>(i))); }))
711 {
712 checkExcessArgs();
713 spec.set(position: index);
714 }
715
716 for (constexpr auto helpArgName = toString(cat: Category::help);
717 [[maybe_unused]] const auto& [subCLIName, categoryMap] : taskDispatcher.extraChoiceRegistry
718 | std::views::filter([this](const auto& subCLIPair)
719 { return mainCLI.isSubcommandUsed(subCLIPair.first) && (checkExcessArgs(), true); }))
720 {
721 const auto& subCLI = mainCLI.at<utility::argument::Argument>(name: subCLIName);
722 const bool notAssigned = !subCLI;
723 taskDispatcher.extraHelping = notAssigned || subCLI.isUsed(argName: helpArgName);
724 if (notAssigned)
725 {
726 return;
727 }
728
729 for ([[maybe_unused]] const auto& [categoryName, categoryAttr] : categoryMap
730 | std::views::filter([this, &subCLI](const auto& categoryPair)
731 { return subCLI.isUsed(argName: categoryPair.first) && (checkExcessArgs(), true); }))
732 {
733 for (const auto& choice : subCLI.get<std::vector<std::string>>(argName: categoryName))
734 {
735 utility::common::patternMatch(
736 var: categoryAttr.event,
737 cases: [this, &choice](auto&& event)
738 { applyingForwarder.onMessage(action::SetChoice<std::decay_t<decltype(event)>>{choice}); });
739 }
740 }
741 }
742}
743
744bool Command::anySelected() const
745{
746 return !taskDispatcher.empty();
747}
748
749void Command::clearSelected()
750{
751 taskDispatcher.reset();
752}
753
754void Command::dispatchAll()
755{
756 if (!taskDispatcher.NativeManager::empty())
757 {
758 for (const auto& spec = taskDispatcher.nativeCategories;
759 const auto index :
760 std::views::iota(0U, spec.size()) | std::views::filter([&spec](const auto i) { return spec.test(position: i); }))
761 {
762 builtInNotifier.notify(key: static_cast<Category>(index));
763 }
764 }
765
766 if (!taskDispatcher.ExtraManager::empty())
767 {
768 if (taskDispatcher.extraHelping)
769 {
770 if (auto whichUsed = std::views::keys(taskDispatcher.extraChoiceRegistry)
771 | std::views::filter([this](const auto& subCLIName)
772 { return mainCLI.isSubcommandUsed(subCLIName); });
773 std::ranges::distance(whichUsed) != 0)
774 {
775 std::cout << mainCLI.at<utility::argument::Argument>(name: *std::ranges::begin(whichUsed)).help().str()
776 << std::flush;
777 }
778 return;
779 }
780
781 for ([[maybe_unused]] const auto& [categoryName, categoryAttr] : taskDispatcher.extraChoiceRegistry
782 | std::views::filter([this](const auto& subCLIPair)
783 { return taskDispatcher.extraChecklist.at(subCLIPair.first).present(); })
784 | std::views::values | std::views::join)
785 {
786 utility::common::patternMatch(
787 var: categoryAttr.event,
788 cases: [this, &candidates = categoryAttr.choices](auto&& event)
789 { applyingForwarder.onMessage(action::RunCandidates<std::decay_t<decltype(event)>>{candidates}); });
790 }
791 }
792}
793
794void Command::checkExcessArgs()
795{
796 if (anySelected())
797 {
798 clearSelected();
799 throw std::runtime_error{"Excessive arguments were found."};
800 }
801}
802
803template <typename Cat>
804std::vector<std::string> Command::extractChoices()
805{
806 constexpr auto refl = REFLECTION_STR("choice");
807 std::vector<std::string> choices{};
808 choices.reserve(n: utility::reflection::TypeInfo<Cat>::fields.size);
809 utility::reflection::TypeInfo<Cat>::fields.forEach(
810 [refl, &choices](const auto field)
811 {
812 static_assert(field.attrs.contains(refl) && (field.attrs.size == 1));
813 const auto attr = field.attrs.find(refl);
814 static_assert(attr.hasValue);
815 choices.emplace_back(attr.value);
816 });
817 return choices;
818}
819
820//! @brief Launch the TCP client for console mode.
821//! @param client - TCP client to be launched
822template <>
823void Command::launchClient<utility::socket::TCPSocket>(std::shared_ptr<utility::socket::TCPSocket>& client)
824{
825 client->subscribeRawMessage(
826 callback: [&client](char* const bytes, const std::size_t size)
827 {
828 try
829 {
830 MACRO_DEFER([]() { notifyClientOutputDone(); });
831 if (!client->stopRequested() && !onParsing4Client(bytes, size))
832 {
833 client->requestStop();
834 }
835 }
836 catch (const std::exception& err)
837 {
838 LOG_WRN << err.what();
839 }
840 });
841 client->connect(ip: view::info::currentTCPHost(), port: view::info::currentTCPPort());
842}
843
844//! @brief Launch the UDP client for console mode.
845//! @param client - UDP client to be launched
846template <>
847void Command::launchClient<utility::socket::UDPSocket>(std::shared_ptr<utility::socket::UDPSocket>& client)
848{
849 client->subscribeRawMessage(
850 callback: [&client](char* const bytes, const std::size_t size, const std::string& /*ip*/, const std::uint16_t /*port*/)
851 {
852 try
853 {
854 MACRO_DEFER([]() { notifyClientOutputDone(); });
855 if (!client->stopRequested() && !onParsing4Client(bytes, size))
856 {
857 client->requestStop();
858 }
859 }
860 catch (const std::exception& err)
861 {
862 LOG_WRN << err.what();
863 }
864 });
865 client->receive();
866 client->connect(ip: view::info::currentUDPHost(), port: view::info::currentUDPPort());
867}
868
869void Command::executeInConsole() const
870{
871 if (!configure::detail::activateHelper())
872 {
873 std::cout << "exit" << std::endl;
874 return;
875 }
876
877 const auto pendingInputs = mainCLI.get<std::vector<std::string>>(argName: toString(cat: Category::console));
878 if (pendingInputs.empty())
879 {
880 return;
881 }
882
883 constexpr std::string_view greeting = "> ";
884 const auto session = std::make_unique<console::Console>(args: greeting);
885 auto tempClient = std::make_shared<utility::socket::UDPSocket>();
886 launchClient(client&: tempClient);
887 registerOnConsole(session&: *session, client&: tempClient);
888
889 for (std::ostringstream out{}; const auto& input : pendingInputs)
890 {
891 try
892 {
893 using RetCode = console::Console::RetCode;
894 out << greeting << input << '\n';
895 std::cout << out.str() << std::flush;
896 out.str(s: "");
897 out.clear();
898 if (session->optionExecutor(option: input) == RetCode::quit)
899 {
900 break;
901 }
902 }
903 catch (const std::exception& err)
904 {
905 LOG_WRN << err.what();
906 interactionLatency();
907 }
908 }
909 tempClient->send(message: buildDisconnectRequest());
910 tempClient->join();
911 interactionLatency();
912}
913
914void Command::showHelpMessage() const
915{
916 std::cout << mainCLI.help().str() << std::flush;
917}
918
919void Command::dumpConfiguration()
920{
921 std::cout << configure::dumpDefaultConfig() << std::endl;
922}
923
924void Command::displayVersionInfo() const
925{
926 validateDependencies();
927
928 const auto briefReview = std::format(
929 fmt: "\033[7m\033[49m{}"
930#ifndef NDEBUG
931 " DEBUG VERSION {} "
932#else
933 " RELEASE VERSION {} "
934#endif
935 "\033[0m\nBuilt {} with {} for {} on {}.\n{}\n",
936 args: build::banner(),
937 args: mainCLI.version(),
938 args: build::revision(),
939 args: build::compiler(),
940 args: build::processor(),
941 args: build::date(),
942 args: build::copyright());
943 std::cout << briefReview << std::flush;
944}
945
946//! @brief Perform the specific operation for Category::console.
947template <>
948template <>
949void Command::LocalNotifier::Handler<Category::console>::execute() const
950{
951 inst.executeInConsole();
952}
953
954//! @brief Perform the specific operation for Category::dump.
955template <>
956template <>
957void Command::LocalNotifier::Handler<Category::dump>::execute() const
958{
959 Command::dumpConfiguration();
960}
961
962//! @brief Perform the specific operation for Category::help.
963template <>
964template <>
965void Command::LocalNotifier::Handler<Category::help>::execute() const
966{
967 inst.showHelpMessage();
968}
969
970//! @brief Perform the specific operation for Category::version.
971template <>
972template <>
973void Command::LocalNotifier::Handler<Category::version>::execute() const
974{
975 inst.displayVersionInfo();
976}
977
978void Command::validateDependencies() const
979{
980 const bool isNativeVerMatched = utility::common::areStringsEqual(
981 str1: mainCLI.version().data(),
982 str2: utility::argument::version(),
983 others: utility::benchmark::version(),
984 others: utility::common::version(),
985 others: utility::currying::version(),
986 others: utility::fsm::version(),
987 others: utility::io::version(),
988 others: utility::json::version(),
989 others: utility::macro::version(),
990 others: utility::memory::version(),
991 others: utility::reflection::version(),
992 others: utility::socket::version(),
993 others: utility::thread::version(),
994 others: utility::time::version());
995 if (!isNativeVerMatched)
996 {
997 throw std::runtime_error{std::format(
998 fmt: "Dependencies version number mismatch. Expected main version: {} ({}).",
999 args: mainCLI.title(),
1000 args: mainCLI.version())};
1001 }
1002
1003 const auto& choiceRegistry = taskDispatcher.extraChoiceRegistry;
1004 const bool isExtraVerMatched = (versionLinks.count(x: {subCLIAppAlgo.title(), subCLIAppAlgo.version()})
1005 == choiceRegistry.at(k: subCLIAppAlgo.title()).size())
1006 && (versionLinks.count(x: {subCLIAppDp.title(), subCLIAppDp.version()})
1007 == choiceRegistry.at(k: subCLIAppDp.title()).size())
1008 && (versionLinks.count(x: {subCLIAppDs.title(), subCLIAppDs.version()})
1009 == choiceRegistry.at(k: subCLIAppDs.title()).size())
1010 && (versionLinks.count(x: {subCLIAppNum.title(), subCLIAppNum.version()})
1011 == choiceRegistry.at(k: subCLIAppNum.title()).size());
1012 if (!isExtraVerMatched)
1013 {
1014 throw std::runtime_error{std::format(
1015 fmt: "Dependencies version number mismatch. Expected sub-version: {} ({}), {} ({}), {} ({}), {} ({}).",
1016 args: subCLIAppAlgo.title(),
1017 args: subCLIAppAlgo.version(),
1018 args: subCLIAppDp.title(),
1019 args: subCLIAppDp.version(),
1020 args: subCLIAppDs.title(),
1021 args: subCLIAppDs.version(),
1022 args: subCLIAppNum.title(),
1023 args: subCLIAppNum.version())};
1024 }
1025}
1026
1027void Command::enterConsoleMode()
1028try
1029{
1030 if (!configure::detail::activateHelper())
1031 {
1032 std::cout << "exit" << std::endl;
1033 return;
1034 }
1035 LOG_DBG << "Enter console mode.";
1036
1037 interactionLatency();
1038 const char* const userEnv = std::getenv(name: "USER"); // NOLINT(concurrency-mt-unsafe)
1039 const std::string userName = userEnv ? userEnv : "USER";
1040 std::array<char, HOST_NAME_MAX> hostName{};
1041 if (::gethostname(name: hostName.data(), len: hostName.size()) != 0)
1042 {
1043 std::strncpy(dest: hostName.data(), src: "HOSTNAME", n: hostName.size() - 1);
1044 hostName[HOST_NAME_MAX - 1] = '\0';
1045 }
1046 const auto greeting = userName + '@' + hostName.data() + " foo > ";
1047 const auto session = std::make_unique<console::Console>(args: greeting);
1048 auto permClient = std::make_shared<utility::socket::TCPSocket>();
1049 launchClient(client&: permClient);
1050 registerOnConsole(session&: *session, client&: permClient);
1051
1052 std::cout << build::banner() << std::endl;
1053 using RetCode = console::Console::RetCode;
1054 auto retCode = RetCode::success;
1055 do
1056 {
1057 try
1058 {
1059 retCode = session->readLine();
1060 }
1061 catch (const std::exception& err)
1062 {
1063 LOG_WRN << err.what();
1064 }
1065 session->setGreeting(greeting);
1066 interactionLatency();
1067 }
1068 while (retCode != RetCode::quit);
1069 permClient->send(message: buildDisconnectRequest());
1070 permClient->join();
1071 interactionLatency();
1072
1073 LOG_DBG << "Exit console mode.";
1074}
1075catch (const std::exception& err)
1076{
1077 LOG_ERR << err.what();
1078}
1079
1080auto Command::processConsoleInputs(const std::function<void()>& handling)
1081{
1082 using RetCode = console::Console::RetCode;
1083 auto retCode = RetCode::success;
1084 try
1085 {
1086 if (handling)
1087 {
1088 handling();
1089 }
1090 }
1091 catch (const std::exception& err)
1092 {
1093 retCode = RetCode::error;
1094 LOG_WRN << err.what();
1095 }
1096 interactionLatency();
1097 return retCode;
1098}
1099
1100template <typename Sock>
1101void Command::registerOnConsole(console::Console& session, std::shared_ptr<Sock>& client)
1102{
1103 static constexpr auto gracefulReset = []<help::ExtHelper Helper>() constexpr
1104 {
1105 using namespace help; // NOLINT(google-build-using-namespace)
1106 triggerEvent<Helper>(ExtEvent::reload);
1107 triggerEvent<Helper>(ExtEvent::startup);
1108 };
1109 const auto asyncReqSender = [&client](const auto& inputs)
1110 {
1111 return processConsoleInputs(
1112 handling: [&client, &inputs]()
1113 {
1114 auto reqBuffer = utility::common::base64Encode(data: std::accumulate(
1115 inputs.cbegin(),
1116 inputs.cend(),
1117 std::string{},
1118 [](const auto& acc, const auto& token) { return acc.empty() ? token : (acc + ' ' + token); }));
1119 client->send(std::move(reqBuffer));
1120 waitClientOutputDone();
1121 });
1122 };
1123
1124 session.registerOption(
1125 name: "refresh",
1126 description: "refresh the outputs",
1127 callback: [](const auto& /*inputs*/)
1128 {
1129 return processConsoleInputs(
1130 handling: []()
1131 {
1132 using log::Log;
1133 gracefulReset.template operator()<Log>();
1134 LOG_INF_F("Refreshed the {} outputs.", Log::name);
1135 });
1136 });
1137 session.registerOption(
1138 name: "reconnect",
1139 description: "reconnect to the servers",
1140 callback: [&client](const auto& /*inputs*/)
1141 {
1142 return processConsoleInputs(
1143 handling: [&client]()
1144 {
1145 client->send(buildDisconnectRequest());
1146 client->join();
1147 interactionLatency();
1148 client.reset();
1149
1150 using view::View;
1151 gracefulReset.template operator()<View>();
1152 client = std::make_shared<Sock>();
1153 launchClient(client);
1154 LOG_INF_F("Reconnected to the {} servers.", View::name);
1155 });
1156 });
1157
1158 auto supportedOptions = view::info::currentSupportedOptions();
1159 decltype(supportedOptions) validOptions{};
1160 for (auto iterator = supportedOptions.cbegin(); iterator != supportedOptions.cend();)
1161 {
1162 auto node = supportedOptions.extract(pos: iterator++);
1163 auto& key = node.key();
1164 key.erase(
1165 std::ranges::remove_if(key, [l = std::locale{}](const auto c) { return std::isspace(c, l); }).begin(),
1166 key.cend());
1167 validOptions.insert(nh: std::move(node));
1168 }
1169 for (const auto& [name, description] : validOptions)
1170 {
1171 session.registerOption(name, description, callback: asyncReqSender);
1172 }
1173}
1174
1175bool Command::onParsing4Client(char* const bytes, const std::size_t size)
1176{
1177 return (size == 0) || view::View::Access().onParsing(bytes, size);
1178}
1179
1180void Command::waitClientOutputDone()
1181{
1182 view::View::Sync().waitTaskDone();
1183}
1184
1185void Command::notifyClientOutputDone()
1186{
1187 view::View::Sync().notifyTaskDone();
1188}
1189
1190std::string Command::buildDisconnectRequest()
1191{
1192 return utility::common::base64Encode(data: view::exitSymbol);
1193}
1194
1195void Command::interactionLatency()
1196{
1197 constexpr auto latency = std::chrono::milliseconds{10};
1198 std::this_thread::sleep_for(rtime: latency);
1199}
1200
1201//! @brief Safely execute the command line interfaces using the given arguments.
1202//! @param argc - argument count
1203//! @param argv - argument vector
1204//! @return successful or failed to execute
1205bool executeCLI(const int argc, const char* const argv[])
1206try
1207{
1208 cliSem.acquire();
1209 const bool status = getInstance().execute(argc, argv);
1210 cliSem.release();
1211 return status;
1212}
1213catch (...)
1214{
1215 cliSem.release();
1216 throw;
1217}
1218} // namespace application::command
1219