LCOV - code coverage report
Current view: top level - advgetopt - advgetopt_usage.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 341 357 95.5 %
Date: 2022-05-26 21:41:34 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13