1 // -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*-
2 
3 /******************************************************************************
4  *
5  *  file:  StdOutput.h
6  *
7  *  Copyright (c) 2004, Michael E. Smoot
8  *  Copyright (c) 2017, Google LLC
9  *  All rights reserved.
10  *
11  *  See the file COPYING in the top directory of this distribution for
12  *  more information.
13  *
14  *  THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS
15  *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17  *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20  *  DEALINGS IN THE SOFTWARE.
21  *
22  *****************************************************************************/
23 
24 #ifndef TCLAP_STD_OUTPUT_H
25 #define TCLAP_STD_OUTPUT_H
26 
27 #include <tclap/Arg.h>
28 #include <tclap/ArgGroup.h>
29 #include <tclap/CmdLineInterface.h>
30 #include <tclap/CmdLineOutput.h>
31 
32 #include <algorithm>
33 #include <cctype>
34 #include <iostream>
35 #include <list>
36 #include <string>
37 #include <utility>
38 #include <vector>
39 
40 namespace TCLAP {
41 
42 /**
43  * A class that isolates any output from the CmdLine object so that it
44  * may be easily modified.
45  */
46 class StdOutput : public CmdLineOutput {
47 public:
48     /**
49      * Prints the usage to stdout.  Can be overridden to
50      * produce alternative behavior.
51      * \param c - The CmdLine object the output is generated for.
52      */
53     virtual void usage(CmdLineInterface &c);
54 
55     /**
56      * Prints the version to stdout. Can be overridden
57      * to produce alternative behavior.
58      * \param c - The CmdLine object the output is generated for.
59      */
60     virtual void version(CmdLineInterface &c);
61 
62     /**
63      * Prints (to stderr) an error message, short usage
64      * Can be overridden to produce alternative behavior.
65      * \param c - The CmdLine object the output is generated for.
66      * \param e - The ArgException that caused the failure.
67      */
68     virtual void failure(CmdLineInterface &c, ArgException &e);
69 
70 protected:
71     /**
72      * Writes a brief usage message with short args.
73      * \param c - The CmdLine object the output is generated for.
74      * \param os - The stream to write the message to.
75      */
76     void _shortUsage(CmdLineInterface &c, std::ostream &os) const;
77 
78     /**
79      * Writes a longer usage message with long and short args,
80      * provides descriptions and prints message.
81      * \param c - The CmdLine object the output is generated for.
82      * \param os - The stream to write the message to.
83      */
84     void _longUsage(CmdLineInterface &c, std::ostream &os) const;
85 
86     /**
87      * This function inserts line breaks and indents long strings
88      * according the  params input. It will only break lines at spaces,
89      * commas and pipes.
90      * \param os - The stream to be printed to.
91      * \param s - The string to be printed.
92      * \param maxWidth - The maxWidth allowed for the output line.
93      * \param indentSpaces - The number of spaces to indent the first line.
94      * \param secondLineOffset - The number of spaces to indent the second
95      * and all subsequent lines in addition to indentSpaces.
96      */
97     void spacePrint(std::ostream &os, const std::string &s, int maxWidth,
98                     int indentSpaces, int secondLineOffset) const;
99 };
100 
version(CmdLineInterface & _cmd)101 inline void StdOutput::version(CmdLineInterface &_cmd) {
102     std::string progName = _cmd.getProgramName();
103     std::string xversion = _cmd.getVersion();
104 
105     std::cout << std::endl
106               << progName << "  version: " << xversion << std::endl
107               << std::endl;
108 }
109 
usage(CmdLineInterface & _cmd)110 inline void StdOutput::usage(CmdLineInterface &_cmd) {
111     std::cout << std::endl << "USAGE: " << std::endl << std::endl;
112 
113     _shortUsage(_cmd, std::cout);
114 
115     std::cout << std::endl << std::endl << "Where: " << std::endl << std::endl;
116 
117     _longUsage(_cmd, std::cout);
118 
119     std::cout << std::endl;
120 }
121 
failure(CmdLineInterface & _cmd,ArgException & e)122 inline void StdOutput::failure(CmdLineInterface &_cmd, ArgException &e) {
123     std::string progName = _cmd.getProgramName();
124 
125     std::cerr << "PARSE ERROR: " << e.argId() << std::endl
126               << "             " << e.error() << std::endl
127               << std::endl;
128 
129     if (_cmd.hasHelpAndVersion()) {
130         std::cerr << "Brief USAGE: " << std::endl;
131 
132         _shortUsage(_cmd, std::cerr);
133 
134         std::cerr << std::endl
135                   << "For complete USAGE and HELP type: " << std::endl
136                   << "   " << progName << " " << Arg::nameStartString()
137                   << "help" << std::endl
138                   << std::endl;
139     } else {
140         usage(_cmd);
141     }
142 
143     throw ExitException(1);
144 }
145 
146 // TODO: Remove this
removeChar(std::string & s,char r)147 inline void removeChar(std::string &s, char r) {
148     size_t p;
149     while ((p = s.find_first_of(r)) != std::string::npos) {
150         s.erase(p, 1);
151     }
152 }
153 
cmpSwitch(const char & a,const char & b)154 inline bool cmpSwitch(const char &a, const char &b) {
155     int lowa = std::tolower(a);
156     int lowb = std::tolower(b);
157 
158     if (lowa == lowb) {
159         return a < b;
160     }
161 
162     return lowa < lowb;
163 }
164 
165 namespace internal {
IsVisibleShortSwitch(const Arg & arg)166 inline bool IsVisibleShortSwitch(const Arg &arg) {
167     return !(arg.getName() == Arg::ignoreNameString() ||
168              arg.isValueRequired() || arg.getFlag() == "") &&
169            arg.visibleInHelp();
170 }
171 
IsVisibleLongSwitch(const Arg & arg)172 inline bool IsVisibleLongSwitch(const Arg &arg) {
173     return (arg.getName() != Arg::ignoreNameString() &&
174             !arg.isValueRequired() && arg.getFlag() == "" &&
175             arg.visibleInHelp());
176 }
177 
IsVisibleOption(const Arg & arg)178 inline bool IsVisibleOption(const Arg &arg) {
179     return (arg.getName() != Arg::ignoreNameString() && arg.isValueRequired() &&
180             arg.hasLabel() && arg.visibleInHelp());
181 }
182 
CompareShortID(const Arg * a,const Arg * b)183 inline bool CompareShortID(const Arg *a, const Arg *b) {
184     if (a->getFlag() == "" && b->getFlag() != "") {
185         return false;
186     }
187     if (b->getFlag() == "" && a->getFlag() != "") {
188         return true;
189     }
190 
191     return a->shortID() < b->shortID();
192 }
193 
194 // TODO: Fix me not to put --gopt before -f
CompareOptions(std::pair<const Arg *,bool> a,std::pair<const Arg *,bool> b)195 inline bool CompareOptions(std::pair<const Arg *, bool> a,
196                            std::pair<const Arg *, bool> b) {
197     // First optional, then required
198     if (!a.second && b.second) {
199         return true;
200     }
201     if (a.second && !b.second) {
202         return false;
203     }
204 
205     return CompareShortID(a.first, b.first);
206 }
207 }  // namespace internal
208 
209 /**
210  * Usage statements should look like the manual pages.  Options w/o
211  * operands come first, in alphabetical order inside a single set of
212  * braces, upper case before lower case (AaBbCc...).  Next are options
213  * with operands, in the same order, each in braces.  Then required
214  * arguments in the order they are specified, followed by optional
215  * arguments in the order they are specified.  A bar (`|') separates
216  * either/or options/arguments, and multiple options/arguments which
217  * are specified together are placed in a single set of braces.
218  *
219  * Use getprogname() instead of hardcoding the program name.
220  *
221  * "usage: f [-aDde] [-b b_arg] [-m m_arg] req1 req2 [opt1 [opt2]]\n"
222  * "usage: f [-a | -b] [-c [-de] [-n number]]\n"
223  */
_shortUsage(CmdLineInterface & _cmd,std::ostream & os)224 inline void StdOutput::_shortUsage(CmdLineInterface &_cmd,
225                                    std::ostream &os) const {
226     std::list<ArgGroup *> argSets = _cmd.getArgGroups();
227 
228     std::ostringstream outp;
229     outp << _cmd.getProgramName() + " ";
230 
231     std::string switches = Arg::flagStartString();
232 
233     std::list<ArgGroup *> exclusiveGroups;
234     std::list<ArgGroup *> nonExclusiveGroups;
235     for (std::list<ArgGroup *>::iterator sit = argSets.begin();
236          sit != argSets.end(); ++sit) {
237         if (CountVisibleArgs(**sit) <= 0) {
238             continue;
239         }
240 
241         if ((*sit)->isExclusive()) {
242             exclusiveGroups.push_back(*sit);
243         } else {
244             nonExclusiveGroups.push_back(*sit);
245         }
246     }
247 
248     // Move "exclusive groups" that have at most a single item to
249     // non-exclusive groups as exclusivit doesn't make sense with a
250     // single option. This can happen if args are hidden in help for
251     // example.
252     for (std::list<ArgGroup *>::iterator it = exclusiveGroups.begin();
253          it != exclusiveGroups.end();) {
254         if (CountVisibleArgs(**it) < 2) {
255             nonExclusiveGroups.push_back(*it);
256             it = exclusiveGroups.erase(it);
257         } else {
258             ++it;
259         }
260     }
261 
262     // First short switches (needs to be special because they are all
263     // stuck together).
264     for (std::list<ArgGroup *>::iterator sit = nonExclusiveGroups.begin();
265          sit != nonExclusiveGroups.end(); ++sit) {
266         for (ArgGroup::iterator it = (*sit)->begin(); it != (*sit)->end();
267              ++it) {
268             if (internal::IsVisibleShortSwitch(**it)) {
269                 switches += (*it)->getFlag();
270             }
271         }
272 
273         std::sort(switches.begin(), switches.end(), cmpSwitch);
274     }
275 
276     outp << " [" << switches << ']';
277 
278     // Now do long switches (e.g., --version, but no -v)
279     std::vector<Arg *> longSwitches;
280     for (std::list<ArgGroup *>::iterator sit = nonExclusiveGroups.begin();
281          sit != nonExclusiveGroups.end(); ++sit) {
282         for (ArgGroup::iterator it = (*sit)->begin(); it != (*sit)->end();
283              ++it) {
284             Arg &arg = **it;
285             if (internal::IsVisibleLongSwitch(arg)) {
286                 longSwitches.push_back(&arg);
287             }
288         }
289     }
290 
291     std::sort(longSwitches.begin(), longSwitches.end(),
292               internal::CompareShortID);
293     for (std::vector<Arg *>::const_iterator it = longSwitches.begin();
294          it != longSwitches.end(); ++it) {
295         outp << " [" << (**it).shortID() << ']';
296     }
297 
298     // Now do all exclusive groups
299     for (std::list<ArgGroup *>::iterator sit = exclusiveGroups.begin();
300          sit != exclusiveGroups.end(); ++sit) {
301         ArgGroup &argGroup = **sit;
302         outp << (argGroup.isRequired() ? " {" : " [");
303 
304         std::vector<Arg *> args;
305         for (ArgGroup::iterator it = argGroup.begin(); it != argGroup.end();
306              ++it) {
307             if ((**it).visibleInHelp()) {
308                 args.push_back(*it);
309             }
310         }
311 
312         std::sort(args.begin(), args.end(), internal::CompareShortID);
313         std::string sep = "";
314         for (std::vector<Arg *>::const_iterator it = args.begin();
315              it != args.end(); ++it) {
316             outp << sep << (**it).shortID();
317             sep = "|";
318         }
319 
320         outp << (argGroup.isRequired() ? '}' : ']');
321     }
322 
323     // Next do options, we sort them later by optional first.
324     std::vector<std::pair<const Arg *, bool> > options;
325     for (std::list<ArgGroup *>::iterator sit = nonExclusiveGroups.begin();
326          sit != nonExclusiveGroups.end(); ++sit) {
327         for (ArgGroup::iterator it = (*sit)->begin(); it != (*sit)->end();
328              ++it) {
329             Arg &arg = **it;
330             int visible = CountVisibleArgs(**sit);
331             bool required = arg.isRequired();
332             if (internal::IsVisibleOption(arg)) {
333                 if (visible == 1 && (**sit).isRequired()) {
334                     required = true;
335                 }
336 
337                 options.push_back(std::make_pair(&arg, required));
338             }
339         }
340     }
341 
342     std::sort(options.begin(), options.end(), internal::CompareOptions);
343     for (std::vector<std::pair<const Arg *, bool> >::const_iterator it =
344              options.begin();
345          it != options.end(); ++it) {
346         const Arg &arg = *it->first;
347         bool required = it->second;
348         outp << (required ? " " : " [");
349         outp << arg.shortID();
350         outp << (required ? "" : "]");
351     }
352 
353     // Next do argsuments ("unlabled") in order of definition
354     for (std::list<ArgGroup *>::iterator sit = nonExclusiveGroups.begin();
355          sit != nonExclusiveGroups.end(); ++sit) {
356         for (ArgGroup::iterator it = (*sit)->begin(); it != (*sit)->end();
357              ++it) {
358             Arg &arg = **it;
359             if (arg.getName() == Arg::ignoreNameString()) {
360                 continue;
361             }
362 
363             if (arg.isValueRequired() && !arg.hasLabel() &&
364                 arg.visibleInHelp()) {
365                 outp << (arg.isRequired() ? " " : " [");
366                 outp << arg.shortID();
367                 outp << (arg.isRequired() ? "" : "]");
368             }
369         }
370     }
371 
372     // if the program name is too long, then adjust the second line offset
373     int secondLineOffset = static_cast<int>(_cmd.getProgramName().length()) + 2;
374     if (secondLineOffset > 75 / 2) secondLineOffset = static_cast<int>(75 / 2);
375 
376     spacePrint(os, outp.str(), 75, 3, secondLineOffset);
377 }
378 
_longUsage(CmdLineInterface & _cmd,std::ostream & os)379 inline void StdOutput::_longUsage(CmdLineInterface &_cmd,
380                                   std::ostream &os) const {
381     std::string message = _cmd.getMessage();
382     std::list<ArgGroup *> argSets = _cmd.getArgGroups();
383 
384     std::list<Arg *> unlabled;
385     for (std::list<ArgGroup *>::iterator sit = argSets.begin();
386          sit != argSets.end(); ++sit) {
387         ArgGroup &argGroup = **sit;
388 
389         int visible = CountVisibleArgs(argGroup);
390         bool exclusive = visible > 1 && argGroup.isExclusive();
391         bool forceRequired = visible == 1 && argGroup.isRequired();
392         if (exclusive) {
393             spacePrint(os, argGroup.isRequired() ? "One of:" : "Either of:", 75,
394                        3, 0);
395         }
396 
397         for (ArgGroup::iterator it = argGroup.begin(); it != argGroup.end();
398              ++it) {
399             Arg &arg = **it;
400             if (!arg.visibleInHelp()) {
401                 continue;
402             }
403 
404             if (!arg.hasLabel()) {
405                 unlabled.push_back(&arg);
406                 continue;
407             }
408 
409             bool required = arg.isRequired() || forceRequired;
410             if (exclusive) {
411                 spacePrint(os, arg.longID(), 75, 6, 3);
412                 spacePrint(os, arg.getDescription(required), 75, 8, 0);
413             } else {
414                 spacePrint(os, arg.longID(), 75, 3, 3);
415                 spacePrint(os, arg.getDescription(required), 75, 5, 0);
416             }
417             os << '\n';
418         }
419     }
420 
421     for (ArgListIterator it = unlabled.begin(); it != unlabled.end(); ++it) {
422         const Arg &arg = **it;
423         spacePrint(os, arg.longID(), 75, 3, 3);
424         spacePrint(os, arg.getDescription(), 75, 5, 0);
425         os << '\n';
426     }
427 
428     if (!message.empty()) {
429         spacePrint(os, message, 75, 3, 0);
430     }
431 
432     os.flush();
433 }
434 
435 namespace {
fmtPrintLine(std::ostream & os,const std::string & s,int maxWidth,int indentSpaces,int secondLineOffset)436 inline void fmtPrintLine(std::ostream &os, const std::string &s, int maxWidth,
437                          int indentSpaces, int secondLineOffset) {
438     const std::string splitChars(" ,|");
439     int maxChars = maxWidth - indentSpaces;
440     std::string indentString(indentSpaces, ' ');
441     int from = 0;
442     int to = 0;
443     int end = s.length();
444     for (;;) {
445         if (end - from <= maxChars) {
446             // Rest of string fits on line, just print the remainder
447             os << indentString << s.substr(from) << std::endl;
448             return;
449         }
450 
451         // Find the next place where it is good to break the string
452         // (to) by finding the place where it is too late (tooFar) and
453         // taking the previous one.
454         int tooFar = to;
455         while (tooFar - from <= maxChars &&
456                static_cast<std::size_t>(tooFar) != std::string::npos) {
457             to = tooFar;
458             tooFar = s.find_first_of(splitChars, to + 1);
459         }
460 
461         if (to == from) {
462             // In case there was no good place to break the string,
463             // just break it in the middle of a word at line length.
464             to = from + maxChars - 1;
465         }
466 
467         if (s[to] != ' ') {
468             // Include delimiter before line break, unless it's a space
469             to++;
470         }
471 
472         os << indentString << s.substr(from, to - from) << '\n';
473 
474         // Avoid printing extra white space at start of a line
475         for (; s[to] == ' '; to++) {
476         }
477         from = to;
478 
479         if (secondLineOffset != 0) {
480             // Adjust offset for following lines
481             indentString.insert(indentString.end(), secondLineOffset, ' ');
482             maxChars -= secondLineOffset;
483             secondLineOffset = 0;
484         }
485     }
486 }
487 }  // namespace
488 
spacePrint(std::ostream & os,const std::string & s,int maxWidth,int indentSpaces,int secondLineOffset)489 inline void StdOutput::spacePrint(std::ostream &os, const std::string &s,
490                                   int maxWidth, int indentSpaces,
491                                   int secondLineOffset) const {
492     std::stringstream ss(s);
493     std::string line;
494     std::getline(ss, line);
495     fmtPrintLine(os, line, maxWidth, indentSpaces, secondLineOffset);
496     indentSpaces += secondLineOffset;
497 
498     while (std::getline(ss, line)) {
499         fmtPrintLine(os, line, maxWidth, indentSpaces, 0);
500     }
501 }
502 
503 }  // namespace TCLAP
504 
505 #endif  // TCLAP_STD_OUTPUT_H
506