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