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 <unistd.h>
54 : #include <sys/ioctl.h>
55 :
56 :
57 : // last include
58 : //
59 : #include <snapdev/poison.h>
60 :
61 :
62 :
63 :
64 : namespace advgetopt
65 : {
66 :
67 :
68 :
69 : /** \brief Transform group names in --\<name>-help commands.
70 : *
71 : * This function allows for the group names to be transformed into help
72 : * command line options.
73 : */
74 317 : void getopt::parse_options_from_group_names()
75 : {
76 : // add the --long-help if at least one option uses the GROUP1 or GROUP2
77 : //
78 2013 : for(auto it(f_options_by_name.begin())
79 1342 : ; it != f_options_by_name.end()
80 : ; ++it)
81 : {
82 360 : if(it->second->has_flag(GETOPT_FLAG_SHOW_GROUP1 | GETOPT_FLAG_SHOW_GROUP2))
83 : {
84 12 : option_info::pointer_t opt(std::make_shared<option_info>("long-help"));
85 6 : opt->add_flag(GETOPT_FLAG_COMMAND_LINE
86 : | GETOPT_FLAG_FLAG
87 6 : | GETOPT_FLAG_GROUP_COMMANDS);
88 6 : opt->set_help("show all the help from all the available options.");
89 6 : f_options_by_name["long-help"] = opt;
90 6 : break;
91 : }
92 : }
93 :
94 317 : if(f_options_environment.f_groups == nullptr)
95 : {
96 : // no groups, ignore following loop
97 : //
98 283 : return;
99 : }
100 :
101 102 : for(group_description const * grp = f_options_environment.f_groups
102 102 : ; grp->f_group != GETOPT_FLAG_GROUP_NONE
103 : ; ++grp)
104 : {
105 : // the name is not mandatory, without it you do not get the command
106 : // line option but still get the group description
107 : //
108 68 : if(grp->f_name != nullptr
109 6 : && *grp->f_name != '\0')
110 : {
111 12 : std::string const name(grp->f_name);
112 12 : std::string const option_name(name + "-help");
113 12 : option_info::pointer_t opt(std::make_shared<option_info>(option_name));
114 6 : opt->add_flag(GETOPT_FLAG_COMMAND_LINE
115 : | GETOPT_FLAG_FLAG
116 6 : | GETOPT_FLAG_GROUP_COMMANDS);
117 6 : opt->set_help("show help from the \""
118 12 : + name
119 12 : + "\" group of options.");
120 6 : f_options_by_name[option_name] = opt;
121 : }
122 : }
123 : }
124 :
125 :
126 : /** \brief Search for \p group in the list of group names.
127 : *
128 : * This function is used to search for the name of a group.
129 : *
130 : * Groups are used by the usage() function to list options by some user
131 : * selected group.
132 : *
133 : * For example, it is often that a tool has a set of commands such as
134 : * `--delete` and a set of options such as `--verbose`. These can represent
135 : * to clear groups of commands and options.
136 : *
137 : * \param[in] group The group to look for (i.e. GETOPT_FLAG_GROUP_ONE).
138 : *
139 : * \return The group structure or nullptr when not found.
140 : */
141 45 : group_description const * getopt::find_group(flag_t group) const
142 : {
143 45 : if(f_options_environment.f_groups == nullptr)
144 : {
145 2 : return nullptr;
146 : }
147 :
148 43 : if((group & ~GETOPT_FLAG_GROUP_MASK) != 0)
149 : {
150 29 : throw getopt_exception_logic("group parameter must represent a valid group.");
151 : }
152 14 : if(group == GETOPT_FLAG_GROUP_NONE)
153 : {
154 1 : throw getopt_exception_logic("group NONE cannot be assigned a name so you cannot search for it.");
155 : }
156 :
157 21 : for(group_description const * grp(f_options_environment.f_groups)
158 21 : ; grp->f_group != GETOPT_FLAG_GROUP_NONE
159 : ; ++grp)
160 : {
161 20 : if(group == grp->f_group)
162 : {
163 12 : if((grp->f_name == nullptr || *grp->f_name == '\0')
164 2 : && (grp->f_description == nullptr || *grp->f_description == '\0'))
165 : {
166 2 : throw getopt_exception_logic("at least one of a group name or description must be defined (a non-empty string).");
167 : }
168 10 : return grp;
169 : }
170 : }
171 :
172 : // group not defined
173 : //
174 1 : return nullptr;
175 : }
176 :
177 :
178 : /** \brief Create a string of the command line arguments.
179 : *
180 : * This function assembles the command line arguments in a string and
181 : * returns that string.
182 : *
183 : * The function has the ability to wrap strings around for better formatting.
184 : *
185 : * The list of arguments to show is defined by the \p show parameter. When
186 : * \p show is 0, then only the regular and error arguments are shown.
187 : * Otherwise only the argumenst with the specified flags are show. Only
188 : * the `..._SHOW_...` flags are valid here.
189 : *
190 : * When an error occurs, it is customary to set \p show to
191 : * GETOPT_FLAG_SHOW_USAGE_ON_ERROR so only a limited set of arguments
192 : * are shown.
193 : *
194 : * The library offers two groups in case you have a command line tools
195 : * with a large number of options, those two can be used to only show
196 : * those specific set of options with using a specific `--help` argument.
197 : *
198 : * \note
199 : * This function does NOT print anything in the output. This is your
200 : * responsibility. We do it this way because you may be using a logger
201 : * and not want to print the usage in the \em wrong destination.
202 : *
203 : * \bug
204 : * The options are written from our map. This means the order will be
205 : * alphabetical and not the order in which you defined the options.
206 : * We are not looking into fixing this problem. That's just something
207 : * you want to keep in mind.
208 : *
209 : * \param[in] show Selection of the options to show.
210 : *
211 : * \return The assembled command line arguments.
212 : */
213 79 : std::string getopt::usage( flag_t show ) const
214 : {
215 158 : std::stringstream ss;
216 :
217 79 : flag_t specific_group(show & GETOPT_FLAG_GROUP_MASK);
218 :
219 : // ignore all the non-show flags
220 : //
221 : show &= GETOPT_FLAG_SHOW_USAGE_ON_ERROR
222 : | GETOPT_FLAG_SHOW_ALL
223 : | GETOPT_FLAG_SHOW_GROUP1
224 79 : | GETOPT_FLAG_SHOW_GROUP2;
225 :
226 79 : size_t const line_width(get_line_width());
227 79 : ss << breakup_line(process_help_string(f_options_environment.f_help_header), 0, line_width);
228 :
229 158 : std::string save_default;
230 158 : std::string save_help;
231 :
232 79 : flag_t pos(GETOPT_FLAG_GROUP_MINIMUM);
233 79 : flag_t group_max(GETOPT_FLAG_GROUP_MAXIMUM);
234 79 : if(f_options_environment.f_groups == nullptr)
235 : {
236 72 : group_max = GETOPT_FLAG_GROUP_MINIMUM;
237 72 : specific_group = GETOPT_FLAG_GROUP_NONE;
238 : }
239 7 : else if(specific_group != GETOPT_FLAG_GROUP_NONE)
240 : {
241 : // only display that specific group if asked to do so
242 : //
243 2 : pos = specific_group >> GETOPT_FLAG_GROUP_SHIFT;
244 2 : group_max = pos;
245 : }
246 :
247 307 : for(; pos <= group_max; ++pos)
248 : {
249 114 : bool group_name_shown(false);
250 114 : flag_t const group(pos << GETOPT_FLAG_GROUP_SHIFT);
251 873 : for(auto const & opt : f_options_by_name)
252 : {
253 1518 : if((opt.second->get_flags() & GETOPT_FLAG_GROUP_MASK) != group
254 759 : && f_options_environment.f_groups != nullptr)
255 : {
256 : // this could be optimized but we'd probably not see much
257 : // difference overall and it's just for the usage() call
258 : //
259 1092 : continue;
260 : }
261 :
262 426 : std::string const help(opt.second->get_help());
263 256 : if(help.empty())
264 : {
265 : // ignore entries without help
266 : //
267 10 : continue;
268 : }
269 :
270 246 : if(opt.second->has_flag(GETOPT_FLAG_ALIAS))
271 : {
272 : // ignore entries representing an alias
273 : //
274 12 : continue;
275 : }
276 :
277 234 : if((show & GETOPT_FLAG_SHOW_ALL) == 0)
278 : {
279 177 : if(show != 0)
280 : {
281 66 : if(!opt.second->has_flag(show))
282 : {
283 : // usage selected group is not present in this option, ignore
284 : //
285 58 : continue;
286 : }
287 : }
288 111 : else if(opt.second->has_flag(GETOPT_FLAG_SHOW_GROUP1 | GETOPT_FLAG_SHOW_GROUP2))
289 : {
290 : // do not show specialized groups
291 : //
292 6 : continue;
293 : }
294 : }
295 :
296 170 : if(!group_name_shown)
297 : {
298 84 : group_name_shown = true;
299 :
300 84 : if(group != GETOPT_FLAG_GROUP_NONE)
301 : {
302 8 : group_description const * grp(find_group(group));
303 8 : if(grp != nullptr)
304 : {
305 8 : ss << std::endl
306 16 : << breakup_line(process_help_string(grp->f_description), 0, line_width);
307 : }
308 : }
309 : }
310 :
311 340 : std::stringstream argument;
312 :
313 170 : if(opt.second->is_default_option())
314 : {
315 6 : switch(opt.second->get_flags() & (GETOPT_FLAG_REQUIRED | GETOPT_FLAG_MULTIPLE))
316 : {
317 : case 0:
318 1 : argument << "[default argument]";
319 1 : break;
320 :
321 : case GETOPT_FLAG_REQUIRED:
322 1 : argument << "<default argument>";
323 1 : break;
324 :
325 : case GETOPT_FLAG_MULTIPLE:
326 2 : argument << "[default arguments]";
327 2 : break;
328 :
329 : case GETOPT_FLAG_REQUIRED | GETOPT_FLAG_MULTIPLE:
330 2 : argument << "<default arguments>";
331 2 : break;
332 :
333 : }
334 : }
335 : else
336 : {
337 164 : argument << "--" << opt.second->get_name();
338 164 : if(opt.second->get_short_name() != NO_SHORT_NAME)
339 : {
340 41 : argument << " or -" << short_name_to_string(opt.second->get_short_name());
341 : }
342 :
343 164 : switch(opt.second->get_flags() & (GETOPT_FLAG_FLAG | GETOPT_FLAG_REQUIRED | GETOPT_FLAG_MULTIPLE))
344 : {
345 : case 0:
346 1 : argument << " [<arg>]";
347 1 : break;
348 :
349 : case GETOPT_FLAG_REQUIRED:
350 33 : argument << " <arg>";
351 33 : break;
352 :
353 : case GETOPT_FLAG_MULTIPLE:
354 8 : argument << " {<arg>}";
355 8 : break;
356 :
357 : case GETOPT_FLAG_REQUIRED | GETOPT_FLAG_MULTIPLE:
358 6 : argument << " <arg> {<arg>}";
359 6 : break;
360 :
361 : }
362 : }
363 :
364 170 : if(opt.second->has_default())
365 : {
366 9 : argument << " (default is \""
367 18 : << opt.second->get_default()
368 9 : << "\")";
369 : }
370 :
371 : // Output argument string with help
372 : //
373 170 : if(opt.second->is_default_option())
374 : {
375 6 : save_default = argument.str();
376 6 : save_help = help;
377 : }
378 : else
379 : {
380 328 : ss << format_usage_string(argument.str()
381 328 : , process_help_string(help.c_str())
382 : , 30
383 164 : , line_width);
384 : }
385 : }
386 : }
387 :
388 79 : if(!save_default.empty())
389 : {
390 12 : ss << format_usage_string(save_default
391 12 : , process_help_string(save_help.c_str())
392 : , 30
393 6 : , line_width);
394 : }
395 :
396 79 : if(f_options_environment.f_help_footer != nullptr
397 77 : && f_options_environment.f_help_footer[0] != '\0')
398 : {
399 77 : ss << std::endl;
400 77 : ss << breakup_line(process_help_string(f_options_environment.f_help_footer), 0, line_width);
401 : }
402 :
403 158 : return ss.str();
404 : }
405 :
406 :
407 : /** \brief Change the % flags in help strings.
408 : *
409 : * This function goes through the help string and replaces the `%\<flag>`
410 : * with various content available in the getopt object.
411 : *
412 : * This is helpful for various reasons. For example, you may use the
413 : * same set of options in several different programs, in which case the
414 : * `%p` is likely useful to print out the name of the program currently
415 : * in use.
416 : *
417 : * Similarly we offer ways to print out lists of configuration files,
418 : * the environment variable name & value, etc. The following is the
419 : * list of supported flags:
420 : *
421 : * \li "%%" -- print out a percent
422 : * \li "%a" -- print out the project name (a.k.a. application name)
423 : * \li "%b" -- print out the build date
424 : * \li "%c" -- print out the copyright notice
425 : * \li "%d" -- print out the first directory with configuration files.
426 : * \li "%*d" -- print out the complete list of directories with configuration
427 : * files.
428 : * \li "%e" -- print out the name of the environment variable.
429 : * \li "%*e" -- print out the name and value of the environment variable.
430 : * \li "%f" -- print out the first configuration path and filename.
431 : * \li "%*f" -- print out all the configuration full paths.
432 : * \li "%g" -- print out the list of existing configuration files.
433 : * \li "%*g" -- print out the list of all possible configuration files.
434 : * \li "%i" -- print out the directory to option files.
435 : * \li "%l" -- print out the license.
436 : * \li "%o" -- show the configuration filename where changes get written.
437 : * \li "%p" -- print out the program basename.
438 : * \li "%*p" -- print out the full program 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 37352 : while(help[0] != '\0')
468 : {
469 18509 : if(help[0] == '%')
470 : {
471 405 : switch(help[1])
472 : {
473 : case '%':
474 13 : result += '%';
475 13 : help += 2;
476 13 : break;
477 :
478 : case '*':
479 98 : switch(help[2])
480 : {
481 : 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 : 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 : 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 : 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 19 : help += 3;
561 : }
562 19 : break;
563 :
564 : case 'p':
565 13 : result += f_program_fullname;
566 13 : help += 3;
567 13 : break;
568 :
569 : }
570 98 : break;
571 :
572 : 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 : 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 : 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 : 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 : 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 : 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 : 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 22 : help += 2;
639 : }
640 22 : break;
641 :
642 : case 'i':
643 19 : if(f_options_environment.f_options_files_directory != nullptr
644 16 : && *f_options_environment.f_options_files_directory != '\0')
645 : {
646 13 : result += f_options_environment.f_options_files_directory;
647 : }
648 : else
649 : {
650 6 : result += "/usr/share/advgetopt/options";
651 : }
652 19 : help += 2;
653 19 : break;
654 :
655 : case 'l':
656 19 : if(f_options_environment.f_license != nullptr)
657 : {
658 16 : result += f_options_environment.f_license;
659 : }
660 19 : help += 2;
661 19 : break;
662 :
663 : case 'o':
664 : {
665 38 : string_list_t const list(get_configuration_filenames(false, true));
666 19 : if(!list.empty())
667 : {
668 13 : result += list.back();
669 : }
670 19 : help += 2;
671 : }
672 19 : break;
673 :
674 : case 'p':
675 32 : result += f_program_name;
676 32 : help += 2;
677 32 : break;
678 :
679 : case 't':
680 19 : if(f_options_environment.f_build_time != nullptr)
681 : {
682 16 : result += f_options_environment.f_build_time;
683 : }
684 19 : help += 2;
685 19 : break;
686 :
687 : case 'v':
688 19 : if(f_options_environment.f_version != nullptr)
689 : {
690 16 : result += f_options_environment.f_version;
691 : }
692 19 : help += 2;
693 19 : break;
694 :
695 : case 'w':
696 : {
697 44 : string_list_t const list(get_configuration_filenames(true, true));
698 22 : bool first(true);
699 31 : for(auto n : list)
700 : {
701 9 : if(first)
702 : {
703 6 : first = false;
704 : }
705 : else
706 : {
707 3 : result += ", ";
708 : }
709 9 : result += n;
710 : }
711 22 : help += 2;
712 : }
713 22 : break;
714 :
715 : }
716 : }
717 : else
718 : {
719 18104 : result += help[0];
720 18104 : ++help;
721 : }
722 : }
723 :
724 334 : return result;
725 : }
726 :
727 :
728 : /** \brief Format a help string to make it fit on a given width.
729 : *
730 : * This function properly wraps a set of help strings so they fit in
731 : * your console. The width has to be given by you at the moment.
732 : *
733 : * The function takes two strings, the argument with it's options
734 : * and the actual help string for that argument. If the argument
735 : * is short enough, it will appear on the first line with the
736 : * first line of help. If not, then one whole line is reserved
737 : * just for the argument and the help starts on the next line.
738 : *
739 : * \param[in] argument The option name with -- and arguments.
740 : * \param[in] help The help string for this argument.
741 : * \param[in] option_width Number of characters reserved for the option.
742 : * \param[in] line_width The maximum number of characters to display in width.
743 : *
744 : * \return A help string formatted for display.
745 : */
746 175 : std::string getopt::format_usage_string(
747 : std::string const & argument
748 : , std::string const & help
749 : , size_t const option_width
750 : , size_t const line_width)
751 : {
752 350 : std::stringstream ss;
753 :
754 175 : ss << " ";
755 :
756 175 : if( argument.size() < option_width - 3 )
757 : {
758 : // enough space on a single line
759 : //
760 145 : ss << argument
761 290 : << std::setw( option_width - 3 - argument.size() )
762 145 : << " ";
763 : }
764 30 : else if(argument.size() >= line_width - 4)
765 : {
766 : // argument too long for even one line on the screen!?
767 : // call the function to break it up with indentation of 3
768 : //
769 1 : ss << breakup_line(argument, 3, line_width);
770 :
771 2 : if(!help.empty()
772 1 : && option_width > 0)
773 : {
774 1 : ss << std::setw( option_width ) << " ";
775 : }
776 : }
777 : else
778 : {
779 : // argument too long for the help to follow immediately
780 : //
781 29 : ss << argument
782 29 : << std::endl
783 58 : << std::setw( option_width )
784 29 : << " ";
785 : }
786 :
787 175 : ss << breakup_line(help, option_width, line_width);
788 :
789 350 : return ss.str();
790 : }
791 :
792 :
793 : /** \brief Breakup a string on multiple lines.
794 : *
795 : * This function breaks up the specified \p line of text in one or more
796 : * strings to fit your output.
797 : *
798 : * The \p line_width represents the maximum number of characters that get
799 : * printed in a row.
800 : *
801 : * The \p option_width parameter is the number of characters in the left
802 : * margin. When dealing with a very long argument, this width is 3 characters.
803 : * When dealing with the help itself, it is expected to be around 30.
804 : *
805 : * \note
806 : * This function always makes sure that the resulting string ends with
807 : * a newline character unless the input \p line string is empty.
808 : *
809 : * \param[in] line The line to breakup.
810 : * \param[in] option_width The number of characters in the left margin.
811 : * \param[in] line_width The total number of characters in the output.
812 : *
813 : * \return The broken up line as required.
814 : */
815 351 : std::string getopt::breakup_line(std::string line
816 : , size_t const option_width
817 : , size_t const line_width)
818 : {
819 702 : std::stringstream ss;
820 :
821 351 : size_t const width(line_width - option_width);
822 :
823 : // TODO: once we have C++17, avoid substr() using std::string_view instead
824 : //
825 1493 : while(line.size() > width)
826 : {
827 1142 : std::string l;
828 571 : std::string::size_type const nl(line.find('\n'));
829 571 : if(nl != std::string::npos
830 398 : && nl < width)
831 : {
832 230 : l = line.substr(0, nl);
833 230 : line = line.substr(nl + 1);
834 : }
835 341 : else if(std::isspace(line[width]))
836 : {
837 : // special case when the space is right at the edge
838 : //
839 23 : l = line.substr(0, width);
840 23 : size_t pos(width);
841 24 : do
842 : {
843 24 : ++pos;
844 : }
845 24 : while(std::isspace(line[pos]));
846 23 : line = line.substr(pos);
847 : }
848 : else
849 : {
850 : // search for the last space before the edge of the screen
851 : //
852 318 : std::string::size_type pos(line.find_last_of(' ', width));
853 318 : if(pos == std::string::npos)
854 : {
855 : // no space found, cut right at the edge...
856 : // (this should be really rare)
857 : //
858 77 : l = line.substr(0, width);
859 77 : line = line.substr(width);
860 : }
861 : else
862 : {
863 : // we found a space, write everything up to that space
864 : //
865 241 : l = line.substr(0, pos);
866 :
867 : // remove additional spaces from the start of the next line
868 241 : do
869 : {
870 241 : ++pos;
871 : }
872 241 : while(std::isspace(line[pos]));
873 241 : line = line.substr(pos);
874 : }
875 : }
876 :
877 571 : ss << l
878 571 : << std::endl;
879 :
880 : // more to print? if so we need the indentation
881 : //
882 1142 : if(!line.empty()
883 571 : && option_width > 0)
884 : {
885 107 : ss << std::setw( option_width ) << " ";
886 : }
887 : }
888 :
889 : // some leftover?
890 : //
891 351 : if(!line.empty())
892 : {
893 351 : ss << line << std::endl;
894 : }
895 :
896 702 : return ss.str();
897 : }
898 :
899 :
900 :
901 : /** \brief Retrieve the width of one line in your console.
902 : *
903 : * This function retrieves the width of the console in number of characters.
904 : *
905 : * If the process is not connected to a TTY, then the function returns 80.
906 : *
907 : * If the width is less than 40, the function returns 40.
908 : *
909 : * \return The width of the console screen.
910 : */
911 79 : size_t getopt::get_line_width()
912 : {
913 79 : std::int64_t cols(80);
914 :
915 79 : if(isatty(STDOUT_FILENO))
916 : {
917 : // when running coverage, the output is redirected for logging purposes
918 : // which means that isatty() returns false -- so at this time I just
919 : // exclude those since they are unreachable from my standard Unit Tests
920 : //
921 : winsize w;
922 : if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) // LCOV_EXCL_LINE
923 : {
924 : cols = std::max(static_cast<unsigned short>(40), w.ws_col); // LCOV_EXCL_LINE
925 : }
926 : }
927 :
928 79 : return cols;
929 : }
930 :
931 :
932 :
933 6 : } // namespace advgetopt
934 : // vim: ts=4 sw=4 et
|