LCOV - code coverage report
Current view: top level - advgetopt - advgetopt_usage.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 339 340 99.7 %
Date: 2021-08-20 21:57:12 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13