1//! @file command.hpp
2//! @author ryftchen
3//! @brief The declarations (command) in the application module.
4//! @version 0.1.0
5//! @copyright Copyright (c) 2022-2025 ryftchen. All rights reserved.
6
7#pragma once
8
9#include "action.hpp"
10#include "build.hpp"
11#include "console.hpp"
12
13#include "utility/include/argument.hpp"
14
15//! @brief The application module.
16namespace application // NOLINT(modernize-concat-nested-namespaces)
17{
18//! @brief Command-line-related functions in the application module.
19namespace command
20{
21//! @brief Instance title.
22inline constexpr std::string title = "commander";
23
24//! @brief Represent the maximum value of an enum.
25//! @tparam Enum - type of specific enum
26template <typename Enum>
27struct Bottom;
28
29//! @brief Enumerate specific native categories.
30enum class Category : std::uint8_t
31{
32 //! @brief Console.
33 console,
34 //! @brief Dump.
35 dump,
36 //! @brief Help.
37 help,
38 //! @brief Version.
39 version
40};
41//! @brief Store the maximum value of the Category enum.
42template <>
43struct Bottom<Category>
44{
45 //! @brief Maximum value of the Category enum.
46 static constexpr std::uint8_t value{4};
47};
48
49//! @brief Gather and notify handlers.
50//! @tparam Key - type of key
51//! @tparam Inst - type of instance
52template <typename Key, typename Inst>
53class Notifier
54{
55public:
56 //! @brief The base procedure when notified.
57 class ProcBase
58 {
59 public:
60 //! @brief Construct a new ProcBase object.
61 ProcBase() = default;
62 //! @brief Destroy the ProcBase object.
63 virtual ~ProcBase() = default;
64 //! @brief Construct a new ProcBase object.
65 ProcBase(const ProcBase&) = default;
66 //! @brief Construct a new ProcBase object.
67 ProcBase(ProcBase&&) noexcept = default;
68 //! @brief The operator (=) overloading of ProcBase class.
69 //! @return reference of the ProcBase object
70 ProcBase& operator=(const ProcBase&) = default;
71 //! @brief The operator (=) overloading of ProcBase class.
72 //! @return reference of the ProcBase object
73 ProcBase& operator=(ProcBase&&) noexcept = default;
74
75 //! @brief Perform the specific operation.
76 virtual void execute() const = 0;
77 };
78 //! @brief The procedure when notified.
79 //! @tparam CRTP - type of derived class for CRTP
80 template <typename CRTP>
81 class Proc : public ProcBase
82 {
83 public:
84 //! @brief Perform the specific operation.
85 void execute() const override { static_cast<const CRTP&>(*this).execute(); }
86 };
87 //! @brief The handler used to trigger a procedure when notified.
88 //! @tparam key - specific key
89 template <Key key>
90 class Handler : public Proc<Handler<key>>
91 {
92 public:
93 //! @brief Construct a new Handler object.
94 //! @param inst - involved instance
95 explicit Handler(const Inst& inst) : inst{inst} {}
96
97 //! @brief Perform the specific operation.
98 void execute() const override;
99
100 private:
101 //! @brief The involved instance.
102 const Inst& inst{};
103 };
104
105 //! @brief Attach a handler with a specific key to the notifier.
106 //! @param key - specific key
107 //! @param handler - handler to be attached
108 void attach(const Key key, std::shared_ptr<ProcBase> handler);
109 //! @brief Notify the handler associated with the given key.
110 //! @param key - specific key
111 void notify(const Key key) const;
112
113private:
114 //! @brief Map of handlers identified by a key.
115 std::map<Key, std::shared_ptr<ProcBase>> handlers{};
116};
117
118//! @brief Execute the command line.
119class Command final
120{
121public:
122 //! @brief Destroy the Command object.
123 ~Command();
124 //! @brief Construct a new Command object.
125 Command(const Command&) = delete;
126 //! @brief Construct a new Command object.
127 Command(Command&&) = delete;
128 //! @brief The operator (=) overloading of Command class.
129 //! @return reference of the Command object
130 Command& operator=(const Command&) = delete;
131 //! @brief The operator (=) overloading of Command class.
132 //! @return reference of the Command object
133 Command& operator=(Command&&) = delete;
134
135 friend Command& getInstance();
136
137private:
138 //! @brief Construct a new Command object.
139 Command();
140
141 //! @brief Mutex for controlling parser.
142 mutable std::mutex parserMtx;
143 //! @brief The synchronization condition for foreground and background. Use with parserMtx.
144 std::condition_variable parserCond;
145 //! @brief Flag to indicate whether parsing of arguments is completed.
146 std::atomic_bool isParsed{false};
147 //! @brief Parse argument helper for commander.
148 utility::argument::Argument mainCLI{"foo", build::version()};
149 //! @brief Parse argument helper to apply algorithm.
150 utility::argument::Argument subCLIAppAlgo{action::name<reg_algo::ApplyAlgorithm>(), reg_algo::version()};
151 //! @brief Parse argument helper to apply design pattern.
152 utility::argument::Argument subCLIAppDp{action::name<reg_dp::ApplyDesignPattern>(), reg_dp::version()};
153 //! @brief Parse argument helper to apply data structure.
154 utility::argument::Argument subCLIAppDs{action::name<reg_ds::ApplyDataStructure>(), reg_ds::version()};
155 //! @brief Parse argument helper to apply numeric.
156 utility::argument::Argument subCLIAppNum{action::name<reg_num::ApplyNumeric>(), reg_num::version()};
157 //! @brief The short prefix for the option.
158 const std::string shortPrefix{"-"};
159 //! @brief The Long prefix for the option.
160 const std::string longPrefix{"--"};
161 //! @brief The meta variable for the option.
162 static constexpr std::string_view metaVar{"OPT"};
163 //! @brief Flag to indicate whether the command is faulty.
164 std::atomic_bool isFaulty{false};
165
166 friend bool executeCLI(const int argc, const char* const argv[]);
167 //! @brief Interface used to execute.
168 //! @param argc - argument count
169 //! @param argv - argument vector
170 //! @return successful or failed to execute
171 bool execute(const int argc, const char* const argv[]);
172 //! @brief Initialize the parse argument helpers for native.
173 void initializeNativeCLI();
174 //! @brief Initialize the parse argument helpers for extra.
175 void initializeExtraCLI();
176 //! @brief Setup the main command line interface.
177 void setupMainCLI();
178 //! @brief Setup the sub-command line interface.
179 //! @tparam SubCLI - type of type of sub-cli
180 template <typename SubCLI>
181 void setupSubCLI();
182 //! @brief Front-end handler for parsing command line arguments.
183 //! @param argc - argument count
184 //! @param argv - argument vector
185 void frontEndHandler(const int argc, const char* const argv[]);
186 //! @brief Back-end handler for performing the specific tasks.
187 void backEndHandler();
188 //! @brief Precheck the native type or extra type task.
189 void precheck();
190 //! @brief Check whether any type tasks exist.
191 //! @return any type tasks exist or do not exist
192 bool anySelected() const;
193 //! @brief Clear all type tasks.
194 void clearSelected();
195 //! @brief Dispatch all specific tasks.
196 void dispatchAll();
197 //! @brief Check for excessive arguments.
198 void checkExcessArgs();
199 //! @brief Execute the command line of console mode.
200 void executeInConsole() const;
201 //! @brief Show help message.
202 void showHelpMessage() const;
203 //! @brief Dump configuration.
204 static void dumpConfiguration();
205 //! @brief Display version information.
206 void displayVersionInfo() const;
207 //! @brief Validate the version of all dependencies.
208 void validateDependencies() const;
209
210 //! @brief Map the alias name.
211 //! @param cat - native category
212 //! @return alias name
213 static consteval std::string_view mappedAlias(const Category cat);
214 //! @brief Map the description.
215 //! @param cat - native category
216 //! @return description
217 static consteval std::string_view mappedDescr(const Category cat);
218 //! @brief Extract all choices in the sub-cli's category.
219 //! @tparam Cat - type of sub-cli's category
220 //! @return all choices
221 template <typename Cat>
222 static std::vector<std::string> extractChoices();
223
224 //! @brief Manage tasks.
225 class TaskManager
226 {
227 public:
228 //! @brief Construct a new TaskManager object.
229 TaskManager() = default;
230 //! @brief Destroy the TaskManager object.
231 virtual ~TaskManager() = default;
232 //! @brief Construct a new TaskManager object.
233 TaskManager(const TaskManager&) = default;
234 //! @brief Construct a new TaskManager object.
235 TaskManager(TaskManager&&) noexcept = default;
236 //! @brief The operator (=) overloading of TaskManager class.
237 //! @return reference of the TaskManager object
238 TaskManager& operator=(const TaskManager&) = default;
239 //! @brief The operator (=) overloading of TaskManager class.
240 //! @return reference of the TaskManager object
241 TaskManager& operator=(TaskManager&&) noexcept = default;
242
243 //! @brief Check whether any tasks do not exist.
244 //! @return any tasks do not exist or exist
245 [[nodiscard]] virtual bool empty() const = 0;
246 //! @brief Reset bit flags that manage all tasks.
247 virtual void reset() = 0;
248 };
249 //! @brief Manage native categories.
250 class NativeManager : virtual public TaskManager
251 {
252 public:
253 //! @brief Bit flags for managing native categories.
254 std::bitset<Bottom<Category>::value> nativeCategories;
255
256 //! @brief Check whether any native categories do not exist.
257 //! @return any native categories do not exist or exist
258 [[nodiscard]] bool empty() const override { return nativeCategories.none(); }
259 //! @brief Reset bit flags that manage native categories.
260 void reset() override { nativeCategories.reset(); }
261 };
262 //! @brief Manage extra choices of sub-cli.
263 class ExtraManager : virtual public TaskManager
264 {
265 public:
266 //! @brief Alias for the attribute of the registered sub-cli's category.
267 struct Attr
268 {
269 //! @brief The candidates for the choice.
270 const std::vector<std::string> choices;
271 //! @brief The internal event for applying.
272 const action::EventType event;
273 };
274 //! @brief Alias for the map of sub-cli's category name and Attr.
275 using CategoryMap = std::map<std::string, Attr>;
276 //! @brief Mapping table of all extra choices. Fill as needed.
277 std::map<std::string, CategoryMap> extraChoiceRegistry;
278
279 //! @brief Wrap interfaces to check for existing and reset extra choices.
280 struct Intf
281 {
282 Intf(std::function<bool()> presentCb, std::function<void()> clearCb) :
283 present{std::move(presentCb)}, clear{std::move(clearCb)}
284 {
285 if (!present || !clear)
286 {
287 throw std::runtime_error{"Invalid sub-command interfaces are being used."};
288 }
289 }
290
291 //! @brief Check the existence status of the extra choice.
292 const std::function<bool()> present;
293 //! @brief Reset control of the extra choice.
294 const std::function<void()> clear;
295 };
296 //! @brief Existence status and reset control of the sub-cli to which the extra choices belong.
297 std::map<std::string, Intf> extraChecklist;
298 //! @brief Flag for help only.
299 bool extraHelping{false};
300
301 //! @brief Check whether any extra choices do not exist.
302 //! @return any extra choices do not exist or exist
303 [[nodiscard]] bool empty() const override
304 {
305 return !extraHelping
306 && std::none_of(
307 first: extraChecklist.cbegin(),
308 last: extraChecklist.cend(),
309 pred: [](const auto& pair) { return pair.second.present(); });
310 }
311 //! @brief Reset bit flags that manage extra choices.
312 void reset() override
313 {
314 extraHelping = false;
315 std::for_each(
316 first: extraChecklist.cbegin(), last: extraChecklist.cend(), f: [](const auto& pair) { pair.second.clear(); });
317 }
318 };
319 //! @brief Schedule all managed tasks.
320 class TaskDispatcher : public NativeManager, public ExtraManager
321 {
322 public:
323 //! @brief Check whether any tasks do not exist.
324 //! @return any tasks do not exist or exist
325 [[nodiscard]] bool empty() const final { return NativeManager::empty() && ExtraManager::empty(); }
326 //! @brief Reset bit flags that manage all tasks.
327 void reset() final
328 {
329 NativeManager::reset();
330 ExtraManager::reset();
331 }
332 } /** @brief Dispatch all types of tasks. */ taskDispatcher{};
333
334 friend class Notifier<Category, Command>;
335 //! @brief Alias for the local notifier.
336 using LocalNotifier = Notifier<Category, Command>;
337 //! @brief Local notification for native type tasks.
338 LocalNotifier builtInNotifier{};
339 //! @brief Forward messages for extra type tasks.
340 action::MessageForwarder applyingForwarder{};
341 //! @brief Alias for the pair of the sub-cli name and the sub-cli version.
342 using VerLinkKey = std::pair<std::string, std::string>;
343 //! @brief Mapping hash value for the related versions.
344 struct VerLinkHash
345 {
346 //! @brief The operator (()) overloading of VerLinkHash struct.
347 //! @param key - pair of the sub-cli name and the sub-cli version
348 //! @return hash value
349 std::size_t operator()(const VerLinkKey& key) const
350 {
351 constexpr std::size_t magicNumber = 0x9E3779B9;
352 constexpr std::size_t leftShift = 6;
353 constexpr std::size_t rightShift = 2;
354 const std::size_t hash1 = std::hash<std::string>()(key.first);
355 const std::size_t hash2 = std::hash<std::string>()(key.second);
356 std::size_t seed = 0;
357 seed ^= hash1 + magicNumber + (seed << leftShift) + (seed >> rightShift);
358 seed ^= hash2 + magicNumber + (seed << leftShift) + (seed >> rightShift);
359 return seed;
360 }
361 };
362 //! @brief Mapping table of related versions. Fill as needed.
363 std::unordered_multimap<VerLinkKey, std::string, VerLinkHash> versionLinks;
364
365 //! @brief Go to console mode for troubleshooting.
366 static void enterConsoleMode();
367 //! @brief Register the command line to console mode.
368 //! @tparam Sock - type of client
369 //! @param session - console to be registered
370 //! @param client - client used to send
371 template <typename Sock>
372 static void registerOnConsole(console::Console& session, std::shared_ptr<Sock>& client);
373 //! @brief Launch the client for console mode.
374 //! @tparam Sock - type of client
375 //! @param client - client to be launched
376 template <typename Sock>
377 static void launchClient(std::shared_ptr<Sock>& client);
378 //! @brief Process the inputs in console mode.
379 //! @param handling - handling for inputs
380 //! @return console return code
381 static auto processConsoleInputs(const std::function<void()>& handling);
382 //! @brief Parse the message inside the client in console mode.
383 //! @param bytes - message buffer
384 //! @param size - message length
385 //! @return need to continue parsing or not
386 static bool onParsing4Client(char* const bytes, const std::size_t size);
387 //! @brief Await outside the client in console mode.
388 static void waitClientOutputDone();
389 //! @brief Awaken inside the client in console mode.
390 static void notifyClientOutputDone();
391 //! @brief Build the exit request message in console mode.
392 //! @return exit request message
393 static std::string buildDisconnectReq();
394 //! @brief Console latency in the millisecond range.
395 static void interactionLatency();
396};
397
398extern bool executeCLI(const int argc, const char* const argv[]);
399} // namespace command
400} // namespace application
401