LCOV - code coverage report
Current view: top level - advgetopt - advgetopt_usage.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 307 307 100.0 %
Date: 2019-07-15 03:11:49 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.12