Line data Source code
1 : /*
2 : * License:
3 : * Copyright (c) 2006-2019 Made to Order Software Corp. All Rights Reserved
4 : *
5 : * https://snapwebsites.org/
6 : * contact@m2osw.com
7 : *
8 : * This program is free software; you can redistribute it and/or modify
9 : * it under the terms of the GNU General Public License as published by
10 : * the Free Software Foundation; either version 2 of the License, or
11 : * (at your option) any later version.
12 : *
13 : * This program is distributed in the hope that it will be useful,
14 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : * GNU General Public License for more details.
17 : *
18 : * You should have received a copy of the GNU General Public License along
19 : * with this program; if not, write to the Free Software Foundation, Inc.,
20 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 : *
22 : * Authors:
23 : * Alexis Wilke alexis@m2osw.com
24 : * Doug Barbieri doug@m2osw.com
25 : */
26 :
27 : /** \file
28 : * \brief Advanced getopt usage() implementation.
29 : *
30 : * The advgetopt class usage() and helper functions are grouped in this
31 : * file.
32 : */
33 :
34 : // self
35 : //
36 : #include "advgetopt/advgetopt.h"
37 :
38 :
39 : // advgetopt lib
40 : //
41 : #include "advgetopt/exception.h"
42 :
43 :
44 : // C++ lib
45 : //
46 : #include <iomanip>
47 : #include <iostream>
48 :
49 :
50 : // C lib
51 : //
52 : #include <unistd.h>
53 : #include <sys/ioctl.h>
54 :
55 :
56 : // last include
57 : //
58 : #include <snapdev/poison.h>
59 :
60 :
61 :
62 :
63 : namespace advgetopt
64 : {
65 :
66 :
67 :
68 : /** \brief Transform group names in --\<name>-help commands.
69 : *
70 : * This function allows for the group names to be transformed into help
71 : * command line options.
72 : */
73 322 : void getopt::parse_options_from_group_names()
74 : {
75 : // add the --long-help if at least one option uses the GROUP1 or GROUP2
76 : //
77 691 : for(auto it(f_options_by_name.begin())
78 691 : ; it != f_options_by_name.end()
79 : ; ++it)
80 : {
81 375 : if(it->second->has_flag(GETOPT_FLAG_SHOW_GROUP1 | GETOPT_FLAG_SHOW_GROUP2))
82 : {
83 12 : option_info::pointer_t opt(std::make_shared<option_info>("long-help"));
84 6 : opt->add_flag(GETOPT_FLAG_COMMAND_LINE
85 : | GETOPT_FLAG_FLAG
86 : | GETOPT_FLAG_GROUP_COMMANDS);
87 6 : opt->set_help("show all the help from all the available options.");
88 6 : f_options_by_name["long-help"] = opt;
89 6 : break;
90 : }
91 : }
92 :
93 322 : if(f_options_environment.f_groups == nullptr)
94 : {
95 : // no groups, ignore following loop
96 : //
97 288 : return;
98 : }
99 :
100 102 : for(group_description const * grp = f_options_environment.f_groups
101 102 : ; grp->f_group != GETOPT_FLAG_GROUP_NONE
102 : ; ++grp)
103 : {
104 : // the name is not mandatory, without it you do not get the command
105 : // line option but still get the group description
106 : //
107 68 : if(grp->f_name != nullptr
108 6 : && *grp->f_name != '\0')
109 : {
110 12 : std::string const name(grp->f_name);
111 12 : std::string const option_name(name + "-help");
112 12 : option_info::pointer_t opt(std::make_shared<option_info>(option_name));
113 6 : opt->add_flag(GETOPT_FLAG_COMMAND_LINE
114 : | GETOPT_FLAG_FLAG
115 : | GETOPT_FLAG_GROUP_COMMANDS);
116 12 : opt->set_help("show help from the \""
117 12 : + name
118 12 : + "\" group of options.");
119 6 : f_options_by_name[option_name] = opt;
120 : }
121 : }
122 : }
123 :
124 :
125 : /** \brief Search for \p group in the list of group names.
126 : *
127 : * This function is used to search for the name of a group.
128 : *
129 : * Groups are used by the usage() function to list options by some user
130 : * selected group.
131 : *
132 : * For example, it is often that a tool has a set of commands such as
133 : * `--delete` and a set of options such as `--verbose`. These can represent
134 : * to clear groups of commands and options.
135 : *
136 : * \param[in] group The group to look for (i.e. GETOPT_FLAG_GROUP_ONE).
137 : *
138 : * \return The group structure or nullptr when not found.
139 : */
140 45 : group_description const * getopt::find_group(flag_t group) const
141 : {
142 45 : if(f_options_environment.f_groups == nullptr)
143 : {
144 2 : return nullptr;
145 : }
146 :
147 43 : if((group & ~GETOPT_FLAG_GROUP_MASK) != 0)
148 : {
149 29 : throw getopt_logic_error("group parameter must represent a valid group.");
150 : }
151 14 : if(group == GETOPT_FLAG_GROUP_NONE)
152 : {
153 1 : throw getopt_logic_error("group NONE cannot be assigned a name so you cannot search for it.");
154 : }
155 :
156 21 : for(group_description const * grp(f_options_environment.f_groups)
157 21 : ; grp->f_group != GETOPT_FLAG_GROUP_NONE
158 : ; ++grp)
159 : {
160 20 : if(group == grp->f_group)
161 : {
162 12 : if((grp->f_name == nullptr || *grp->f_name == '\0')
163 2 : && (grp->f_description == nullptr || *grp->f_description == '\0'))
164 : {
165 2 : throw getopt_logic_error("at least one of a group name or description must be defined (a non-empty string).");
166 : }
167 10 : return grp;
168 : }
169 : }
170 :
171 : // group not defined
172 : //
173 1 : return nullptr;
174 : }
175 :
176 :
177 : /** \brief Create a string of the command line arguments.
178 : *
179 : * This function assembles the command line arguments in a string and
180 : * returns that string.
181 : *
182 : * The function has the ability to wrap strings around for better formatting.
183 : *
184 : * The list of arguments to show is defined by the \p show parameter. When
185 : * \p show is 0, then only the regular and error arguments are shown.
186 : * Otherwise only the argumenst with the specified flags are show. Only
187 : * the `..._SHOW_...` flags are valid here.
188 : *
189 : * When an error occurs, it is customary to set \p show to
190 : * GETOPT_FLAG_SHOW_USAGE_ON_ERROR so only a limited set of arguments
191 : * are shown.
192 : *
193 : * The library offers two groups in case you have a command line tools
194 : * with a large number of options, those two can be used to only show
195 : * those specific set of options with using a specific `--help` argument.
196 : *
197 : * \note
198 : * This function does NOT print anything in the output. This is your
199 : * responsibility. We do it this way because you may be using a logger
200 : * and not want to print the usage in the \em wrong destination.
201 : *
202 : * \bug
203 : * The options are written from our map. This means the order will be
204 : * alphabetical and not the order in which you defined the options.
205 : * We are not looking into fixing this problem. That's just something
206 : * you want to keep in mind.
207 : *
208 : * \param[in] show Selection of the options to show.
209 : *
210 : * \return The assembled command line arguments.
211 : */
212 79 : std::string getopt::usage( flag_t show ) const
213 : {
214 158 : std::stringstream ss;
215 :
216 79 : flag_t specific_group(show & GETOPT_FLAG_GROUP_MASK);
217 :
218 : // ignore all the non-show flags
219 : //
220 79 : show &= GETOPT_FLAG_SHOW_USAGE_ON_ERROR
221 : | GETOPT_FLAG_SHOW_ALL
222 : | GETOPT_FLAG_SHOW_GROUP1
223 : | GETOPT_FLAG_SHOW_GROUP2;
224 :
225 79 : size_t const line_width(get_line_width());
226 79 : ss << breakup_line(process_help_string(f_options_environment.f_help_header), 0, line_width);
227 :
228 158 : std::string save_default;
229 158 : std::string save_help;
230 :
231 79 : flag_t pos(GETOPT_FLAG_GROUP_MINIMUM);
232 79 : flag_t group_max(GETOPT_FLAG_GROUP_MAXIMUM);
233 79 : if(f_options_environment.f_groups == nullptr)
234 : {
235 72 : group_max = GETOPT_FLAG_GROUP_MINIMUM;
236 72 : specific_group = GETOPT_FLAG_GROUP_NONE;
237 : }
238 7 : else if(specific_group != GETOPT_FLAG_GROUP_NONE)
239 : {
240 : // only display that specific group if asked to do so
241 : //
242 2 : pos = specific_group >> GETOPT_FLAG_GROUP_SHIFT;
243 2 : group_max = pos;
244 : }
245 :
246 307 : for(; pos <= group_max; ++pos)
247 : {
248 114 : bool group_name_shown(false);
249 114 : flag_t const group(pos << GETOPT_FLAG_GROUP_SHIFT);
250 873 : for(auto const & opt : f_options_by_name)
251 : {
252 1518 : if((opt.second->get_flags() & GETOPT_FLAG_GROUP_MASK) != group
253 759 : && f_options_environment.f_groups != nullptr)
254 : {
255 : // this could be optimized but we'd probably not see much
256 : // difference overall and it's just for the usage() call
257 : //
258 1092 : continue;
259 : }
260 :
261 426 : std::string const help(opt.second->get_help());
262 256 : if(help.empty())
263 : {
264 : // ignore entries without help
265 : //
266 10 : continue;
267 : }
268 :
269 246 : if(opt.second->has_flag(GETOPT_FLAG_ALIAS))
270 : {
271 : // ignore entries representing an alias
272 : //
273 12 : continue;
274 : }
275 :
276 234 : if((show & GETOPT_FLAG_SHOW_ALL) == 0)
277 : {
278 177 : if(show != 0)
279 : {
280 66 : if(!opt.second->has_flag(show))
281 : {
282 : // usage selected group is not present in this option, ignore
283 : //
284 58 : continue;
285 : }
286 : }
287 111 : else if(opt.second->has_flag(GETOPT_FLAG_SHOW_GROUP1 | GETOPT_FLAG_SHOW_GROUP2))
288 : {
289 : // do not show specialized groups
290 : //
291 6 : continue;
292 : }
293 : }
294 :
295 170 : if(!group_name_shown)
296 : {
297 84 : group_name_shown = true;
298 :
299 84 : if(group != GETOPT_FLAG_GROUP_NONE)
300 : {
301 8 : group_description const * grp(find_group(group));
302 8 : if(grp != nullptr)
303 : {
304 8 : ss << std::endl
305 8 : << breakup_line(process_help_string(grp->f_description), 0, line_width);
306 : }
307 : }
308 : }
309 :
310 340 : std::stringstream argument;
311 :
312 170 : if(opt.second->is_default_option())
313 : {
314 6 : switch(opt.second->get_flags() & (GETOPT_FLAG_REQUIRED | GETOPT_FLAG_MULTIPLE))
315 : {
316 1 : case 0:
317 1 : argument << "[default argument]";
318 1 : break;
319 :
320 1 : case GETOPT_FLAG_REQUIRED:
321 1 : argument << "<default argument>";
322 1 : break;
323 :
324 2 : case GETOPT_FLAG_MULTIPLE:
325 2 : argument << "[default arguments]";
326 2 : break;
327 :
328 2 : case GETOPT_FLAG_REQUIRED | GETOPT_FLAG_MULTIPLE:
329 2 : argument << "<default arguments>";
330 2 : break;
331 :
332 : }
333 : }
334 : else
335 : {
336 164 : argument << "--" << opt.second->get_name();
337 164 : if(opt.second->get_short_name() != NO_SHORT_NAME)
338 : {
339 41 : argument << " or -" << short_name_to_string(opt.second->get_short_name());
340 : }
341 :
342 164 : switch(opt.second->get_flags() & (GETOPT_FLAG_FLAG | GETOPT_FLAG_REQUIRED | GETOPT_FLAG_MULTIPLE))
343 : {
344 1 : case 0:
345 1 : argument << " [<arg>]";
346 1 : break;
347 :
348 33 : case GETOPT_FLAG_REQUIRED:
349 33 : argument << " <arg>";
350 33 : break;
351 :
352 8 : case GETOPT_FLAG_MULTIPLE:
353 8 : argument << " {<arg>}";
354 8 : break;
355 :
356 6 : case GETOPT_FLAG_REQUIRED | GETOPT_FLAG_MULTIPLE:
357 6 : argument << " <arg> {<arg>}";
358 6 : break;
359 :
360 : }
361 : }
362 :
363 170 : if(opt.second->has_default())
364 : {
365 : argument << " (default is \""
366 9 : << opt.second->get_default()
367 18 : << "\")";
368 : }
369 :
370 : // Output argument string with help
371 : //
372 170 : if(opt.second->is_default_option())
373 : {
374 6 : save_default = argument.str();
375 6 : save_help = help;
376 : }
377 : else
378 : {
379 164 : ss << format_usage_string(argument.str()
380 328 : , process_help_string(help.c_str())
381 : , 30
382 : , line_width);
383 : }
384 : }
385 : }
386 :
387 79 : if(!save_default.empty())
388 : {
389 6 : ss << format_usage_string(save_default
390 12 : , process_help_string(save_help.c_str())
391 : , 30
392 : , line_width);
393 : }
394 :
395 79 : if(f_options_environment.f_help_footer != nullptr
396 77 : && f_options_environment.f_help_footer[0] != '\0')
397 : {
398 77 : ss << std::endl;
399 77 : ss << breakup_line(process_help_string(f_options_environment.f_help_footer), 0, line_width);
400 : }
401 :
402 158 : return ss.str();
403 : }
404 :
405 :
406 : /** \brief Change the % flags in help strings.
407 : *
408 : * This function goes through the help string and replaces the `%\<flag>`
409 : * with various content available in the getopt object.
410 : *
411 : * This is helpful for various reasons. For example, you may use the
412 : * same set of options in several different programs, in which case the
413 : * `%p` is likely useful to print out the name of the program currently
414 : * in use.
415 : *
416 : * Similarly we offer ways to print out lists of configuration files,
417 : * the environment variable name & value, etc. The following is the
418 : * list of supported flags:
419 : *
420 : * \li "%%" -- print out a percent
421 : * \li "%a" -- print out the project name (a.k.a. application name)
422 : * \li "%b" -- print out the build date
423 : * \li "%c" -- print out the copyright notice
424 : * \li "%d" -- print out the first directory with configuration files.
425 : * \li "%*d" -- print out the complete list of directories with configuration
426 : * files.
427 : * \li "%e" -- print out the name of the environment variable.
428 : * \li "%*e" -- print out the name and value of the environment variable.
429 : * \li "%f" -- print out the first configuration path and filename.
430 : * \li "%*f" -- print out all the configuration full paths.
431 : * \li "%g" -- print out the list of existing configuration files.
432 : * \li "%*g" -- print out the list of all possible configuration files.
433 : * \li "%i" -- print out the directory to option files.
434 : * \li "%l" -- print out the license.
435 : * \li "%o" -- show the configuration filename where changes get written.
436 : * \li "%p" -- print out the program basename.
437 : * \li "%*p" -- print out the full program name.
438 : * \li "%s" -- print out the group name.
439 : * \li "%t" -- print out the build time.
440 : * \li "%v" -- print out the version.
441 : * \li "%w" -- print out the list of all the writable configuration files.
442 : *
443 : * Here is an example where the `%p` can be used:
444 : *
445 : * \code
446 : * "Usage: %p [-opt] filename ..."
447 : * \endcode
448 : *
449 : * The other flags are more often used in places like the copyright notice
450 : * the footer, the license notice, etc.
451 : *
452 : * \param[in] help A string that may include `%` flags.
453 : *
454 : * \return The string with any '%\<flag>' replaced.
455 : *
456 : * \sa parse_program_name()
457 : */
458 335 : std::string getopt::process_help_string(char const * help) const
459 : {
460 335 : if(help == nullptr)
461 : {
462 1 : return std::string();
463 : }
464 :
465 668 : std::string result;
466 :
467 37572 : while(help[0] != '\0')
468 : {
469 18619 : if(help[0] == '%')
470 : {
471 410 : switch(help[1])
472 : {
473 13 : case '%':
474 13 : result += '%';
475 13 : help += 2;
476 13 : break;
477 :
478 98 : case '*':
479 98 : switch(help[2])
480 : {
481 19 : case 'd':
482 19 : if(f_options_environment.f_configuration_directories != nullptr)
483 : {
484 16 : bool first(true);
485 68 : for(char const * const * directories(f_options_environment.f_configuration_directories)
486 68 : ; *directories != nullptr
487 : ; ++directories)
488 : {
489 52 : if(first)
490 : {
491 13 : first = false;
492 : }
493 : else
494 : {
495 39 : result += ", ";
496 : }
497 52 : result += *directories;
498 : }
499 : }
500 19 : help += 3;
501 19 : break;
502 :
503 28 : case 'e':
504 28 : if(f_options_environment.f_environment_variable_name != nullptr
505 22 : && *f_options_environment.f_environment_variable_name != '\0')
506 : {
507 16 : result += f_options_environment.f_environment_variable_name;
508 16 : char const * env(getenv(f_options_environment.f_environment_variable_name));
509 16 : if(env != nullptr)
510 : {
511 3 : result += '=';
512 3 : result += env;
513 : }
514 : else
515 : {
516 13 : result += " (not set)";
517 : }
518 : }
519 28 : help += 3;
520 28 : break;
521 :
522 19 : case 'f':
523 19 : if(f_options_environment.f_configuration_files != nullptr)
524 : {
525 16 : bool first(true);
526 68 : for(char const * const * filenames(f_options_environment.f_configuration_files)
527 68 : ; *filenames != nullptr
528 : ; ++filenames)
529 : {
530 52 : if(first)
531 : {
532 13 : first = false;
533 : }
534 : else
535 : {
536 39 : result += ", ";
537 : }
538 52 : result += *filenames;
539 : }
540 : }
541 19 : help += 3;
542 19 : break;
543 :
544 19 : case 'g':
545 : {
546 38 : string_list_t list(get_configuration_filenames(false, false));
547 19 : bool first(true);
548 193 : for(auto n : list)
549 : {
550 174 : if(first)
551 : {
552 13 : first = false;
553 : }
554 : else
555 : {
556 161 : result += ", ";
557 : }
558 174 : result += n;
559 : }
560 38 : help += 3;
561 : }
562 19 : break;
563 :
564 13 : case 'p':
565 13 : result += f_program_fullname;
566 13 : help += 3;
567 13 : break;
568 :
569 : }
570 98 : break;
571 :
572 19 : case 'a':
573 19 : if(f_options_environment.f_project_name != nullptr)
574 : {
575 16 : result += f_options_environment.f_project_name;
576 : }
577 19 : help += 2;
578 19 : break;
579 :
580 19 : case 'b':
581 19 : if(f_options_environment.f_build_date != nullptr)
582 : {
583 16 : result += f_options_environment.f_build_date;
584 : }
585 19 : help += 2;
586 19 : break;
587 :
588 19 : case 'c':
589 19 : if(f_options_environment.f_copyright != nullptr)
590 : {
591 16 : result += f_options_environment.f_copyright;
592 : }
593 19 : help += 2;
594 19 : break;
595 :
596 19 : case 'd':
597 19 : if(f_options_environment.f_configuration_directories != nullptr
598 16 : && *f_options_environment.f_configuration_directories != nullptr)
599 : {
600 13 : result += *f_options_environment.f_configuration_directories;
601 : }
602 19 : help += 2;
603 19 : break;
604 :
605 28 : case 'e':
606 28 : if(f_options_environment.f_environment_variable_name != nullptr)
607 : {
608 22 : result += f_options_environment.f_environment_variable_name;
609 : }
610 28 : help += 2;
611 28 : break;
612 :
613 19 : case 'f':
614 19 : if(f_options_environment.f_configuration_files != nullptr
615 16 : && *f_options_environment.f_configuration_files != nullptr)
616 : {
617 13 : result += *f_options_environment.f_configuration_files;
618 : }
619 19 : help += 2;
620 19 : break;
621 :
622 22 : case 'g':
623 : {
624 44 : string_list_t list(get_configuration_filenames(true, false));
625 22 : bool first(true);
626 34 : for(auto n : list)
627 : {
628 12 : if(first)
629 : {
630 6 : first = false;
631 : }
632 : else
633 : {
634 6 : result += ", ";
635 : }
636 12 : result += n;
637 : }
638 44 : help += 2;
639 : }
640 22 : break;
641 :
642 19 : case 'i':
643 : // in the advgetopt_options.cpp, we clearly add a final "/"
644 : // so we want to add it here too, to be consistent
645 : {
646 38 : std::string directory("/usr/share/advgetopt/options/");
647 19 : if(f_options_environment.f_options_files_directory != nullptr
648 16 : && *f_options_environment.f_options_files_directory != '\0')
649 : {
650 13 : directory = f_options_environment.f_options_files_directory;
651 13 : if(directory.back() != '/')
652 : {
653 13 : directory += '/';
654 : }
655 : }
656 38 : result += directory;
657 : }
658 19 : help += 2;
659 19 : break;
660 :
661 19 : case 'l':
662 19 : if(f_options_environment.f_license != nullptr)
663 : {
664 16 : result += f_options_environment.f_license;
665 : }
666 19 : help += 2;
667 19 : break;
668 :
669 19 : case 'o':
670 : {
671 38 : string_list_t const list(get_configuration_filenames(false, true));
672 19 : if(!list.empty())
673 : {
674 13 : result += list.back();
675 : }
676 38 : help += 2;
677 : }
678 19 : break;
679 :
680 32 : case 'p':
681 32 : result += f_program_name;
682 32 : help += 2;
683 32 : break;
684 :
685 5 : case 's':
686 5 : if(f_options_environment.f_group_name != nullptr)
687 : {
688 5 : result += f_options_environment.f_group_name;
689 : }
690 5 : help += 2;
691 5 : break;
692 :
693 19 : case 't':
694 19 : if(f_options_environment.f_build_time != nullptr)
695 : {
696 16 : result += f_options_environment.f_build_time;
697 : }
698 19 : help += 2;
699 19 : break;
700 :
701 19 : case 'v':
702 19 : if(f_options_environment.f_version != nullptr)
703 : {
704 16 : result += f_options_environment.f_version;
705 : }
706 19 : help += 2;
707 19 : break;
708 :
709 22 : case 'w':
710 : {
711 44 : string_list_t const list(get_configuration_filenames(true, true));
712 22 : bool first(true);
713 31 : for(auto n : list)
714 : {
715 9 : if(first)
716 : {
717 6 : first = false;
718 : }
719 : else
720 : {
721 3 : result += ", ";
722 : }
723 9 : result += n;
724 : }
725 44 : help += 2;
726 : }
727 22 : break;
728 :
729 : }
730 : }
731 : else
732 : {
733 18209 : result += help[0];
734 18209 : ++help;
735 : }
736 : }
737 :
738 334 : return result;
739 : }
740 :
741 :
742 : /** \brief Format a help string to make it fit on a given width.
743 : *
744 : * This function properly wraps a set of help strings so they fit in
745 : * your console. The width has to be given by you at the moment.
746 : *
747 : * The function takes two strings, the argument with it's options
748 : * and the actual help string for that argument. If the argument
749 : * is short enough, it will appear on the first line with the
750 : * first line of help. If not, then one whole line is reserved
751 : * just for the argument and the help starts on the next line.
752 : *
753 : * \param[in] argument The option name with -- and arguments.
754 : * \param[in] help The help string for this argument.
755 : * \param[in] option_width Number of characters reserved for the option.
756 : * \param[in] line_width The maximum number of characters to display in width.
757 : *
758 : * \return A help string formatted for display.
759 : */
760 300 : std::string getopt::format_usage_string(
761 : std::string const & argument
762 : , std::string const & help
763 : , size_t const option_width
764 : , size_t const line_width)
765 : {
766 600 : std::stringstream ss;
767 :
768 300 : ss << " ";
769 :
770 300 : if(argument.size() < option_width - 3)
771 : {
772 : // enough space on a single line
773 : //
774 : ss << argument
775 240 : << std::setw(option_width - 3 - argument.size())
776 480 : << " ";
777 : }
778 60 : else if(argument.size() >= line_width - 4)
779 : {
780 : // argument too long for even one line on the screen!?
781 : // call the function to break it up with indentation of 3
782 : //
783 2 : ss << breakup_line(argument, 3, line_width);
784 :
785 4 : if(!help.empty()
786 2 : && option_width > 0)
787 : {
788 2 : ss << std::setw(option_width) << " ";
789 : }
790 : }
791 : else
792 : {
793 : // argument too long for the help to follow immediately
794 : //
795 58 : ss << argument
796 58 : << std::endl
797 : << std::setw(option_width)
798 58 : << " ";
799 : }
800 :
801 300 : ss << breakup_line(help, option_width, line_width);
802 :
803 600 : return ss.str();
804 : }
805 :
806 :
807 : /** \brief Breakup a string on multiple lines.
808 : *
809 : * This function breaks up the specified \p line of text in one or more
810 : * strings to fit your output.
811 : *
812 : * The \p line_width represents the maximum number of characters that get
813 : * printed in a row.
814 : *
815 : * The \p option_width parameter is the number of characters in the left
816 : * margin. When dealing with a very long argument, this width is 3 characters.
817 : * When dealing with the help itself, it is expected to be around 30.
818 : *
819 : * \note
820 : * This function always makes sure that the resulting string ends with
821 : * a newline character unless the input \p line string is empty.
822 : *
823 : * \param[in] line The line to breakup.
824 : * \param[in] option_width The number of characters in the left margin.
825 : * \param[in] line_width The total number of characters in the output.
826 : *
827 : * \return The broken up line as required.
828 : */
829 532 : std::string getopt::breakup_line(std::string line
830 : , size_t const option_width
831 : , size_t const line_width)
832 : {
833 1064 : std::stringstream ss;
834 :
835 532 : size_t const width(line_width - option_width);
836 :
837 : // TODO: once we have C++17, avoid substr() using std::string_view instead
838 : //
839 1840 : while(line.size() > width)
840 : {
841 1308 : std::string l;
842 654 : std::string::size_type const nl(line.find('\n'));
843 654 : if(nl != std::string::npos
844 404 : && nl < width)
845 : {
846 236 : l = line.substr(0, nl);
847 236 : line = line.substr(nl + 1);
848 : }
849 418 : else if(std::isspace(line[width]))
850 : {
851 : // special case when the space is right at the edge
852 : //
853 38 : l = line.substr(0, width);
854 38 : size_t pos(width);
855 2 : do
856 : {
857 40 : ++pos;
858 : }
859 40 : while(std::isspace(line[pos]));
860 38 : line = line.substr(pos);
861 : }
862 : else
863 : {
864 : // search for the last space before the edge of the screen
865 : //
866 380 : std::string::size_type pos(line.find_last_of(' ', width));
867 380 : if(pos == std::string::npos)
868 : {
869 : // no space found, cut right at the edge...
870 : // (this should be really rare)
871 : //
872 78 : l = line.substr(0, width);
873 78 : line = line.substr(width);
874 : }
875 : else
876 : {
877 : // we found a space, write everything up to that space
878 : //
879 302 : l = line.substr(0, pos);
880 :
881 : // remove additional spaces from the start of the next line
882 : do // LCOV_EXCL_LINE
883 : {
884 302 : ++pos;
885 : }
886 302 : while(std::isspace(line[pos]));
887 302 : line = line.substr(pos);
888 : }
889 : }
890 :
891 654 : ss << l
892 654 : << std::endl;
893 :
894 : // more to print? if so we need the indentation
895 : //
896 1308 : if(!line.empty()
897 654 : && option_width > 0)
898 : {
899 174 : ss << std::setw( option_width ) << " ";
900 : }
901 : }
902 :
903 : // some leftover?
904 : //
905 532 : if(!line.empty())
906 : {
907 532 : ss << line << std::endl;
908 : }
909 :
910 1064 : return ss.str();
911 : }
912 :
913 :
914 :
915 : /** \brief Retrieve the width of one line in your console.
916 : *
917 : * This function retrieves the width of the console in number of characters.
918 : *
919 : * If the process is not connected to a TTY, then the function returns 80.
920 : *
921 : * If the width is less than 40, the function returns 40.
922 : *
923 : * \return The width of the console screen.
924 : */
925 275 : size_t getopt::get_line_width()
926 : {
927 275 : std::int64_t cols(80);
928 :
929 275 : if(isatty(STDOUT_FILENO))
930 : {
931 : // when running coverage, the output is redirected for logging purposes
932 : // which means that isatty() returns false -- so at this time I just
933 : // exclude those since they are unreachable from my standard Unit Tests
934 : //
935 : winsize w;
936 : if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) // LCOV_EXCL_LINE
937 : {
938 : cols = std::max(static_cast<unsigned short>(40), w.ws_col); // LCOV_EXCL_LINE
939 : }
940 : }
941 :
942 275 : return cols;
943 : }
944 :
945 :
946 :
947 6 : } // namespace advgetopt
948 : // vim: ts=4 sw=4 et
|