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