Line data Source code
1 : // Copyright (c) 2006-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/advgetopt
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : /** \file
21 : * \brief Advanced getopt usage() implementation.
22 : *
23 : * The advgetopt class usage() and helper functions are grouped in this
24 : * file.
25 : */
26 :
27 : // self
28 : //
29 : #include "advgetopt/advgetopt.h"
30 :
31 : #include "advgetopt/exception.h"
32 :
33 :
34 : // C++
35 : //
36 : #include <iomanip>
37 : #include <iostream>
38 :
39 :
40 : // C
41 : //
42 : //#include <unistd.h>
43 : //#include <sys/ioctl.h>
44 :
45 :
46 : // last include
47 : //
48 : #include <snapdev/poison.h>
49 :
50 :
51 :
52 :
53 : namespace advgetopt
54 : {
55 :
56 :
57 :
58 : /** \brief Transform group names in --\<name>-help commands.
59 : *
60 : * This function allows for the group names to be transformed into help
61 : * command line options.
62 : */
63 355 : void getopt::parse_options_from_group_names()
64 : {
65 : // add the --long-help if at least one option uses the GROUP1 or GROUP2
66 : //
67 785 : for(auto it(f_options_by_name.begin())
68 785 : ; it != f_options_by_name.end()
69 : ; ++it)
70 : {
71 438 : if(it->second->has_flag(GETOPT_FLAG_SHOW_GROUP1 | GETOPT_FLAG_SHOW_GROUP2))
72 : {
73 16 : option_info::pointer_t opt(std::make_shared<option_info>("long-help"));
74 8 : opt->add_flag(GETOPT_FLAG_COMMAND_LINE
75 : | GETOPT_FLAG_FLAG
76 : | GETOPT_FLAG_GROUP_COMMANDS);
77 8 : opt->set_help("show all the help from all the available options.");
78 8 : f_options_by_name["long-help"] = opt;
79 8 : if(f_options_by_short_name.find(L'?') == f_options_by_short_name.end())
80 : {
81 8 : opt->set_short_name(L'?');
82 8 : f_options_by_short_name[L'?'] = opt;
83 : }
84 8 : break;
85 : }
86 : }
87 :
88 355 : if(f_options_environment.f_groups == nullptr)
89 : {
90 : // no groups, ignore following loop
91 : //
92 321 : return;
93 : }
94 :
95 102 : for(group_description const * grp = f_options_environment.f_groups
96 102 : ; grp->f_group != GETOPT_FLAG_GROUP_NONE
97 : ; ++grp)
98 : {
99 : // the name is not mandatory, without it you do not get the command
100 : // line option but still get the group description
101 : //
102 68 : if(grp->f_name != nullptr
103 6 : && *grp->f_name != '\0')
104 : {
105 12 : std::string const name(grp->f_name);
106 12 : std::string const option_name(name + "-help");
107 12 : option_info::pointer_t opt(std::make_shared<option_info>(option_name));
108 6 : opt->add_flag(GETOPT_FLAG_COMMAND_LINE
109 : | GETOPT_FLAG_FLAG
110 : | GETOPT_FLAG_GROUP_COMMANDS);
111 12 : opt->set_help("show help from the \""
112 12 : + name
113 18 : + "\" group of options.");
114 6 : f_options_by_name[option_name] = opt;
115 : }
116 : }
117 : }
118 :
119 :
120 : /** \brief Search for \p group in the list of group names.
121 : *
122 : * This function is used to search for the name of a group.
123 : *
124 : * Groups are used by the usage() function to list options by some user
125 : * selected group.
126 : *
127 : * For example, it is often that a tool has a set of commands such as
128 : * `--delete` and a set of options such as `--verbose`. These can represent
129 : * to clear groups of commands and options.
130 : *
131 : * \param[in] group The group to look for (i.e. GETOPT_FLAG_GROUP_ONE).
132 : *
133 : * \return The group structure or nullptr when not found.
134 : */
135 45 : group_description const * getopt::find_group(flag_t group) const
136 : {
137 45 : if(f_options_environment.f_groups == nullptr)
138 : {
139 2 : return nullptr;
140 : }
141 :
142 43 : if((group & ~GETOPT_FLAG_GROUP_MASK) != 0)
143 : {
144 29 : throw getopt_logic_error("group parameter must represent a valid group.");
145 : }
146 14 : if(group == GETOPT_FLAG_GROUP_NONE)
147 : {
148 1 : throw getopt_logic_error("group NONE cannot be assigned a name so you cannot search for it.");
149 : }
150 :
151 21 : for(group_description const * grp(f_options_environment.f_groups)
152 21 : ; grp->f_group != GETOPT_FLAG_GROUP_NONE
153 : ; ++grp)
154 : {
155 20 : if(group == grp->f_group)
156 : {
157 12 : if((grp->f_name == nullptr || *grp->f_name == '\0')
158 2 : && (grp->f_description == nullptr || *grp->f_description == '\0'))
159 : {
160 2 : throw getopt_logic_error("at least one of a group name or description must be defined (a non-empty string).");
161 : }
162 10 : return grp;
163 : }
164 : }
165 :
166 : // group not defined
167 : //
168 1 : return nullptr;
169 : }
170 :
171 :
172 : /** \brief Create a string of the command line arguments.
173 : *
174 : * This function assembles the command line arguments in a string and
175 : * returns that string.
176 : *
177 : * The function has the ability to wrap strings around for better formatting.
178 : *
179 : * The list of arguments to show is defined by the \p show parameter. When
180 : * \p show is 0, then only the regular and error arguments are shown.
181 : * Otherwise only the argumenst with the specified flags are show. Only
182 : * the `..._SHOW_...` flags are valid here.
183 : *
184 : * When an error occurs, it is customary to set \p show to
185 : * GETOPT_FLAG_SHOW_USAGE_ON_ERROR so only a limited set of arguments
186 : * are shown.
187 : *
188 : * The library offers two groups in case you have a command line tools
189 : * with a large number of options, those two can be used to only show
190 : * those specific set of options with using a specific `--help` argument.
191 : *
192 : * \note
193 : * This function does NOT print anything in the output. This is your
194 : * responsibility. We do it this way because you may be using a logger
195 : * and not want to print the usage in the \em wrong destination.
196 : *
197 : * \bug
198 : * The options are written from our map. This means the order will be
199 : * alphabetical and not the order in which you defined the options.
200 : * We are not looking into fixing this problem. That's just something
201 : * you want to keep in mind.
202 : *
203 : * \param[in] show Selection of the options to show.
204 : *
205 : * \return The assembled command line arguments.
206 : */
207 80 : std::string getopt::usage(flag_t show) const
208 : {
209 160 : std::stringstream ss;
210 :
211 80 : flag_t specific_group(show & GETOPT_FLAG_GROUP_MASK);
212 :
213 : // ignore all the non-show flags
214 : //
215 80 : show &= GETOPT_FLAG_SHOW_USAGE_ON_ERROR
216 : | GETOPT_FLAG_SHOW_ALL
217 : | GETOPT_FLAG_SHOW_GROUP1
218 : | GETOPT_FLAG_SHOW_GROUP2;
219 :
220 80 : size_t const line_width(get_screen_width());
221 80 : ss << breakup_line(process_help_string(f_options_environment.f_help_header), 0, line_width);
222 :
223 160 : std::string save_default;
224 160 : std::string save_help;
225 :
226 80 : flag_t pos(GETOPT_FLAG_GROUP_MINIMUM);
227 80 : flag_t group_max(GETOPT_FLAG_GROUP_MAXIMUM);
228 80 : if(f_options_environment.f_groups == nullptr)
229 : {
230 73 : group_max = GETOPT_FLAG_GROUP_MINIMUM;
231 73 : specific_group = GETOPT_FLAG_GROUP_NONE;
232 : }
233 7 : else if(specific_group != GETOPT_FLAG_GROUP_NONE)
234 : {
235 : // only display that specific group if asked to do so
236 : //
237 2 : pos = specific_group >> GETOPT_FLAG_GROUP_SHIFT;
238 2 : group_max = pos;
239 : }
240 :
241 310 : for(; pos <= group_max; ++pos)
242 : {
243 115 : bool group_name_shown(false);
244 115 : flag_t const group(pos << GETOPT_FLAG_GROUP_SHIFT);
245 891 : for(auto const & opt : f_options_by_name)
246 : {
247 1552 : if((opt.second->get_flags() & GETOPT_FLAG_GROUP_MASK) != group
248 776 : && f_options_environment.f_groups != nullptr)
249 : {
250 : // this could be optimized but we'd probably not see much
251 : // difference overall and it's just for the usage() call
252 : //
253 507 : continue;
254 : }
255 :
256 452 : std::string const help(opt.second->get_help());
257 269 : if(help.empty())
258 : {
259 : // ignore entries without help
260 : //
261 10 : continue;
262 : }
263 :
264 259 : if(opt.second->has_flag(GETOPT_FLAG_ALIAS))
265 : {
266 : // ignore entries representing an alias
267 : //
268 12 : continue;
269 : }
270 :
271 247 : if((show & GETOPT_FLAG_SHOW_ALL) == 0)
272 : {
273 186 : if(show != 0)
274 : {
275 66 : if(!opt.second->has_flag(show))
276 : {
277 : // usage selected group is not present in this option, ignore
278 : //
279 58 : continue;
280 : }
281 : }
282 120 : else if(opt.second->has_flag(GETOPT_FLAG_SHOW_GROUP1 | GETOPT_FLAG_SHOW_GROUP2))
283 : {
284 : // do not show specialized groups
285 : //
286 6 : continue;
287 : }
288 : }
289 :
290 183 : if(!group_name_shown)
291 : {
292 85 : group_name_shown = true;
293 :
294 85 : if(group != GETOPT_FLAG_GROUP_NONE)
295 : {
296 8 : group_description const * grp(find_group(group));
297 8 : if(grp != nullptr)
298 : {
299 8 : ss << std::endl
300 8 : << breakup_line(process_help_string(grp->f_description), 0, line_width);
301 : }
302 : }
303 : }
304 :
305 366 : std::stringstream argument;
306 :
307 183 : if(opt.second->is_default_option())
308 : {
309 6 : switch(opt.second->get_flags() & (GETOPT_FLAG_REQUIRED | GETOPT_FLAG_MULTIPLE))
310 : {
311 1 : case 0:
312 1 : argument << "[default argument]";
313 1 : break;
314 :
315 1 : case GETOPT_FLAG_REQUIRED:
316 1 : argument << "<default argument>";
317 1 : break;
318 :
319 2 : case GETOPT_FLAG_MULTIPLE:
320 2 : argument << "[default arguments]";
321 2 : break;
322 :
323 2 : case GETOPT_FLAG_REQUIRED | GETOPT_FLAG_MULTIPLE:
324 2 : argument << "<default arguments>";
325 2 : break;
326 :
327 : }
328 : }
329 : else
330 : {
331 177 : argument << "--" << opt.second->get_name();
332 177 : if(opt.second->get_short_name() != NO_SHORT_NAME)
333 : {
334 49 : argument << " or -" << short_name_to_string(opt.second->get_short_name());
335 : }
336 :
337 177 : switch(opt.second->get_flags() & (GETOPT_FLAG_FLAG | GETOPT_FLAG_REQUIRED | GETOPT_FLAG_MULTIPLE))
338 : {
339 1 : case 0:
340 1 : argument << " [<arg>]";
341 1 : break;
342 :
343 36 : case GETOPT_FLAG_REQUIRED:
344 36 : argument << " <arg>";
345 36 : break;
346 :
347 8 : case GETOPT_FLAG_MULTIPLE:
348 8 : argument << " {<arg>}";
349 8 : break;
350 :
351 6 : case GETOPT_FLAG_REQUIRED | GETOPT_FLAG_MULTIPLE:
352 6 : argument << " <arg> {<arg>}";
353 6 : break;
354 :
355 : }
356 : }
357 :
358 183 : if(opt.second->has_default())
359 : {
360 : argument << " (default is \""
361 9 : << opt.second->get_default()
362 18 : << "\")";
363 : }
364 :
365 : // Output argument string with help
366 : //
367 183 : if(opt.second->is_default_option())
368 : {
369 6 : save_default = argument.str();
370 6 : save_help = help;
371 : }
372 : else
373 : {
374 354 : std::string variable_name;
375 177 : if(!opt.second->get_environment_variable_name().empty())
376 : {
377 2 : variable_name += "\nEnvironment Variable Name: \"";
378 2 : if(f_options_environment.f_environment_variable_intro != nullptr)
379 : {
380 2 : variable_name += f_options_environment.f_environment_variable_intro;
381 : }
382 2 : variable_name += opt.second->get_environment_variable_name();
383 2 : variable_name += '"';
384 : }
385 354 : ss << format_usage_string(argument.str()
386 354 : , process_help_string((help + variable_name).c_str())
387 : , 30
388 : , line_width);
389 : }
390 : }
391 : }
392 :
393 80 : if(!save_default.empty())
394 : {
395 12 : ss << format_usage_string(save_default
396 12 : , process_help_string(save_help.c_str())
397 : , 30
398 : , line_width);
399 : }
400 :
401 80 : if(f_options_environment.f_help_footer != nullptr
402 78 : && f_options_environment.f_help_footer[0] != '\0')
403 : {
404 78 : ss << std::endl;
405 78 : ss << breakup_line(process_help_string(f_options_environment.f_help_footer), 0, line_width);
406 : }
407 :
408 160 : return ss.str();
409 : }
410 :
411 :
412 : /** \brief Change the % flags in help strings.
413 : *
414 : * This function goes through the help string and replaces the `%\<flag>`
415 : * with various content available in the getopt object.
416 : *
417 : * This is helpful for various reasons. For example, you may use the
418 : * same set of options in several different programs, in which case the
419 : * `%p` is likely useful to print out the name of the program currently
420 : * in use.
421 : *
422 : * Similarly we offer ways to print out lists of configuration files,
423 : * the environment variable name & value, etc. The following is the
424 : * list of supported flags:
425 : *
426 : * \li "%%" -- print out a percent
427 : * \li "%a" -- print out the project name (a.k.a. application name)
428 : * \li "%b" -- print out the build date
429 : * \li "%c" -- print out the copyright notice
430 : * \li "%d" -- print out the first directory with configuration files.
431 : * \li "%*d" -- print out the complete list of directories with configuration
432 : * files.
433 : * \li "%e" -- print out the name of the environment variable.
434 : * \li "%*e" -- print out the name and value of the environment variable.
435 : * \li "%f" -- print out the first configuration path and filename.
436 : * \li "%*f" -- print out all the configuration full paths.
437 : * \li "%g" -- print out the list of existing configuration files.
438 : * \li "%*g" -- print out the list of all possible configuration files.
439 : * \li "%i" -- print out the directory to option files.
440 : * \li "%l" -- print out the license.
441 : * \li "%o" -- show the configuration filename where changes get written.
442 : * \li "%p" -- print out the program basename.
443 : * \li "%*p" -- print out the full program name.
444 : * \li "%s" -- print out the group name.
445 : * \li "%t" -- print out the build time.
446 : * \li "%v" -- print out the version.
447 : * \li "%w" -- print out the list of all the writable configuration files.
448 : *
449 : * Here is an example where the `%p` can be used:
450 : *
451 : * \code
452 : * "Usage: %p [-opt] filename ..."
453 : * \endcode
454 : *
455 : * The other flags are more often used in places like the copyright notice
456 : * the footer, the license notice, etc.
457 : *
458 : * \param[in] help A string that may include `%` flags.
459 : *
460 : * \return The string with any '%\<flag>' replaced.
461 : *
462 : * \sa parse_program_name()
463 : */
464 350 : std::string getopt::process_help_string(char const * help) const
465 : {
466 350 : if(help == nullptr)
467 : {
468 1 : return std::string();
469 : }
470 :
471 698 : std::string result;
472 :
473 41227 : while(help[0] != '\0')
474 : {
475 20439 : if(help[0] == '%')
476 : {
477 433 : switch(help[1])
478 : {
479 13 : case '%':
480 13 : result += '%';
481 13 : help += 2;
482 13 : break;
483 :
484 98 : case '*':
485 98 : switch(help[2])
486 : {
487 19 : case 'd':
488 19 : if(f_options_environment.f_configuration_directories != nullptr)
489 : {
490 16 : bool first(true);
491 68 : for(char const * const * directories(f_options_environment.f_configuration_directories)
492 68 : ; *directories != nullptr
493 : ; ++directories)
494 : {
495 52 : if(first)
496 : {
497 13 : first = false;
498 : }
499 : else
500 : {
501 39 : result += ", ";
502 : }
503 52 : result += *directories;
504 : }
505 : }
506 19 : help += 3;
507 19 : break;
508 :
509 28 : case 'e':
510 28 : if(f_options_environment.f_environment_variable_name != nullptr
511 22 : && *f_options_environment.f_environment_variable_name != '\0')
512 : {
513 16 : result += f_options_environment.f_environment_variable_name;
514 16 : char const * env(getenv(f_options_environment.f_environment_variable_name));
515 16 : if(env != nullptr)
516 : {
517 8 : result += '=';
518 8 : result += env;
519 : }
520 : else
521 : {
522 8 : result += " (not set)";
523 : }
524 : }
525 28 : help += 3;
526 28 : break;
527 :
528 19 : case 'f':
529 19 : if(f_options_environment.f_configuration_files != nullptr)
530 : {
531 16 : bool first(true);
532 68 : for(char const * const * filenames(f_options_environment.f_configuration_files)
533 68 : ; *filenames != nullptr
534 : ; ++filenames)
535 : {
536 52 : if(first)
537 : {
538 13 : first = false;
539 : }
540 : else
541 : {
542 39 : result += ", ";
543 : }
544 52 : result += *filenames;
545 : }
546 : }
547 19 : help += 3;
548 19 : break;
549 :
550 19 : case 'g':
551 : {
552 38 : string_list_t list(get_configuration_filenames(false, false));
553 19 : bool first(true);
554 193 : for(auto n : list)
555 : {
556 174 : if(first)
557 : {
558 13 : first = false;
559 : }
560 : else
561 : {
562 161 : result += ", ";
563 : }
564 174 : result += n;
565 : }
566 38 : help += 3;
567 : }
568 19 : break;
569 :
570 13 : case 'p':
571 13 : result += f_program_fullname;
572 13 : help += 3;
573 13 : break;
574 :
575 : }
576 98 : break;
577 :
578 19 : case 'a':
579 19 : if(f_options_environment.f_project_name != nullptr)
580 : {
581 16 : result += f_options_environment.f_project_name;
582 : }
583 19 : help += 2;
584 19 : break;
585 :
586 19 : case 'b':
587 19 : if(f_options_environment.f_build_date != nullptr)
588 : {
589 16 : result += f_options_environment.f_build_date;
590 : }
591 19 : help += 2;
592 19 : break;
593 :
594 19 : case 'c':
595 19 : if(f_options_environment.f_copyright != nullptr)
596 : {
597 16 : result += f_options_environment.f_copyright;
598 : }
599 19 : help += 2;
600 19 : break;
601 :
602 19 : case 'd':
603 19 : if(f_options_environment.f_configuration_directories != nullptr
604 16 : && *f_options_environment.f_configuration_directories != nullptr)
605 : {
606 13 : result += *f_options_environment.f_configuration_directories;
607 : }
608 19 : help += 2;
609 19 : break;
610 :
611 28 : case 'e':
612 28 : if(f_options_environment.f_environment_variable_name != nullptr)
613 : {
614 22 : result += f_options_environment.f_environment_variable_name;
615 : }
616 28 : help += 2;
617 28 : break;
618 :
619 13 : case 'E':
620 13 : if(f_options_environment.f_environment_variable_intro != nullptr)
621 : {
622 10 : result += f_options_environment.f_environment_variable_intro;
623 : }
624 13 : help += 2;
625 13 : break;
626 :
627 19 : case 'f':
628 19 : if(f_options_environment.f_configuration_files != nullptr
629 16 : && *f_options_environment.f_configuration_files != nullptr)
630 : {
631 13 : result += *f_options_environment.f_configuration_files;
632 : }
633 19 : help += 2;
634 19 : break;
635 :
636 22 : case 'g':
637 : {
638 44 : string_list_t list(get_configuration_filenames(true, false));
639 22 : bool first(true);
640 34 : for(auto n : list)
641 : {
642 12 : if(first)
643 : {
644 6 : first = false;
645 : }
646 : else
647 : {
648 6 : result += ", ";
649 : }
650 12 : result += n;
651 : }
652 44 : help += 2;
653 : }
654 22 : break;
655 :
656 19 : case 'i':
657 : // in the advgetopt_options.cpp, we clearly add a final "/"
658 : // so we want to add it here too, to be consistent
659 : {
660 38 : std::string directory("/usr/share/advgetopt/options/");
661 19 : if(f_options_environment.f_options_files_directory != nullptr
662 16 : && *f_options_environment.f_options_files_directory != '\0')
663 : {
664 13 : directory = f_options_environment.f_options_files_directory;
665 13 : if(directory.back() != '/')
666 : {
667 13 : directory += '/';
668 : }
669 : }
670 38 : result += directory;
671 : }
672 19 : help += 2;
673 19 : break;
674 :
675 19 : case 'l':
676 19 : if(f_options_environment.f_license != nullptr)
677 : {
678 16 : result += f_options_environment.f_license;
679 : }
680 19 : help += 2;
681 19 : break;
682 :
683 10 : case 'm':
684 10 : if(f_options_environment.f_section_variables_name != nullptr)
685 : {
686 5 : result += f_options_environment.f_section_variables_name;
687 : }
688 10 : help += 2;
689 10 : break;
690 :
691 19 : case 'o':
692 : {
693 38 : string_list_t const list(get_configuration_filenames(false, true));
694 19 : if(!list.empty())
695 : {
696 13 : result += list.back();
697 : }
698 38 : help += 2;
699 : }
700 19 : break;
701 :
702 32 : case 'p':
703 32 : result += f_program_name;
704 32 : help += 2;
705 32 : break;
706 :
707 5 : case 's':
708 5 : if(f_options_environment.f_group_name != nullptr)
709 : {
710 5 : result += f_options_environment.f_group_name;
711 : }
712 5 : help += 2;
713 5 : break;
714 :
715 19 : case 't':
716 19 : if(f_options_environment.f_build_time != nullptr)
717 : {
718 16 : result += f_options_environment.f_build_time;
719 : }
720 19 : help += 2;
721 19 : break;
722 :
723 19 : case 'v':
724 19 : if(f_options_environment.f_version != nullptr)
725 : {
726 16 : result += f_options_environment.f_version;
727 : }
728 19 : help += 2;
729 19 : break;
730 :
731 22 : case 'w':
732 : {
733 44 : string_list_t const list(get_configuration_filenames(true, true));
734 22 : bool first(true);
735 31 : for(auto n : list)
736 : {
737 9 : if(first)
738 : {
739 6 : first = false;
740 : }
741 : else
742 : {
743 3 : result += ", ";
744 : }
745 9 : result += n;
746 : }
747 44 : help += 2;
748 : }
749 22 : break;
750 :
751 : }
752 : }
753 : else
754 : {
755 20006 : result += help[0];
756 20006 : ++help;
757 : }
758 : }
759 :
760 349 : return result;
761 : }
762 :
763 :
764 :
765 :
766 6 : } // namespace advgetopt
767 : // vim: ts=4 sw=4 et
|