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