1 /* $Id: clparser.cpp 591546 2019-08-16 16:59:06Z vasilche $
2 * ===========================================================================
3 *
4 * PUBLIC DOMAIN NOTICE
5 * National Center for Biotechnology Information
6 *
7 * This software/database is a "United States Government Work" under the
8 * terms of the United States Copyright Act. It was written as part of
9 * the author's official duties as a United States Government employee and
10 * thus cannot be copyrighted. This software/database is freely available
11 * to the public for use. The National Library of Medicine and the U.S.
12 * Government have not placed any restriction on its use or reproduction.
13 *
14 * Although all reasonable efforts have been taken to ensure the accuracy
15 * and reliability of the software and data, the NLM and the U.S.
16 * Government do not and cannot warrant the performance or results that
17 * may be obtained by using this software or data. The NLM and the U.S.
18 * Government disclaim all warranties, express or implied, including
19 * warranties of performance, merchantability or fitness for any particular
20 * purpose.
21 *
22 * Please cite the author in any work or product based on this material.
23 *
24 * ===========================================================================
25 *
26 * Authors: Dmitry Kazimirov
27 *
28 */
29
30 #include <ncbi_pch.hpp>
31
32 #include <corelib/ncbiapp_api.hpp>
33
34 #include <connect/services/clparser.hpp>
35
36 #include <algorithm>
37
38 BEGIN_NCBI_SCOPE
39
40 #define VERSION_OPT_ID -1
41 #define HELP_OPT_ID -2
42 #define COMMAND_OPT_ID -3
43
44 #define HELP_CMD_ID -1
45
46 #define UNSPECIFIED_CATEGORY_ID -1
47
48 #define DEFAULT_HELP_TEXT_WIDTH 72
49 #define DEFAULT_CMD_DESCR_INDENT 24
50 #define DEFAULT_OPT_DESCR_INDENT 32
51
52 typedef list<string> TNameVariantList;
53
54 struct SOptionOrCommandInfo : public CObject
55 {
SOptionOrCommandInfoSOptionOrCommandInfo56 SOptionOrCommandInfo(int id, const string& name_variants) : m_Id(id)
57 {
58 NStr::Split(name_variants, "|", m_NameVariants,
59 NStr::fSplit_MergeDelimiters | NStr::fSplit_Truncate);
60 }
61
GetPrimaryNameSOptionOrCommandInfo62 const string& GetPrimaryName() const {return m_NameVariants.front();}
63
64 int m_Id;
65 TNameVariantList m_NameVariants;
66 };
67
68 struct SOptionInfo : public SOptionOrCommandInfo
69 {
SOptionInfoSOptionInfo70 SOptionInfo(int opt_id, const string& name_variants,
71 CCommandLineParser::EOptionType type,
72 const string& description) :
73 SOptionOrCommandInfo(opt_id, name_variants),
74 m_Type(type),
75 m_Description(description)
76 {
77 }
78
AddDashesSOptionInfo79 static string AddDashes(const string& opt_name)
80 {
81 return opt_name.length() == 1 ? '-' + opt_name : "--" + opt_name;
82 }
83
GetNameVariantsSOptionInfo84 string GetNameVariants() const
85 {
86 string result(AddDashes(m_NameVariants.front()));
87 if (m_NameVariants.size() > 1) {
88 result.append(", ");
89 TNameVariantList::const_iterator name(m_NameVariants.begin());
90 result.append(AddDashes(*++name));
91 while (++name != m_NameVariants.end()) {
92 result.append(", ");
93 result.append(AddDashes(*name));
94 }
95 }
96 if (m_Type == CCommandLineParser::eOptionWithParameter)
97 result.append("=ARG");
98 return result;
99 }
100
101 int m_Type;
102 string m_Description;
103 };
104
105 typedef list<const SOptionInfo*> TOptionInfoList;
106
107 struct SCommonParts
108 {
SCommonPartsSCommonParts109 SCommonParts(const string& synopsis, const string& usage) :
110 m_Synopsis(synopsis),
111 m_Usage(usage)
112 {
113 }
114
115 string m_Synopsis;
116 string m_Usage;
117
118 TOptionInfoList m_PositionalArguments;
119 TOptionInfoList m_AcceptedOptions;
120 };
121
122 struct SCommandInfo : public SOptionOrCommandInfo, public SCommonParts
123 {
SCommandInfoSCommandInfo124 SCommandInfo(int cmd_id, const string& name_variants,
125 const string& synopsis, const string& usage) :
126 SOptionOrCommandInfo(cmd_id, name_variants),
127 SCommonParts(synopsis, usage)
128 {
129 }
130
GetNameVariantsSCommandInfo131 string GetNameVariants() const
132 {
133 if (m_NameVariants.size() == 1)
134 return m_NameVariants.front();
135 TNameVariantList::const_iterator name(m_NameVariants.begin());
136 string result(*name);
137 result.append(" (");
138 result.append(*++name);
139 while (++name != m_NameVariants.end()) {
140 result.append(", ");
141 result.append(*name);
142 }
143 result.push_back(')');
144 return result;
145 }
146 };
147
148 typedef list<const SCommandInfo*> TCommandInfoList;
149
150 struct SCategoryInfo : public CObject
151 {
SCategoryInfoSCategoryInfo152 SCategoryInfo(const string& title) : m_Title(title) {}
153
154 string m_Title;
155 TCommandInfoList m_Commands;
156 };
157
158 typedef list<const char*> TPositionalArgumentList;
159
160 struct SCommandLineParserImpl : public CObject, public SCommonParts
161 {
162 SCommandLineParserImpl(
163 const string& program_name,
164 const string& program_summary,
165 const string& program_description,
166 const string& version_info);
167
168 void PrintWordWrapped(int topic_len, int indent,
169 const string& text, int cont_indent = -1) const;
170 void Help(const TPositionalArgumentList& commands,
171 bool using_help_command) const;
172 void HelpOnCommand(const SCommonParts* common_parts,
173 const string& name_for_synopsis, const string& name_for_usage) const;
174 void Throw(const string& error, const string& cmd = kEmptyStr) const;
175 int ParseAndValidate(int argc, const char* const *argv);
176
177 // Command and argument definitions.
178 string m_ProgramName;
179 string m_VersionInfo;
180 typedef map<string, const SOptionInfo*> TOptionNameToOptionInfoMap;
181 typedef map<string, const SCommandInfo*> TCommandNameToCommandInfoMap;
182 const SOptionInfo* m_SingleLetterOptions[256];
183 TOptionNameToOptionInfoMap m_OptionToOptInfoMap;
184 map<int, CRef<SOptionInfo> > m_OptIdToOptionInfoMap;
185 TCommandNameToCommandInfoMap m_CommandNameToCommandInfoMap;
186 map<int, CRef<SCommandInfo> > m_CmdIdToCommandInfoMap;
187 typedef map<int, CRef<SCategoryInfo> > TCatIdToCategoryInfoMap;
188 TCatIdToCategoryInfoMap m_CatIdToCatInfoMap;
189 SOptionInfo m_VersionOption;
190 SOptionInfo m_HelpOption;
191
CommandsAreDefinedSCommandLineParserImpl192 bool CommandsAreDefined() const
193 {
194 return !m_CommandNameToCommandInfoMap.empty();
195 }
196
197 // Parsing results.
198 typedef pair<const SOptionInfo*, const char*> TOptionValue;
199 typedef list<TOptionValue> TOptionValues;
200 TOptionValues m_OptionValues;
201 TOptionValues::const_iterator m_NextOptionValue;
202
203 // Help text formatting.
204 int m_MaxHelpTextWidth;
205 int m_CmdDescrIndent;
206 int m_OptDescrIndent;
207 };
208
209 static const char s_Help[] = "help";
210 static const char s_Version[] = "version";
211
SCommandLineParserImpl(const string & program_name,const string & program_summary,const string & program_description,const string & version_info)212 SCommandLineParserImpl::SCommandLineParserImpl(
213 const string& program_name,
214 const string& program_summary,
215 const string& program_description,
216 const string& version_info) :
217 SCommonParts(program_summary, program_description),
218 m_ProgramName(program_name),
219 m_VersionInfo(version_info),
220 m_VersionOption(VERSION_OPT_ID, s_Version,
221 CCommandLineParser::eSwitch, kEmptyStr),
222 m_HelpOption(HELP_OPT_ID, s_Help,
223 CCommandLineParser::eSwitch, kEmptyStr),
224 m_MaxHelpTextWidth(DEFAULT_HELP_TEXT_WIDTH),
225 m_CmdDescrIndent(DEFAULT_CMD_DESCR_INDENT),
226 m_OptDescrIndent(DEFAULT_OPT_DESCR_INDENT)
227 {
228 memset(m_SingleLetterOptions, 0, sizeof(m_SingleLetterOptions));
229 m_OptionToOptInfoMap[s_Version] = &m_VersionOption;
230 m_OptionToOptInfoMap[s_Help] = &m_HelpOption;
231 m_CatIdToCatInfoMap[UNSPECIFIED_CATEGORY_ID] =
232 new SCategoryInfo("Available commands");
233 }
234
PrintWordWrapped(int topic_len,int indent,const string & text,int cont_indent) const235 void SCommandLineParserImpl::PrintWordWrapped(int topic_len,
236 int indent, const string& text, int cont_indent) const
237 {
238 if (text.empty()) {
239 printf("\n");
240 return;
241 }
242
243 const char* line = text.data();
244 const char* text_end = line + text.length();
245
246 int offset = indent;
247 if (topic_len > 0 && (offset -= topic_len) < 1) {
248 offset = indent;
249 printf("\n");
250 }
251
252 if (cont_indent < 0)
253 cont_indent = indent;
254
255 #ifdef __GNUC__
256 const char* next_line = NULL; // A no-op assignment to make GCC happy.
257 #else
258 const char* next_line;
259 #endif
260 do {
261 const char* line_end;
262 // Check for verbatim formatting.
263 if (*line != ' ') {
264 const char* pos = line;
265 const char* max_pos = line + m_MaxHelpTextWidth - indent;
266 line_end = NULL;
267 for (;;) {
268 if (*pos == ' ') {
269 line_end = pos;
270 while (pos < text_end && pos[1] == ' ')
271 ++pos;
272 next_line = pos + 1;
273 } else if (*pos == '\n') {
274 next_line = (line_end = pos) + 1;
275 break;
276 }
277 if (++pos > max_pos && line_end != NULL)
278 break;
279 if (pos == text_end) {
280 next_line = line_end = pos;
281 break;
282 }
283 }
284 } else {
285 // Preformatted text -- do not wrap.
286 line_end = strchr(line, '\n');
287 next_line = (line_end == NULL ? line_end = text_end : line_end + 1);
288 }
289 int line_len = int(line_end - line);
290 if (line_len > 0)
291 printf("%*.*s\n", offset + line_len, line_len, line);
292 else
293 printf("\n");
294 offset = indent = cont_indent;
295 } while ((line = next_line) < text_end);
296 }
297
Help(const TPositionalArgumentList & commands,bool using_help_command) const298 void SCommandLineParserImpl::Help(const TPositionalArgumentList& commands,
299 bool using_help_command) const
300 {
301 ITERATE(TOptionValues, option_value, m_OptionValues)
302 if (option_value->first->m_Id != HELP_OPT_ID) {
303 string opt_name(option_value->first->GetNameVariants());
304 if (using_help_command)
305 Throw("command 'help' doesn't accept option '" +
306 opt_name + "'", s_Help);
307 else
308 Throw("'--help' cannot be combined with option '" +
309 opt_name + "'", "--help");
310 }
311
312 if (!CommandsAreDefined())
313 HelpOnCommand(this, m_ProgramName, m_ProgramName);
314 else if (commands.empty()) {
315 printf("Usage: %s <command> [options] [args]\n", m_ProgramName.c_str());
316 PrintWordWrapped(0, 0, m_Synopsis);
317 printf("Type '%s help <command>' for help on a specific command.\n"
318 "Type '%s --version' to see the program version.\n",
319 m_ProgramName.c_str(), m_ProgramName.c_str());
320 if (!m_Usage.empty()) {
321 printf("\n");
322 PrintWordWrapped(0, 0, m_Usage);
323 }
324 ITERATE(TCatIdToCategoryInfoMap, category, m_CatIdToCatInfoMap) {
325 if (!category->second->m_Commands.empty()) {
326 printf("\n%s:\n\n", category->second->m_Title.c_str());
327 ITERATE(TCommandInfoList, cmd, category->second->m_Commands)
328 PrintWordWrapped(printf(" %s",
329 (*cmd)->GetNameVariants().c_str()),
330 m_CmdDescrIndent - 2, "- " + (*cmd)->m_Synopsis,
331 m_CmdDescrIndent);
332 }
333 }
334 printf("\n");
335 } else {
336 SCommandInfo help_command(HELP_CMD_ID, s_Help,
337 "Describe the usage of this program or its commands.", kEmptyStr);
338 SOptionInfo command_arg(COMMAND_OPT_ID, "COMMAND",
339 CCommandLineParser::eZeroOrMorePositional, kEmptyStr);
340 help_command.m_PositionalArguments.push_back(&command_arg);
341
342 ITERATE(TPositionalArgumentList, cmd_name, commands) {
343 const SCommandInfo* command_info;
344 TCommandNameToCommandInfoMap::const_iterator cmd =
345 m_CommandNameToCommandInfoMap.find(*cmd_name);
346 if (cmd != m_CommandNameToCommandInfoMap.end())
347 command_info = cmd->second;
348 else if (*cmd_name == s_Help)
349 command_info = &help_command;
350 else {
351 printf("'%s': unknown command.\n\n", *cmd_name);
352 continue;
353 }
354
355 HelpOnCommand(command_info, command_info->GetNameVariants(),
356 m_ProgramName + ' ' + command_info->GetPrimaryName());
357 }
358 }
359 }
360
HelpOnCommand(const SCommonParts * common_parts,const string & name_for_synopsis,const string & name_for_usage) const361 void SCommandLineParserImpl::HelpOnCommand(const SCommonParts* common_parts,
362 const string& name_for_synopsis, const string& name_for_usage) const
363 {
364 int text_len = printf("%s:", name_for_synopsis.c_str());
365 PrintWordWrapped(text_len, text_len + 1, common_parts->m_Synopsis);
366 printf("\n");
367
368 string args;
369 ITERATE(TOptionInfoList, arg, common_parts->m_PositionalArguments) {
370 if (!args.empty())
371 args.push_back(' ');
372 switch ((*arg)->m_Type) {
373 case CCommandLineParser::ePositionalArgument:
374 args.append((*arg)->GetPrimaryName());
375 break;
376 case CCommandLineParser::eOptionalPositional:
377 args.push_back('[');
378 args.append((*arg)->GetPrimaryName());
379 args.push_back(']');
380 break;
381 case CCommandLineParser::eZeroOrMorePositional:
382 args.push_back('[');
383 args.append((*arg)->GetPrimaryName());
384 args.append("...]");
385 break;
386 default: // always CCommandLineParser::eOneOrMorePositional
387 args.append((*arg)->GetPrimaryName());
388 args.append("...");
389 }
390 }
391 text_len = printf("Usage: %s", name_for_usage.c_str());
392 PrintWordWrapped(text_len, text_len + 1, args);
393
394 if (!common_parts->m_Usage.empty()) {
395 printf("\n");
396 PrintWordWrapped(0, 0, common_parts->m_Usage);
397 }
398
399 if (!common_parts->m_AcceptedOptions.empty()) {
400 printf("\nValid options:\n");
401 ITERATE(TOptionInfoList, opt, common_parts->m_AcceptedOptions)
402 PrintWordWrapped(printf(" %-*s :", m_OptDescrIndent - 5,
403 (*opt)->GetNameVariants().c_str()),
404 m_OptDescrIndent, (*opt)->m_Description);
405 }
406 printf("\n");
407 }
408
Throw(const string & error,const string & cmd) const409 void SCommandLineParserImpl::Throw(const string& error, const string& cmd) const
410 {
411 string message;
412 if (error.empty())
413 message.append(m_Synopsis);
414 else {
415 message.append(m_ProgramName);
416 message.append(": ");
417 message.append(error);
418 }
419 message.append("\nType '");
420 message.append(m_ProgramName);
421
422 if (!CommandsAreDefined())
423 message.append(" --help' for usage.\n");
424 else if (cmd.empty())
425 message.append(" help' for usage.\n");
426 else {
427 message.append(" help ");
428 message.append(cmd);
429 message.append("' for usage.\n");
430 }
431 throw runtime_error(message);
432 }
433
ParseAndValidate(int argc,const char * const * argv)434 int SCommandLineParserImpl::ParseAndValidate(int argc, const char* const *argv)
435 {
436 if (m_ProgramName.empty()) {
437 m_ProgramName = *argv;
438 string::size_type basename_pos = m_ProgramName.find_last_of("/\\:");
439 if (basename_pos != string::npos)
440 m_ProgramName = m_ProgramName.substr(basename_pos + 1);
441 }
442
443 TPositionalArgumentList positional_arguments;
444
445 // Part one: parsing.
446 while (--argc > 0) {
447 const char* arg = *++argv;
448
449 // Check if the current argument is a positional argument.
450 if (*arg != '-' || arg[1] == '\0')
451 positional_arguments.push_back(arg);
452 else {
453 // No, it's an option. Check whether it's a
454 // single-letter option or a long option.
455 const SOptionInfo* option_info;
456 const char* opt_param;
457 if (*++arg == '-') {
458 // It's a long option.
459 // If it's a free standing double dash marker,
460 // treat the rest of arguments as positional.
461 if (*++arg == '\0') {
462 while (--argc > 0)
463 positional_arguments.push_back(*++argv);
464 break;
465 }
466 // Check if a parameter is specified for this option.
467 opt_param = strchr(arg, '=');
468 string opt_name;
469 if (opt_param != NULL)
470 opt_name.assign(arg, opt_param++);
471 else
472 opt_name.assign(arg);
473 TOptionNameToOptionInfoMap::const_iterator opt_info(
474 m_OptionToOptInfoMap.find(opt_name));
475 if (opt_info == m_OptionToOptInfoMap.end())
476 Throw("unknown option '--" + opt_name + "'");
477 option_info = opt_info->second;
478 // Check if this option must have a parameter.
479 if (option_info->m_Type == CCommandLineParser::eSwitch) {
480 // No, it's a switch; it's not supposed to have a parameter.
481 if (opt_param != NULL)
482 Throw("option '--" + opt_name +
483 "' does not expect a parameter");
484 opt_param = "yes";
485 } else
486 // The option expects a parameter.
487 if (opt_param == NULL) {
488 // Parameter is not specified; use the next
489 // command line argument as a parameter for
490 // this option.
491 if (--argc == 0)
492 Throw("option '--" + opt_name +
493 "' requires a parameter");
494 opt_param = *++argv;
495 }
496 } else {
497 // The current argument is a (series of) one-letter option(s).
498 for (;;) {
499 char opt_letter = *arg++;
500 option_info = m_SingleLetterOptions[
501 (unsigned char) opt_letter];
502 if (option_info == NULL)
503 Throw(string("unknown option '-") + opt_letter + "'");
504
505 // Check if this option must have a parameter.
506 if (option_info->m_Type == CCommandLineParser::eSwitch) {
507 // It's a switch; it's not supposed to have a parameter.
508 opt_param = "yes";
509 if (*arg == '\0')
510 break;
511 } else {
512 // It's an option that expects a parameter.
513 if (*arg == '\0') {
514 // Use the next command line argument
515 // as a parameter for this option.
516 if (--argc == 0)
517 Throw(string("option '-") + opt_letter +
518 "' requires a parameter");
519 opt_param = *++argv;
520 } else
521 opt_param = arg;
522 break;
523 }
524
525 m_OptionValues.push_back(TOptionValue(
526 option_info, opt_param));
527 }
528 }
529
530 m_OptionValues.push_back(TOptionValue(option_info, opt_param));
531 }
532 }
533
534 // Part two: validation.
535 TOptionValues::iterator option_value(m_OptionValues.begin());
536 while (option_value != m_OptionValues.end())
537 switch (option_value->first->m_Id) {
538 case VERSION_OPT_ID:
539 puts(m_VersionInfo.c_str());
540 return HELP_CMD_ID;
541
542 case HELP_OPT_ID:
543 m_OptionValues.erase(option_value++);
544 Help(positional_arguments, false);
545 return HELP_CMD_ID;
546
547 default:
548 ++option_value;
549 }
550
551 string command_name;
552 const TOptionInfoList* expected_positional_arguments;
553 int ret_val;
554
555 if (!CommandsAreDefined()) {
556 expected_positional_arguments = &m_PositionalArguments;
557 ret_val = 0;
558 } else {
559 if (positional_arguments.empty())
560 Throw(m_OptionValues.empty() ? "" : "a command is required");
561
562 command_name = positional_arguments.front();
563 positional_arguments.pop_front();
564
565 TCommandNameToCommandInfoMap::const_iterator command =
566 m_CommandNameToCommandInfoMap.find(command_name);
567
568 if (command == m_CommandNameToCommandInfoMap.end()) {
569 if (command_name == s_Help) {
570 Help(positional_arguments, true);
571 return HELP_CMD_ID;
572 }
573 Throw("unknown command '" + command_name + "'");
574 }
575
576 const SCommandInfo* command_info = command->second;
577
578 ITERATE(TOptionValues, it, m_OptionValues)
579 if (find(command_info->m_AcceptedOptions.begin(),
580 command_info->m_AcceptedOptions.end(), it->first) ==
581 command_info->m_AcceptedOptions.end())
582 Throw("command '" + command_name + "' doesn't accept option '" +
583 it->first->GetNameVariants() + "'",
584 command_name);
585
586 expected_positional_arguments = &command_info->m_PositionalArguments;
587 ret_val = command_info->m_Id;
588 }
589
590 TPositionalArgumentList::const_iterator arg_value =
591 positional_arguments.begin();
592 TOptionInfoList::const_iterator expected_arg =
593 expected_positional_arguments->begin();
594
595 for (;;) {
596 if (expected_arg != expected_positional_arguments->end())
597 if (arg_value == positional_arguments.end())
598 switch ((*expected_arg)->m_Type) {
599 case CCommandLineParser::ePositionalArgument:
600 case CCommandLineParser::eOneOrMorePositional:
601 Throw("missing argument '" +
602 (*expected_arg)->GetPrimaryName() + "'", command_name);
603 }
604 else
605 switch ((*expected_arg)->m_Type) {
606 case CCommandLineParser::ePositionalArgument:
607 case CCommandLineParser::eOptionalPositional:
608 m_OptionValues.push_back(TOptionValue(
609 *expected_arg, *arg_value));
610 ++arg_value;
611 ++expected_arg;
612 continue;
613 default:
614 do
615 m_OptionValues.push_back(TOptionValue(
616 *expected_arg, *arg_value));
617 while (++arg_value != positional_arguments.end());
618 }
619 else
620 if (arg_value != positional_arguments.end())
621 Throw("too many positional arguments", command_name);
622 break;
623 }
624
625 m_NextOptionValue = m_OptionValues.begin();
626
627 return ret_val;
628 }
629
CCommandLineParser(const string & program_name,const string & version_info,const string & program_summary,const string & program_description)630 CCommandLineParser::CCommandLineParser(
631 const string& program_name,
632 const string& version_info,
633 const string& program_summary,
634 const string& program_description) :
635 m_Impl(new SCommandLineParserImpl(
636 program_name, program_summary, program_description, version_info))
637 {
638 }
639
SetHelpTextMargins(int help_text_width,int cmd_descr_indent,int opt_descr_indent)640 void CCommandLineParser::SetHelpTextMargins(int help_text_width,
641 int cmd_descr_indent, int opt_descr_indent)
642 {
643 m_Impl->m_MaxHelpTextWidth = help_text_width;
644 m_Impl->m_CmdDescrIndent = cmd_descr_indent;
645 m_Impl->m_OptDescrIndent = opt_descr_indent;
646 }
647
AddOption(CCommandLineParser::EOptionType type,int opt_id,const string & name_variants,const string & description)648 void CCommandLineParser::AddOption(CCommandLineParser::EOptionType type,
649 int opt_id, const string& name_variants, const string& description)
650 {
651 _ASSERT(opt_id >= 0 && m_Impl->m_OptIdToOptionInfoMap.find(opt_id) ==
652 m_Impl->m_OptIdToOptionInfoMap.end() && "Option IDs must be unique");
653
654 SOptionInfo* option_info = m_Impl->m_OptIdToOptionInfoMap[opt_id] =
655 new SOptionInfo(opt_id, name_variants, type, description);
656
657 switch (type) {
658 default:
659 _ASSERT(option_info->m_NameVariants.size() == 1 &&
660 "Positional arguments do not allow name variants");
661
662 m_Impl->m_PositionalArguments.push_back(option_info);
663 break;
664
665 case eSwitch:
666 case eOptionWithParameter:
667 ITERATE(TNameVariantList, name, option_info->m_NameVariants)
668 if (name->length() == 1)
669 m_Impl->m_SingleLetterOptions[
670 (unsigned char) name->at(0)] = option_info;
671 else
672 m_Impl->m_OptionToOptInfoMap[*name] = option_info;
673
674 m_Impl->m_AcceptedOptions.push_back(option_info);
675 }
676 }
677
AddCommandCategory(int cat_id,const string & title)678 void CCommandLineParser::AddCommandCategory(int cat_id, const string& title)
679 {
680 _ASSERT(cat_id >= 0 && m_Impl->m_CatIdToCatInfoMap.find(cat_id) ==
681 m_Impl->m_CatIdToCatInfoMap.end() && "Category IDs must be unique");
682
683 m_Impl->m_CatIdToCatInfoMap[cat_id] = new SCategoryInfo(title);
684 }
685
AddCommand(int cmd_id,const string & name_variants,const string & synopsis,const string & usage,int cat_id)686 void CCommandLineParser::AddCommand(int cmd_id, const string& name_variants,
687 const string& synopsis, const string& usage, int cat_id)
688 {
689 _ASSERT(cmd_id >= 0 && m_Impl->m_CmdIdToCommandInfoMap.find(cmd_id) ==
690 m_Impl->m_CmdIdToCommandInfoMap.end() && "Command IDs must be unique");
691
692 _ASSERT(m_Impl->m_CatIdToCatInfoMap.find(cat_id) !=
693 m_Impl->m_CatIdToCatInfoMap.end() && "No such category ID");
694
695 SCommandInfo* command_info = m_Impl->m_CmdIdToCommandInfoMap[cmd_id] =
696 new SCommandInfo(cmd_id, name_variants, synopsis, usage);
697
698 m_Impl->m_CatIdToCatInfoMap[cat_id]->m_Commands.push_back(command_info);
699
700 ITERATE(TNameVariantList, name, command_info->m_NameVariants)
701 m_Impl->m_CommandNameToCommandInfoMap[*name] = command_info;
702 }
703
AddAssociation(int cmd_id,int opt_id)704 void CCommandLineParser::AddAssociation(int cmd_id, int opt_id)
705 {
706 _ASSERT(m_Impl->m_CmdIdToCommandInfoMap.find(cmd_id) !=
707 m_Impl->m_CmdIdToCommandInfoMap.end() && "No such command ID");
708
709 _ASSERT(m_Impl->m_OptIdToOptionInfoMap.find(opt_id) !=
710 m_Impl->m_OptIdToOptionInfoMap.end() && "No such option ID");
711
712 SCommandInfo* cmd_info = m_Impl->m_CmdIdToCommandInfoMap[cmd_id];
713 SOptionInfo* opt_info = m_Impl->m_OptIdToOptionInfoMap[opt_id];
714
715 switch (opt_info->m_Type) {
716 case eSwitch:
717 case eOptionWithParameter:
718 cmd_info->m_AcceptedOptions.push_back(opt_info);
719 break;
720
721 default:
722 _ASSERT("Invalid sequence of optional positional arguments" &&
723 (cmd_info->m_PositionalArguments.empty() ||
724 cmd_info->m_PositionalArguments.back()->m_Type ==
725 ePositionalArgument ||
726 (cmd_info->m_PositionalArguments.back()->m_Type ==
727 eOptionalPositional &&
728 opt_info->m_Type != ePositionalArgument)));
729
730 cmd_info->m_PositionalArguments.push_back(opt_info);
731 }
732 }
733
Parse(int argc,const char * const * argv)734 int CCommandLineParser::Parse(int argc, const char* const *argv)
735 {
736 return m_Impl->ParseAndValidate(argc, argv);
737 }
738
GetProgramName() const739 const string& CCommandLineParser::GetProgramName() const
740 {
741 return m_Impl->m_ProgramName;
742 }
743
NextOption(int * opt_id,const char ** opt_value)744 bool CCommandLineParser::NextOption(int* opt_id, const char** opt_value)
745 {
746 if (m_Impl->m_NextOptionValue == m_Impl->m_OptionValues.end())
747 return false;
748
749 *opt_id = m_Impl->m_NextOptionValue->first->m_Id;
750 *opt_value = m_Impl->m_NextOptionValue->second;
751
752 ++m_Impl->m_NextOptionValue;
753
754 return true;
755 }
756
757 END_NCBI_SCOPE
758