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 <ranges>
15#else
16#include "application/pch/precompiled_header.hpp"
17#endif
18
19#include "utility/include/benchmark.hpp"
20#include "utility/include/currying.hpp"
21#include "utility/include/time.hpp"
22
23namespace application
24{
25namespace command
26{
27//! @brief Anonymous namespace.
28inline namespace
29{
30//! @brief The semaphore that controls the maximum access limit.
31std::counting_semaphore<1> cliSem(1); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
32} // namespace
33
34//! @brief Manage external helpers.
35namespace help
36{
37//! @brief Constraint for external helpers.
38//! @tparam Type - type of helper
39template <typename Type>
40concept ExtHelper =
41 !std::is_constructible_v<Type> && !std::is_copy_constructible_v<Type> && !std::is_copy_assignable_v<Type>
42 && !std::is_move_constructible_v<Type> && !std::is_move_assignable_v<Type> && requires (const Type& /*helper*/) {
43 { Type::getInstance() } -> std::same_as<std::shared_ptr<Type>>;
44 };
45
46//! @brief Enumerate specific events to control external helpers.
47enum class ExtEvent : std::uint8_t
48{
49 //! @brief Startup.
50 startup,
51 //! @brief Shutdown.
52 shutdown,
53 //! @brief Reload.
54 reload
55};
56
57//! @brief Trigger the external helper with event.
58//! @tparam Helper - type of helper
59//! @param event - target event
60template <ExtHelper Helper>
61requires std::derived_from<Helper, utility::fsm::FSM<Helper>>
62static void triggerEvent(const ExtEvent event)
63{
64 if (!configure::detail::activateHelper())
65 {
66 return;
67 }
68
69 switch (event)
70 {
71 using Controller = configure::Controller<Helper>;
72 case ExtEvent::startup:
73 Controller().startup();
74 return;
75 case ExtEvent::shutdown:
76 Controller().shutdown();
77 return;
78 case ExtEvent::reload:
79 Controller().reload();
80 return;
81 default:
82 break;
83 }
84}
85
86//! @brief Helper daemon service.
87//! @tparam Helpers - type of arguments of helper
88template <ExtHelper... Helpers>
89requires (std::derived_from<Helpers, utility::fsm::FSM<Helpers>> && ...)
90static void daemonService()
91{
92 utility::thread::Thread extendedJob(sizeof...(Helpers));
93 (extendedJob.enqueue(Helpers::name, &Helpers::service, Helpers::getInstance()), ...);
94}
95
96// NOLINTBEGIN(readability-static-accessed-through-instance)
97//! @brief Coroutine for managing the lifecycle of helper components.
98//! @tparam Hs - type of helpers
99//! @return awaitable instance
100template <typename... Hs>
101static schedule::Awaitable launchLifecycle()
102{
103 if (!configure::detail::activateHelper())
104 {
105 co_return;
106 }
107
108 std::latch waitPoint(1);
109 const std::jthread daemon(
110 [&waitPoint]()
111 {
112 daemonService<Hs...>();
113 waitPoint.count_down();
114 });
115 std::barrier syncPoint(sizeof...(Hs) + 1);
116 static constexpr auto publish = [](std::barrier<>& phase, const ExtEvent event) constexpr
117 {
118 std::vector<std::jthread> senders{};
119 senders.reserve(n: sizeof...(Hs));
120 (senders.emplace_back(args: std::jthread{[&phase, event]()
121 {
122 triggerEvent<Hs>(event);
123 phase.arrive_and_wait();
124 }}),
125 ...);
126 phase.arrive_and_wait();
127 };
128
129 co_await std::suspend_always{};
130 publish(syncPoint, ExtEvent::startup);
131 co_await std::suspend_always{};
132 publish(syncPoint, ExtEvent::shutdown);
133
134 waitPoint.wait();
135}
136// NOLINTEND(readability-static-accessed-through-instance)
137} // namespace help
138
139// clang-format off
140//! @brief Mapping table for enum and attribute about command categories. X macro.
141#define COMMAND_CATEGORY_X_MACRO_MAPPING \
142 X(Category::console, "c", "run options in console mode and exit\n" \
143 "separate with quotes" ) \
144 X(Category::dump , "d", "dump default configuration and exit" ) \
145 X(Category::help , "h", "show this help message and exit" ) \
146 X(Category::version, "v", "show version information and exit" )
147// clang-format on
148
149//! @brief Map the alias name.
150//! @param cat - native category
151//! @return alias name
152static consteval std::string_view mappedAlias(const Category cat)
153{
154//! @cond
155#define X(enum, descr, alias) {descr, alias},
156 constexpr std::string_view table[][2] = {COMMAND_CATEGORY_X_MACRO_MAPPING};
157 static_assert((sizeof(table) / sizeof(table[0])) == Bottom<Category>::value);
158 return table[static_cast<std::underlying_type_t<Category>>(cat)][0];
159//! @endcond
160#undef X
161}
162
163//! @brief Map the description.
164//! @param cat - native category
165//! @return description
166static consteval std::string_view mappedDescr(const Category cat)
167{
168//! @cond
169#define X(enum, descr, alias) {descr, alias},
170 constexpr std::string_view table[][2] = {COMMAND_CATEGORY_X_MACRO_MAPPING};
171 static_assert((sizeof(table) / sizeof(table[0])) == Bottom<Category>::value);
172 return table[static_cast<std::underlying_type_t<Category>>(cat)][1];
173//! @endcond
174#undef X
175}
176
177#undef COMMAND_CATEGORY_X_MACRO_MAPPING
178
179//! @brief Convert category enumeration to string.
180//! @param cat - native category
181//! @return category name
182static constexpr std::string_view toString(const Category cat)
183{
184 constexpr std::array<std::string_view, Bottom<Category>::value> stringify = {
185 MACRO_STRINGIFY(console), MACRO_STRINGIFY(dump), MACRO_STRINGIFY(help), MACRO_STRINGIFY(version)};
186 return stringify.at(n: static_cast<std::underlying_type_t<Category>>(cat));
187}
188
189//! @brief Get the Command instance.
190//! @return reference of the Command object
191Command& getInstance()
192{
193 static Command commander{};
194 return commander;
195}
196
197Command::Command()
198{
199 initializeNativeCLI();
200 initializeExtraCLI();
201}
202
203Command::~Command()
204{
205 clearSelected();
206}
207
208bool Command::doExecute(const int argc, const char* const argv[])
209try
210{
211 isFaulty.store(i: false);
212 isParsed.store(i: false);
213 auto helpCtrl = help::launchLifecycle<log::Log, view::View>();
214 schedule::enterNextPhase(awaitable&: helpCtrl);
215
216 if (argc > 1)
217 {
218 constexpr std::uint8_t endNum = 2;
219 utility::thread::Thread scheduledJob(endNum);
220 scheduledJob.enqueue(name: title + "-front", func: &Command::frontEndHandler, args: this, args: argc, args&: argv);
221 scheduledJob.enqueue(name: title + "-back", func: &Command::backEndHandler, args: this);
222 }
223 else
224 {
225 enterConsoleMode();
226 }
227
228 schedule::enterNextPhase(awaitable&: helpCtrl);
229 return !isFaulty.load();
230}
231catch (const std::exception& err)
232{
233 isFaulty.store(i: true);
234 LOG_ERR << err.what();
235 return !isFaulty.load();
236}
237
238template <Category Cat>
239void Command::buildSimpleFlagInMainCLI()
240{
241 mainCLI.addArgument(fewArgs: shortPrefix + std::string{mappedAlias(cat: Cat)}, fewArgs: longPrefix + std::string{toString(cat: Cat)})
242 .argsNum(num: 0)
243 .implicitValue(value: true)
244 .help(message: mappedDescr(cat: Cat));
245 builtInNotifier.attach(key: Cat, handler: std::make_shared<LocalNotifier::Handler<Cat>>(*this));
246}
247
248//! @brief Build a custom option in the main cli for Category::console.
249template <>
250void Command::buildCustomOptionInMainCLI<Category::console>()
251{
252 using ArgsNumPattern = utility::argument::ArgsNumPattern;
253 mainCLI
254 .addArgument(
255 fewArgs: shortPrefix + std::string{mappedAlias(cat: Category::console)},
256 fewArgs: longPrefix + std::string{toString(cat: Category::console)})
257 .argsNum(pattern: ArgsNumPattern::any)
258 .defaultValue<console::Console::Args>(value: {"usage"})
259 .appending()
260 .action(
261 callable: [](const std::string& input)
262 {
263 if (std::ranges::all_of(input, [l = std::locale{}](const auto c) { return std::isspace(c, l); }))
264 {
265 throw std::runtime_error{"Invalid " + std::string{toString(cat: Category::console)} + " command."};
266 }
267 return input;
268 })
269 .metaVariable(variable: "CMD")
270 .help(message: mappedDescr(cat: Category::console));
271 builtInNotifier.attach(key: Category::console, handler: std::make_shared<LocalNotifier::Handler<Category::console>>(args&: *this));
272}
273
274void Command::mainCLISetup()
275{
276 buildSimpleFlagInMainCLI<Category::help>();
277 buildSimpleFlagInMainCLI<Category::version>();
278 buildSimpleFlagInMainCLI<Category::dump>();
279 buildCustomOptionInMainCLI<Category::console>();
280}
281
282template <typename Mapped>
283auto& Command::resolveSubCLI()
284{
285 if constexpr (std::is_same_v<Mapped, reg_algo::ApplyAlgorithm> || reg_algo::Registrant<Mapped>)
286 {
287 return subCLIAppAlgo;
288 }
289 else if constexpr (std::is_same_v<Mapped, reg_dp::ApplyDesignPattern> || reg_dp::Registrant<Mapped>)
290 {
291 return subCLIAppDp;
292 }
293 else if constexpr (std::is_same_v<Mapped, reg_ds::ApplyDataStructure> || reg_ds::Registrant<Mapped>)
294 {
295 return subCLIAppDs;
296 }
297 else if constexpr (std::is_same_v<Mapped, reg_num::ApplyNumeric> || reg_num::Registrant<Mapped>)
298 {
299 return subCLIAppNum;
300 }
301 throw std::runtime_error{"Unknown sub-command registration."};
302}
303
304template <typename SubCLI, typename Intf>
305void Command::injectNewSubCLI(Intf&& intf)
306{
307 using schedule::meta::descr;
308 auto& subCLI = resolveSubCLI<SubCLI>();
309 auto& checklist = scheduleDispatcher.extraChecklist;
310 checklist.emplace(subCLI.title(), std::forward<Intf>(intf));
311 subCLI.addDescription(descr<SubCLI>());
312 subCLI
313 .addArgument(
314 shortPrefix + std::string{mappedAlias(cat: Category::help)}, longPrefix + std::string{toString(cat: Category::help)})
315 .argsNum(0)
316 .implicitValue(true)
317 .help(mappedDescr(cat: Category::help));
318 mainCLI.addSubParser(parser&: subCLI);
319}
320
321// NOLINTBEGIN(google-build-using-namespace)
322template <typename Cat>
323void Command::addNewCategoryToSubCLI(const std::string_view version)
324{
325 using namespace reg_algo;
326 using namespace reg_dp;
327 using namespace reg_ds;
328 using namespace reg_num;
329 using Attr = schedule::ExtraManager::Attr;
330 using schedule::extra::SetChoice, schedule::extra::RunCandidates, schedule::meta::name, schedule::meta::alias,
331 schedule::meta::descr, schedule::meta::choice;
332 auto& subCLI = resolveSubCLI<Cat>();
333 auto& registry = scheduleDispatcher.extraChoiceRegistry[subCLI.title()];
334 auto candidates = choice<Cat>();
335 registry.emplace(name<Cat>(), Attr{candidates, Cat{}});
336 subCLI.addArgument(shortPrefix + std::string{alias<Cat>()}, longPrefix + std::string{name<Cat>()})
337 .argsNum(0, candidates.size())
338 .template defaultValue<decltype(candidates)>(std::move(candidates))
339 .remaining()
340 .metaVariable("OPT")
341 .help(descr<Cat>());
342 applyingForwarder.registerHandler([](const SetChoice<Cat>& msg) { setChoice<Cat>(msg.choice); });
343 applyingForwarder.registerHandler([](const RunCandidates<Cat>& msg) { runCandidates<Cat>(msg.candidates); });
344 versionLinks.emplace(VerLinkKey{subCLI.title(), version}, name<Cat>());
345}
346
347//! @brief Set up the sub-command line interface (algorithm module).
348template <>
349void Command::subCLISetup<reg_algo::ApplyAlgorithm>()
350{
351 using namespace reg_algo;
352 using Intf = schedule::ExtraManager::Intf;
353 injectNewSubCLI<ApplyAlgorithm>(intf: Intf{manage::present, manage::clear});
354 addNewCategoryToSubCLI<MatchMethod>(version: match::version());
355 addNewCategoryToSubCLI<NotationMethod>(version: notation::version());
356 addNewCategoryToSubCLI<OptimalMethod>(version: optimal::version());
357 addNewCategoryToSubCLI<SearchMethod>(version: search::version());
358 addNewCategoryToSubCLI<SortMethod>(version: sort::version());
359}
360
361//! @brief Set up the sub-command line interface (design pattern module).
362template <>
363void Command::subCLISetup<reg_dp::ApplyDesignPattern>()
364{
365 using namespace reg_dp;
366 using Intf = schedule::ExtraManager::Intf;
367 injectNewSubCLI<ApplyDesignPattern>(intf: Intf{manage::present, manage::clear});
368 addNewCategoryToSubCLI<BehavioralInstance>(version: behavioral::version());
369 addNewCategoryToSubCLI<CreationalInstance>(version: creational::version());
370 addNewCategoryToSubCLI<StructuralInstance>(version: structural::version());
371}
372
373//! @brief Set up the sub-command line interface (data structure module).
374template <>
375void Command::subCLISetup<reg_ds::ApplyDataStructure>()
376{
377 using namespace reg_ds;
378 using Intf = schedule::ExtraManager::Intf;
379 injectNewSubCLI<ApplyDataStructure>(intf: Intf{manage::present, manage::clear});
380 addNewCategoryToSubCLI<CacheInstance>(version: cache::version());
381 addNewCategoryToSubCLI<FilterInstance>(version: filter::version());
382 addNewCategoryToSubCLI<GraphInstance>(version: graph::version());
383 addNewCategoryToSubCLI<HeapInstance>(version: heap::version());
384 addNewCategoryToSubCLI<LinearInstance>(version: linear::version());
385 addNewCategoryToSubCLI<TreeInstance>(version: tree::version());
386}
387
388//! @brief Set up the sub-command line interface (numeric module).
389template <>
390void Command::subCLISetup<reg_num::ApplyNumeric>()
391{
392 using namespace reg_num;
393 using Intf = schedule::ExtraManager::Intf;
394 injectNewSubCLI<ApplyNumeric>(intf: Intf{manage::present, manage::clear});
395 addNewCategoryToSubCLI<ArithmeticMethod>(version: arithmetic::version());
396 addNewCategoryToSubCLI<DivisorMethod>(version: divisor::version());
397 addNewCategoryToSubCLI<IntegralMethod>(version: integral::version());
398 addNewCategoryToSubCLI<PrimeMethod>(version: prime::version());
399}
400// NOLINTEND(google-build-using-namespace)
401
402void Command::initializeNativeCLI()
403{
404 mainCLISetup();
405}
406
407void Command::initializeExtraCLI()
408{
409 subCLISetup<reg_algo::ApplyAlgorithm>();
410 subCLISetup<reg_dp::ApplyDesignPattern>();
411 subCLISetup<reg_ds::ApplyDataStructure>();
412 subCLISetup<reg_num::ApplyNumeric>();
413}
414
415void Command::frontEndHandler(const int argc, const char* const argv[])
416try
417{
418 std::unique_lock<std::mutex> parserLock(parserMtx);
419 mainCLI.clearUsed();
420 mainCLI.parseArgs(argc, argv);
421 precheckSchedule();
422
423 isParsed.store(i: true);
424 parserLock.unlock();
425 parserCond.notify_one();
426}
427catch (const std::exception& err)
428{
429 isParsed.store(i: true);
430 parserCond.notify_one();
431 isFaulty.store(i: true);
432 LOG_WRN << err.what();
433}
434
435void Command::backEndHandler()
436try
437{
438 if (std::unique_lock<std::mutex> parserLock(parserMtx); true)
439 {
440 parserCond.wait(lock&: parserLock, p: [this]() { return isParsed.load(); });
441 }
442
443 if (anySelected())
444 {
445 dispatchTasks();
446 clearSelected();
447 }
448}
449catch (const std::exception& err)
450{
451 clearSelected();
452 isFaulty.store(i: true);
453 LOG_WRN << err.what();
454}
455
456void Command::precheckSchedule()
457{
458 for (auto& spec = scheduleDispatcher.nativeCategories;
459 const auto index : std::views::iota(0U, spec.size())
460 | std::views::filter([this](const auto i) { return mainCLI.isUsed(argName: toString(cat: static_cast<Category>(i))); }))
461 {
462 checkExcessArgs();
463 spec.set(position: index);
464 }
465
466 for ([[maybe_unused]] const auto& [subCLIName, categoryMap] : scheduleDispatcher.extraChoiceRegistry
467 | std::views::filter([this](const auto& subCLIPair)
468 { return mainCLI.isSubcommandUsed(subCLIPair.first) && (checkExcessArgs(), true); }))
469 {
470 const auto& subCLI = mainCLI.at<Argument>(name: subCLIName);
471 const bool notAssigned = !subCLI;
472 scheduleDispatcher.extraHelping = notAssigned || subCLI.isUsed(argName: toString(cat: Category::help));
473 if (notAssigned)
474 {
475 return;
476 }
477
478 for ([[maybe_unused]] const auto& [categoryName, categoryAttr] : categoryMap
479 | std::views::filter([this, &subCLI](const auto& categoryPair)
480 { return subCLI.isUsed(argName: categoryPair.first) && (checkExcessArgs(), true); }))
481 {
482 for (const auto& choice : subCLI.get<std::remove_const_t<decltype(categoryAttr.choices)>>(argName: categoryName))
483 {
484 using schedule::extra::SetChoice;
485 utility::common::patternMatch(
486 var: categoryAttr.event,
487 cases: [this, &choice](auto&& event)
488 { applyingForwarder.onMessage(SetChoice<std::decay_t<decltype(event)>>{choice}); });
489 }
490 }
491 }
492}
493
494bool Command::anySelected() const
495{
496 return !scheduleDispatcher.empty();
497}
498
499void Command::clearSelected()
500{
501 scheduleDispatcher.reset();
502}
503
504void Command::dispatchTasks()
505{
506 if (!scheduleDispatcher.NativeManager::empty())
507 {
508 for (const auto& spec = scheduleDispatcher.nativeCategories;
509 const auto index :
510 std::views::iota(0U, spec.size()) | std::views::filter([&spec](const auto i) { return spec.test(position: i); }))
511 {
512 builtInNotifier.notify(key: static_cast<Category>(index));
513 }
514 }
515
516 if (!scheduleDispatcher.ExtraManager::empty())
517 {
518 if (!scheduleDispatcher.extraHelping)
519 {
520 for ([[maybe_unused]] const auto& [categoryName, categoryAttr] : scheduleDispatcher.extraChoiceRegistry
521 | std::views::filter([this](const auto& subCLIPair)
522 { return scheduleDispatcher.extraChecklist.at(subCLIPair.first).present(); })
523 | std::views::values | std::views::join)
524 {
525 using schedule::extra::RunCandidates;
526 utility::common::patternMatch(
527 var: categoryAttr.event,
528 cases: [this, &candidates = categoryAttr.choices](auto&& event)
529 { applyingForwarder.onMessage(RunCandidates<std::decay_t<decltype(event)>>{candidates}); });
530 }
531 }
532 else if (auto whichUsed = std::views::keys(scheduleDispatcher.extraChoiceRegistry)
533 | std::views::filter([this](const auto& subCLIName)
534 { return mainCLI.isSubcommandUsed(subCLIName); });
535 std::ranges::distance(whichUsed) != 0)
536 {
537 std::cout << mainCLI.at<Argument>(name: *std::ranges::begin(whichUsed)).help().str() << std::flush;
538 }
539 }
540}
541
542void Command::checkExcessArgs()
543{
544 if (anySelected())
545 {
546 clearSelected();
547 throw std::runtime_error{"Excessive arguments were found."};
548 }
549}
550
551void Command::executeInConsole() const
552{
553 if (!configure::detail::activateHelper())
554 {
555 std::cout << "exit" << std::endl;
556 return;
557 }
558
559 const auto pendingInputs = mainCLI.get<console::Console::Args>(argName: toString(cat: Category::console));
560 if (pendingInputs.empty())
561 {
562 return;
563 }
564
565 constexpr std::string_view greeting = "> ";
566 const auto session = std::make_unique<console::Console>(args: greeting);
567 auto tempClient = std::make_shared<utility::socket::UDPSocket>();
568 view::intf::connectFromClient(client&: tempClient);
569 registerOnConsole(session&: *session, client&: tempClient);
570
571 for (std::ostringstream out{}; const auto& input : pendingInputs)
572 {
573 try
574 {
575 using RetCode = console::Console::RetCode;
576 out << greeting << input << '\n';
577 std::cout << out.str() << std::flush;
578 out.str(s: "");
579 out.clear();
580 if (session->optionExecutor(option: input) == RetCode::quit)
581 {
582 break;
583 }
584 }
585 catch (const std::exception& err)
586 {
587 LOG_WRN << err.what();
588 view::interactionLatency();
589 }
590 }
591 tempClient->send(message: view::buildDisconnectRequest());
592 tempClient->join();
593 view::interactionLatency();
594}
595
596void Command::showHelpMessage() const
597{
598 std::cout << mainCLI.help().str() << std::flush;
599}
600
601void Command::dumpConfiguration()
602{
603 std::cout << configure::dumpDefaultConfig() << std::endl;
604}
605
606void Command::displayVersionInfo() const
607{
608 validateDependencies();
609
610 const auto briefReview = std::format(
611 fmt: "\033[7m\033[49m{}"
612#ifndef NDEBUG
613 " DEBUG VERSION {} "
614#else
615 " RELEASE VERSION {} "
616#endif
617 "\033[0m\nBuilt {} with {} for {} on {}.\n{}\n",
618 args: build::banner(),
619 args: mainCLI.version(),
620 args: build::revision(),
621 args: build::compiler(),
622 args: build::processor(),
623 args: build::date(),
624 args: build::copyright());
625 std::cout << briefReview << std::flush;
626}
627
628void Command::validateDependencies() const
629{
630 const bool isNativeVerMatched = utility::common::areStringsEqual(
631 str1: mainCLI.version().data(),
632 str2: utility::argument::version(),
633 others: utility::benchmark::version(),
634 others: utility::common::version(),
635 others: utility::currying::version(),
636 others: utility::fsm::version(),
637 others: utility::io::version(),
638 others: utility::json::version(),
639 others: utility::macro::version(),
640 others: utility::memory::version(),
641 others: utility::reflection::version(),
642 others: utility::socket::version(),
643 others: utility::thread::version(),
644 others: utility::time::version());
645 if (!isNativeVerMatched)
646 {
647 throw std::runtime_error{std::format(
648 fmt: "Dependencies version number mismatch. Expected main version: {} ({}).",
649 args: mainCLI.title(),
650 args: mainCLI.version())};
651 }
652
653 const auto& choiceRegistry = scheduleDispatcher.extraChoiceRegistry;
654 const bool isExtraVerMatched = (versionLinks.count(x: {subCLIAppAlgo.title(), subCLIAppAlgo.version()})
655 == choiceRegistry.at(k: subCLIAppAlgo.title()).size())
656 && (versionLinks.count(x: {subCLIAppDp.title(), subCLIAppDp.version()})
657 == choiceRegistry.at(k: subCLIAppDp.title()).size())
658 && (versionLinks.count(x: {subCLIAppDs.title(), subCLIAppDs.version()})
659 == choiceRegistry.at(k: subCLIAppDs.title()).size())
660 && (versionLinks.count(x: {subCLIAppNum.title(), subCLIAppNum.version()})
661 == choiceRegistry.at(k: subCLIAppNum.title()).size());
662 if (!isExtraVerMatched)
663 {
664 throw std::runtime_error{std::format(
665 fmt: "Dependencies version number mismatch. Expected sub-version: {} ({}), {} ({}), {} ({}), {} ({}).",
666 args: subCLIAppAlgo.title(),
667 args: subCLIAppAlgo.version(),
668 args: subCLIAppDp.title(),
669 args: subCLIAppDp.version(),
670 args: subCLIAppDs.title(),
671 args: subCLIAppDs.version(),
672 args: subCLIAppNum.title(),
673 args: subCLIAppNum.version())};
674 }
675}
676
677void Command::enterConsoleMode()
678try
679{
680 if (!configure::detail::activateHelper())
681 {
682 std::cout << "exit" << std::endl;
683 return;
684 }
685 LOG_DBG << "Enter console mode.";
686
687 view::interactionLatency();
688 const char* const userEnv = std::getenv(name: "USER"); // NOLINT(concurrency-mt-unsafe)
689 const std::string userName = userEnv ? userEnv : "USER";
690 std::array<char, HOST_NAME_MAX> hostName{};
691 if (::gethostname(name: hostName.data(), len: hostName.size()) != 0)
692 {
693 std::strncpy(dest: hostName.data(), src: "HOSTNAME", n: hostName.size() - 1);
694 hostName[HOST_NAME_MAX - 1] = '\0';
695 }
696 const auto greeting = userName + '@' + hostName.data() + " foo > ";
697 const auto session = std::make_unique<console::Console>(args: greeting);
698 auto permClient = std::make_shared<utility::socket::TCPSocket>();
699 view::intf::connectFromClient(client&: permClient);
700 registerOnConsole(session&: *session, client&: permClient);
701
702 std::cout << build::banner() << std::endl;
703 using RetCode = console::Console::RetCode;
704 auto retCode = RetCode::success;
705 do
706 {
707 try
708 {
709 retCode = session->readLine();
710 }
711 catch (const std::exception& err)
712 {
713 LOG_WRN << err.what();
714 }
715 session->setGreeting(greeting);
716 view::interactionLatency();
717 }
718 while (retCode != RetCode::quit);
719 permClient->send(message: view::buildDisconnectRequest());
720 permClient->join();
721 view::interactionLatency();
722
723 LOG_DBG << "Exit console mode.";
724}
725catch (const std::exception& err)
726{
727 LOG_ERR << err.what();
728}
729
730auto Command::processConsoleInputs(const std::function<void()>& handling)
731{
732 using RetCode = console::Console::RetCode;
733 auto retCode = RetCode::success;
734 try
735 {
736 if (handling)
737 {
738 handling();
739 }
740 }
741 catch (const std::exception& err)
742 {
743 retCode = RetCode::error;
744 LOG_WRN << err.what();
745 }
746 view::interactionLatency();
747 return retCode;
748}
749
750template <typename Sock>
751void Command::registerOnConsole(console::Console& session, std::shared_ptr<Sock>& client)
752{
753 static constexpr auto gracefulReset = []<help::ExtHelper Helper>() constexpr
754 {
755 using namespace help; // NOLINT(google-build-using-namespace)
756 triggerEvent<Helper>(ExtEvent::reload);
757 triggerEvent<Helper>(ExtEvent::startup);
758 };
759 const auto asyncReqSender = [&client](const auto& inputs)
760 { return processConsoleInputs(handling: [&client, &inputs]() { view::intf::forwardByClient(client, inputs); }); };
761
762 session.registerOption(
763 name: "refresh",
764 description: "refresh the outputs",
765 callback: [](const auto& /*inputs*/)
766 {
767 return processConsoleInputs(
768 handling: []()
769 {
770 using log::Log;
771 gracefulReset.template operator()<Log>();
772 LOG_INF_F("Refreshed the {} outputs.", Log::name);
773 });
774 });
775 session.registerOption(
776 name: "reconnect",
777 description: "reconnect to the servers",
778 callback: [&client](const auto& /*inputs*/)
779 {
780 return processConsoleInputs(
781 handling: [&client]()
782 {
783 client->send(view::buildDisconnectRequest());
784 client->join();
785 view::interactionLatency();
786 client.reset();
787
788 using view::View;
789 gracefulReset.template operator()<View>();
790 client = std::make_shared<Sock>();
791 view::intf::connectFromClient(client);
792 LOG_INF_F("Reconnected to the {} servers.", View::name);
793 });
794 });
795
796 auto supportedOptions = view::obtainSupportedOptions();
797 decltype(supportedOptions) validOptions{};
798 for (auto iterator = supportedOptions.cbegin(); iterator != supportedOptions.cend();)
799 {
800 auto node = supportedOptions.extract(pos: iterator++);
801 auto& key = node.key();
802 key.erase(
803 std::ranges::remove_if(key, [l = std::locale{}](const auto c) { return std::isspace(c, l); }).begin(),
804 key.cend());
805 validOptions.insert(nh: std::move(node));
806 }
807 for (const auto& [name, description] : validOptions)
808 {
809 session.registerOption(name, description, callback: asyncReqSender);
810 }
811}
812
813//! @brief Safely execute the command line interfaces using the given arguments.
814//! @param argc - argument count
815//! @param argv - argument vector
816//! @return successful or failed to execute
817bool executeCLI(const int argc, const char* const argv[])
818try
819{
820 cliSem.acquire();
821 const bool status = getInstance().doExecute(argc, argv);
822 cliSem.release();
823 return status;
824}
825catch (...)
826{
827 cliSem.release();
828 throw;
829}
830} // namespace command
831
832//! @brief Perform the specific operation for Category::console.
833template <>
834template <>
835void command::LocalNotifier::Handler<command::Category::console>::execute() const
836{
837 ctx.executeInConsole();
838}
839
840//! @brief Perform the specific operation for Category::dump.
841template <>
842template <>
843void command::LocalNotifier::Handler<command::Category::dump>::execute() const
844{
845 command::Command::dumpConfiguration();
846}
847
848//! @brief Perform the specific operation for Category::help.
849template <>
850template <>
851void command::LocalNotifier::Handler<command::Category::help>::execute() const
852{
853 ctx.showHelpMessage();
854}
855
856//! @brief Perform the specific operation for Category::version.
857template <>
858template <>
859void command::LocalNotifier::Handler<command::Category::version>::execute() const
860{
861 ctx.displayVersionInfo();
862}
863} // namespace application
864