args-parser 6.3.6
Loading...
Searching...
No Matches
cmd_line.hpp
Go to the documentation of this file.
1
2/*
3 SPDX-FileCopyrightText: 2026 Igor Mironchik <igor.mironchik@gmail.com>
4 SPDX-License-Identifier: MIT
5*/
6
7#ifndef ARGS__CMD_LINE_HPP__INCLUDED
8#define ARGS__CMD_LINE_HPP__INCLUDED
9
10// Args include.
11#include "api.hpp"
12#include "command.hpp"
13#include "context.hpp"
14#include "exceptions.hpp"
15#include "help.hpp"
16#include "utils.hpp"
17
18// C++ include.
19#include <algorithm>
20#include <memory>
21#include <vector>
22
23namespace Args
24{
25
26namespace details
27{
28
29//
30// makeContext
31//
32
34static inline ContextInternal
35#ifdef ARGS_WSTRING_BUILD
36makeContext(int argc,
37 const Char *const *argv)
38#else
39makeContext(int argc,
40 const char *const *argv)
41#endif
42{
43 ContextInternal context;
44
45 // We skip first argv because of it's executable name.
46 for (int i = 1; i < argc; ++i) {
47 context.push_back(argv[i]);
48 }
49
50 return context;
51} // makeContext
52
53//
54// formatCorrectNamesString
55//
56
58static inline String formatCorrectNamesString(const StringList &names)
59{
60 if (!names.empty()) {
61 String res;
62
63 bool first = true;
64
65 for (const auto &name : details::asConst(names)) {
66 if (!first) {
67 res.append(SL(" or "));
68 }
69
70 res.append(name);
71
72 first = false;
73 }
74
75 return res;
76 } else {
77 return String();
78 }
79}
80
81} /* namespace details */
82
83//
84// CmdLine
85//
86
87template<typename PARENT, typename SELF, typename ARGPTR>
89
90using ArgPtrToAPI = std::unique_ptr<ArgIface, details::Deleter<ArgIface>>;
91
97class CmdLine final : public CmdLineAPI<CmdLine, CmdLine, ArgPtrToAPI>
98{
99public:
103 using Arguments = std::vector<ArgPtr>;
104
108 Empty = 0,
113 }; // enum CmdLineOpt
114
116 using CmdLineOpts = int;
117
119 explicit CmdLine(CmdLineOpts opt = Empty)
121 CmdLine,
122 ArgPtr,
123 true>(*this,
124 *this)
125 , m_command(nullptr)
126 , m_currCommand(nullptr)
127 , m_opt(opt)
128 {
129 }
130
131#ifdef ARGS_WSTRING_BUILD
133 CmdLine(int argc,
134 const Char *const *argv,
135 CmdLineOpts opt = Empty);
136#else
138 CmdLine(int argc,
139 const char *const *argv,
140 CmdLineOpts opt = Empty);
141#endif
142
143 virtual ~CmdLine()
144 {
145 }
146
149 {
150 return m_opt;
151 }
152
154 CmdLine &addArg(ArgIface *arg);
155
157 CmdLine &addArg(ArgIface &arg);
158
161 {
162 if (std::find(m_args.begin(), m_args.end(), arg) == m_args.end()) {
163 arg->setCmdLine(this);
164
165 m_args.push_back(std::move(arg));
166 } else {
167 throw BaseException(String(SL("Argument \"")) + arg->name() + SL("\" already in the command line parser."));
168 }
169 return *this;
170 }
171
173 void parse();
174
175#ifdef ARGS_WSTRING_BUILD
177 void parse(int argc,
178 const Char *const *argv)
179#else
181 void parse(int argc,
182 const char *const *argv)
183#endif
184 {
185 m_context = std::move(details::makeContext(argc, argv));
186
187 parse();
188 }
189
191 const StringList &positional() const
192 {
193 return m_positional;
194 }
195
198 {
199 return m_positionalDescription;
200 }
201
204 {
205 m_positionalDescription = d;
206 return *this;
207 }
208
212 {
213 auto it = std::find_if(m_args.begin(), m_args.end(), [&name](const auto &arg) -> bool {
214 return (arg->findArgument(name) != nullptr);
215 });
216
217 if (it != m_args.end()) {
218 if ((*it)->type() == ArgType::Command) {
219 return it->get();
220 } else {
221 return (*it)->findArgument(name);
222 }
223 } else if (!m_prevCommand.empty()) {
224 for (const auto &c : m_prevCommand) {
225 ArgIface *tmp = c->findChild(name);
226
227 if (tmp) {
228 return tmp;
229 }
230 }
231 }
232
233 return nullptr;
234 }
235
238 const ArgIface *findArgument(const String &name) const
239 {
240 auto it = std::find_if(m_args.cbegin(), m_args.cend(), [&name](const auto &arg) -> bool {
241 return (arg->findArgument(name) != nullptr);
242 });
243
244 if (it != m_args.end()) {
245 if ((*it)->type() == ArgType::Command) {
246 return it->get();
247 } else {
248 return (*it)->findArgument(name);
249 }
250 } else if (!m_prevCommand.empty()) {
251 for (const auto &c : m_prevCommand) {
252 ArgIface *tmp = c->findChild(name);
253
254 if (tmp) {
255 return tmp;
256 }
257 }
258 }
259
260 return nullptr;
261 }
262
264 const Arguments &arguments() const;
265
269 const String &name,
271 StringList &possibleNames) const
272 {
273 bool ret = false;
274
275 std::for_each(arguments().cbegin(), arguments().cend(), [&](const auto &arg) {
276 if (arg->type() == ArgType::Command) {
277 if (arg.get() == m_command) {
278 if (arg->isMisspelledName(name, possibleNames))
279 ret = true;
280 } else if (static_cast<Command *>(arg.get())->isMisspelledCommand(name, possibleNames)) {
281 ret = true;
282 }
283 } else if (arg->isMisspelledName(name, possibleNames)) {
284 ret = true;
285 }
286 });
287
288 return ret;
289 }
290
294 bool throwExceptionOnPrint = true,
296 const String &appExe = String(),
298 const String &appDesc = String(),
300 String::size_type length = 79,
302 const String &posDesc = String())
303 {
304 auto help = std::unique_ptr<Help, details::Deleter<ArgIface>>(new Help(throwExceptionOnPrint),
306
307 if (!appExe.empty()) {
308 help->setExecutable(appExe);
309 }
310
311 if (!appDesc.empty()) {
312 help->setAppDescription(appDesc);
313 }
314
315 if (!posDesc.empty()) {
317 }
318
319 help->setLineLength(length);
320
321 ArgPtr arg = std::move(help);
322
323 addArg(std::move(arg));
324
325 return *this;
326 }
327
328 template<typename T>
329 void addHelp(T throwExceptionOnPrint,
330 const String &appExe = String(),
331 const String &appDesc = String(),
332 String::size_type length = 79) = delete;
333
338 const String &name) const
339 {
340 const auto *arg = findArgument(name);
341
342 if (arg) {
343 switch (arg->type()) {
344 case ArgType::Command:
345 return (static_cast<const Command *>(arg)->value());
346
347 case ArgType::Arg:
348 return (static_cast<const Arg *>(arg)->value());
349
351 return (static_cast<const MultiArg *>(arg)->value());
352
353 default:
354 return String();
355 }
356 } else {
357 return String();
358 }
359 }
360
365 const String &name) const
366 {
367 const auto *arg = findArgument(name);
368
369 if (arg) {
370 switch (arg->type()) {
371 case ArgType::Command:
372 return (static_cast<const Command *>(arg)->values());
373
374 case ArgType::Arg:
375 return StringList({(static_cast<const Arg *>(arg)->value())});
376
378 return (static_cast<const MultiArg *>(arg)->values());
379
380 default:
381 return StringList();
382 }
383 } else {
384 return StringList();
385 }
386 }
387
394 const String &name) const
395 {
396 const auto *arg = findArgument(name);
397
398 if (arg) {
399 return arg->isDefined();
400 } else {
401 return false;
402 }
403 }
404
406 void clear()
407 {
408 std::for_each(arguments().begin(), arguments().end(), [](const auto &a) {
409 a->clear();
410 });
411
412 m_command = nullptr;
413 m_currCommand = nullptr;
414 m_prevCommand.clear();
415 }
416
417private:
419 void checkCorrectnessBeforeParsing() const;
421 void checkCorrectnessAfterParsing() const;
422
424 void printInfoAboutUnknownArgument(const String &word)
425 {
426 StringList correctNames;
427
428 if (isMisspelledName(word, correctNames)) {
429 const String names = details::formatCorrectNamesString(correctNames);
430
431 throw BaseException(
432 String(SL("Unknown argument \"")) + word + SL("\".\n\nProbably you mean \"") + names + SL("\"."));
433 } else {
434 throw BaseException(String(SL("Unknown argument \"")) + word + SL("\"."));
435 }
436 }
437
439 void savePositionalArguments(const String &word,
440 bool splitted,
441 bool valuePrepended)
442 {
443 auto tmp = word;
444
445 if (splitted) {
446 tmp.append(1, '=');
447
448 if (valuePrepended) {
449 tmp.append(*m_context.next());
450 }
451 }
452
453 if (tmp != String(2, '-')) {
454 m_positional.push_back(tmp);
455 }
456
457 while (!m_context.atEnd()) {
458 auto it = m_context.next();
459
460 m_positional.push_back(*it);
461 }
462 }
463
464private:
466
467 // Context.
468 Context m_context;
470 Arguments m_args;
472 Command *m_command;
474 Command *m_currCommand;
476 std::vector<Command *> m_prevCommand;
478 CmdLineOpts m_opt;
480 StringList m_positional;
482 String m_positionalDescription;
483}; // class CmdLine
484
485//
486// CmdLine
487//
488
489inline
490#ifdef ARGS_WSTRING_BUILD
491 CmdLine::CmdLine(int argc,
492 const Char *const *argv,
493 CmdLineOpts opt)
494#else
496 const char *const *argv,
497 CmdLineOpts opt)
498#endif
500 CmdLine,
501 ArgPtr,
502 true>(*this,
503 *this)
504 , m_context(details::makeContext(argc,
505 argv))
506 , m_command(nullptr)
507 , m_currCommand(nullptr)
508 , m_opt(opt)
509{
510}
511
513{
514 if (arg) {
515 if (std::find(m_args.begin(), m_args.end(), ArgPtr(arg, details::Deleter<ArgIface>(false))) == m_args.end()) {
516 arg->setCmdLine(this);
517
518 m_args.push_back(ArgPtr(arg, details::Deleter<ArgIface>(false)));
519 } else {
520 throw BaseException(String(SL("Argument \"")) + arg->name() + SL("\" already in the command line parser."));
521 }
522 } else {
523 throw BaseException(
524 String(SL("Attempt to add nullptr to the "
525 "command line as argument.")));
526 }
527 return *this;
528}
529
531{
532 return addArg(&arg);
533}
534
535inline void CmdLine::parse()
536{
537 clear();
538
539 checkCorrectnessBeforeParsing();
540
541 while (!m_context.atEnd()) {
542 String word = *m_context.next();
543
544 const String::size_type eqIt = word.find('=');
545
546 bool splitted = false;
547 bool valuePrepended = false;
548
549 if (eqIt != String::npos) {
550 splitted = true;
551
552 const String value = word.substr(eqIt + 1);
553
554 if (!value.empty()) {
555 valuePrepended = true;
556 m_context.prepend(value);
557 }
558
559 word = word.substr(0, eqIt);
560 }
561
562 if (details::isArgument(word)) {
563 auto *arg = findArgument(word);
564
565 if (arg) {
566 arg->process(m_context);
567 } else if (m_opt & HandlePositionalArguments) {
568 savePositionalArguments(word, splitted, valuePrepended);
569 } else {
570 printInfoAboutUnknownArgument(word);
571 }
572 } else if (details::isFlag(word)) {
573 std::vector<ArgIface *> tmpArgs;
574 bool failed = false;
575
576 for (String::size_type i = 1, length = word.length(); i < length; ++i) {
577 const String flag = String(SL("-")) +
578
579#ifdef ARGS_QSTRING_BUILD
580 String(word[i]);
581#else
582 String(1, word[i]);
583#endif
584
585 auto *arg = findArgument(flag);
586
587 if (!arg) {
588 failed = true;
589
590 if (!(m_opt & HandlePositionalArguments)) {
591 throw BaseException(String(SL("Unknown argument \"")) + flag + SL("\"."));
592 } else {
593 break;
594 }
595 } else {
596 tmpArgs.push_back(arg);
597 }
598
599 if (i < length - 1 && arg->isWithValue()) {
600 failed = true;
601
602 if (!(m_opt & HandlePositionalArguments)) {
603 throw BaseException(String(SL("Only last argument in "
604 "flags combo can be with value. Flags combo is \""))
605 + word
606 + SL("\"."));
607 } else {
608 break;
609 }
610 } else {
611 arg->process(m_context);
612 }
613 }
614
615 if (failed && (m_opt & HandlePositionalArguments)) {
616 for (auto *a : tmpArgs) {
617 a->clear();
618 }
619
620 savePositionalArguments(word, splitted, valuePrepended);
621 }
622 }
623 // Command?
624 else {
625 auto *tmp = findArgument(word);
626
627 auto check = [this, &tmp, &word]() {
628 const auto &args = m_args;
629
630 const auto it = std::find_if(args.cbegin(), args.cend(), [&tmp](const auto &arg) -> bool {
631 return (arg.get() == tmp);
632 });
633
634 if (m_command && it != args.cend() && !m_command->findChild(word)) {
635 throw BaseException(String(SL("Only one command from one level can be specified. "
636 "But you defined \""))
637 + word
638 + SL("\" and \"")
639 + m_command->name()
640 + SL("\"."));
641 }
642 };
643
644 if (tmp) {
645 if (tmp->type() == ArgType::Command) {
646 bool stopProcessing = false;
647
648 try {
649 if (!m_prevCommand.empty()) {
650 for (const auto &prev : m_prevCommand) {
651 const auto &args = prev->children();
652
653 const auto it =
654 std::find_if(args.cbegin(), args.cend(), [&tmp](const auto &arg) -> bool {
655 return (arg.get() == tmp);
656 });
657
658 if (it != args.cend() && !m_currCommand->findChild(word)) {
659 throw BaseException(String(SL("Only one command from one level can be specified. "
660 "But you defined \""))
661 + word
662 + SL("\" and \"")
663 + prev->name()
664 + SL("\"."));
665 }
666 }
667
668 check();
669 } else {
670 check();
671 }
672 } catch (const BaseException &) {
673 if (m_opt & HandlePositionalArguments) {
674 savePositionalArguments(word, splitted, valuePrepended);
675
676 stopProcessing = true;
677 } else {
678 throw;
679 }
680 }
681
682 if (stopProcessing) {
683 continue;
684 }
685
686 if (!m_command) {
687 m_command = static_cast<Command *>(tmp);
688
689 m_currCommand = m_command;
690
691 m_prevCommand.push_back(m_command);
692
693 m_command->process(m_context);
694 } else {
695 auto *cmd = static_cast<Command *>(tmp);
696
697 m_currCommand->setCurrentSubCommand(cmd);
698
699 m_currCommand = cmd;
700
701 m_prevCommand.push_back(m_currCommand);
702
703 m_currCommand->process(m_context);
704 }
705 } else if (m_opt & HandlePositionalArguments) {
706 savePositionalArguments(word, splitted, valuePrepended);
707 } else {
708 printInfoAboutUnknownArgument(word);
709 }
710 } else if (m_opt & HandlePositionalArguments) {
711 savePositionalArguments(word, splitted, valuePrepended);
712 } else {
713 printInfoAboutUnknownArgument(word);
714 }
715 }
716 }
717
718 checkCorrectnessAfterParsing();
719}
720
722{
723 return m_args;
724}
725
726inline void CmdLine::checkCorrectnessBeforeParsing() const
727{
728 StringList flags;
729 StringList names;
730
731 std::vector<ArgIface *> cmds;
732
733 std::for_each(m_args.cbegin(), m_args.cend(), [&cmds, &flags, &names](const auto &arg) {
734 if (arg->type() == ArgType::Command) {
735 cmds.push_back(arg.get());
736 } else {
737 arg->checkCorrectnessBeforeParsing(flags, names);
738 }
739 });
740
741 std::for_each(cmds.cbegin(), cmds.cend(), [&flags, &names](ArgIface *arg) {
742 arg->checkCorrectnessBeforeParsing(flags, names);
743 });
744}
745
746inline void CmdLine::checkCorrectnessAfterParsing() const
747{
748 std::for_each(m_args.begin(), m_args.end(), [](const auto &arg) {
749 arg->checkCorrectnessAfterParsing();
750 });
751
752 if (m_opt & CommandIsRequired && !m_command) {
753 throw BaseException(SL("Not specified command."));
754 }
755}
756
757} /* namespace Args */
758
759#include "help_printer.hpp"
760
761namespace Args
762{
763
764inline Help::Help(bool throwExceptionOnPrint)
765 : Arg(SL('h'),
766 SL("help"),
767 true,
768 false)
769 , m_printer(new HelpPrinter)
770 , m_throwExceptionOnPrint(throwExceptionOnPrint)
771{
772 setDescription(SL("Print this help."));
773 setLongDescription(SL("Print this help."));
774}
775
776} /* namespace Args */
777
778#endif // ARGS__CMD_LINE_HPP__INCLUDED
Argument with one value that can be present only once in the command line.
Definition arg.hpp:33
Arg & setLongDescription(const String &desc)
Set long description.
Definition arg.hpp:469
virtual const String & value() const
Definition arg.hpp:401
Arg(Char flag, T &&name, bool isWithValue=false, bool isRequired=false)
Construct argument with flag and name.
Definition arg.hpp:245
Arg & setDescription(const String &desc)
Set description.
Definition arg.hpp:454
Interface for arguments.
Definition arg_iface.hpp:29
virtual String name() const =0
virtual void setCmdLine(CmdLine *cmdLine)
Set command line parser.
Base exception of the library.
CmdLine is class that holds all rguments and parse command line arguments in the correspondence with ...
Definition cmd_line.hpp:98
void parse(int argc, const char *const *argv)
Parse arguments.
Definition cmd_line.hpp:181
const StringList & positional() const
Definition cmd_line.hpp:191
CmdLine & addHelp(bool throwExceptionOnPrint=true, const String &appExe=String(), const String &appDesc=String(), String::size_type length=79, const String &posDesc=String())
Add help.
Definition cmd_line.hpp:292
CmdLine & addArg(ArgIface *arg)
Add argument.
Definition cmd_line.hpp:512
bool isMisspelledName(const String &name, StringList &possibleNames) const
Definition cmd_line.hpp:267
CmdLine & setPositionalDescription(const String &d)
Set positional string description for the help.
Definition cmd_line.hpp:203
bool isDefined(const String &name) const
Definition cmd_line.hpp:391
String value(const String &name) const
Definition cmd_line.hpp:335
CmdLine(CmdLineOpts opt=Empty)
Construct empty CmdLine.
Definition cmd_line.hpp:119
void parse()
Parse arguments.
Definition cmd_line.hpp:535
void clear()
Clear state of the arguments.
Definition cmd_line.hpp:406
const ArgIface * findArgument(const String &name) const
Definition cmd_line.hpp:238
CmdLineOpt
Command line options.
Definition cmd_line.hpp:106
@ Empty
No special options.
Definition cmd_line.hpp:108
@ HandlePositionalArguments
Handle positional arguments.
Definition cmd_line.hpp:112
@ CommandIsRequired
Command should be defined.
Definition cmd_line.hpp:110
void addHelp(T throwExceptionOnPrint, const String &appExe=String(), const String &appDesc=String(), String::size_type length=79)=delete
StringList values(const String &name) const
Definition cmd_line.hpp:362
int CmdLineOpts
Storage of command line options.
Definition cmd_line.hpp:116
std::vector< ArgPtr > Arguments
List of child arguments.
Definition cmd_line.hpp:103
ArgPtrToAPI ArgPtr
Smart pointer to the argument.
Definition cmd_line.hpp:101
const String & positionalDescription() const
Definition cmd_line.hpp:197
ArgIface * findArgument(const String &name)
Definition cmd_line.hpp:211
const Arguments & arguments() const
Definition cmd_line.hpp:721
CmdLine & addArg(ArgPtr arg)
Add argument.
Definition cmd_line.hpp:160
CmdLineOpts parserOptions() const
Definition cmd_line.hpp:148
virtual ~CmdLine()
Definition cmd_line.hpp:143
Command in the command line interface.
Definition command.hpp:28
bool isMisspelledCommand(const String &nm, StringList &possibleNames) const
Definition command.hpp:214
void process(Context &ctx) override
Process argument's staff, for example take values from context.
Definition command.hpp:313
void setCurrentSubCommand(Command *sub)
Set current subcommand.
Definition command.hpp:385
Help argument.
Definition help.hpp:50
Help(bool throwExceptionOnPrint=true)
Definition cmd_line.hpp:764
HelpPrinter is a class that prints help.
MultiArg is a class that presents argument in the command line taht can be presented more than once o...
Definition multi_arg.hpp:34
Auxiliary API.
Definition api.hpp:30
constexpr std::add_const< T >::type & asConst(T &t) noexcept
Adds const to non-const objects.
Definition utils.hpp:45
Definition api.hpp:18
std::string String
String type.
Definition types.hpp:314
@ Arg
Argument.
Definition enums.hpp:36
@ MultiArg
Multi argument.
Definition enums.hpp:38
@ Command
Command.
Definition enums.hpp:34
StringList ContextInternal
What Context actually is.
Definition context.hpp:25
String::value_type Char
Char type.
Definition types.hpp:317
details::API< PARENT, SELF, ARGPTR, true > CmdLineAPI
Definition cmd_line.hpp:88
std::unique_ptr< ArgIface, details::Deleter< ArgIface > > ArgPtrToAPI
Definition cmd_line.hpp:90
std::vector< String > StringList
List of strings.
Definition types.hpp:336
#define SL(str)
Definition types.hpp:328
#define DISABLE_COPY(Class)
Macro for disabling copy.
Definition utils.hpp:25