LCOV - code coverage report
Current view: top level - advgetopt - utils.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 205 234 87.6 %
Date: 2022-07-15 09:02:52 Functions: 16 18 88.9 %
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             : 
      21             : /** \file
      22             :  * \brief Implementation of utility functions.
      23             :  *
      24             :  * This file includes various utility functions that are not specifically
      25             :  * attached to a class.
      26             :  */
      27             : 
      28             : // self
      29             : //
      30             : #include    "advgetopt/utils.h"
      31             : 
      32             : #include    "advgetopt/exception.h"
      33             : 
      34             : 
      35             : // snapdev
      36             : //
      37             : #include    <snapdev/glob_to_list.h>
      38             : #include    <snapdev/isatty.h>
      39             : #include    <snapdev/not_used.h>
      40             : #include    <snapdev/trim_string.h>
      41             : 
      42             : 
      43             : // cppthread
      44             : //
      45             : #include    <cppthread/guard.h>
      46             : #include    <cppthread/mutex.h>
      47             : 
      48             : 
      49             : // C++
      50             : //
      51             : #include    <cstring>
      52             : #include    <iomanip>
      53             : #include    <set>
      54             : #include    <sstream>
      55             : 
      56             : 
      57             : // C
      58             : //
      59             : #include    <sys/ioctl.h>
      60             : #include    <sys/stat.h>
      61             : #include    <unistd.h>
      62             : 
      63             : 
      64             : 
      65             : 
      66             : // last include
      67             : //
      68             : #include    <snapdev/poison.h>
      69             : 
      70             : 
      71             : 
      72             : namespace advgetopt
      73             : {
      74             : 
      75             : 
      76             : 
      77             : namespace
      78             : {
      79             : 
      80             : 
      81             : 
      82             : /** \brief The configuration file mutex.
      83             :  *
      84             :  * This options are generally viewed as read-only global variables. They
      85             :  * get setup once early on and then used and reused as many times as
      86             :  * required.
      87             :  *
      88             :  * This mutex makes sure that access between multiple thread happens in
      89             :  * a safe manner.
      90             :  */
      91             : cppthread::mutex *      g_mutex;
      92             : 
      93             : 
      94             : 
      95             : constexpr char const    g_single_quote = '\'';
      96             : constexpr char const *  g_empty_string = "\"\"";
      97             : constexpr char const *  g_escaped_single_quotes = "'\\''";
      98             : constexpr char const *  g_simple_characters = "+-./0123456789=ABCEFGHIJKLMNOPQRSTUVWXYZabcefghijklmnopqrstuvwxyz_";
      99             : 
     100             : 
     101             : 
     102             : }
     103             : // no name namespace
     104             : 
     105             : 
     106             : 
     107             : /** \brief Get a global mutex.
     108             :  *
     109             :  * This function returns a global mutex we can use to lock the advgetopt
     110             :  * whenever multithread functionality is required (i.e. a global is used.)
     111             :  *
     112             :  * It is safe to call this function early (i.e. before main was ever
     113             :  * called.)
     114             :  *
     115             :  * Usage:
     116             :  *
     117             :  * \code
     118             :  *    cppthread::guard lock(get_global_mutex());
     119             :  * \endcode
     120             :  *
     121             :  * \return A reference to our global mutex.
     122             :  */
     123        8498 : cppthread::mutex & get_global_mutex()
     124             : {
     125             :     {
     126       16996 :         cppthread::guard lock(*cppthread::g_system_mutex);
     127             : 
     128        8498 :         if(g_mutex == nullptr)
     129             :         {
     130           1 :             g_mutex = new cppthread::mutex();
     131             :         }
     132             :     }
     133             : 
     134        8498 :     return *g_mutex;
     135             : }
     136             : 
     137             : 
     138             : 
     139             : /** \brief Remove single (') or double (") quotes from a string.
     140             :  *
     141             :  * If a string starts and ends with the same quotation mark, then it
     142             :  * gets removed.
     143             :  *
     144             :  * If no quotes appear, then the function returns a copy of the input as is.
     145             :  *
     146             :  * The \p pairs parameter must have an even size (or the last character
     147             :  * gets ignored). By default, it is set to the double and single quotes:
     148             :  *
     149             :  * \code
     150             :  *     "\"\"''"
     151             :  * \endcode
     152             :  *
     153             :  * To remove square, angle, curly brackets:
     154             :  *
     155             :  * \code
     156             :  *     "[]<>{}"
     157             :  * \endcode
     158             :  *
     159             :  * \todo
     160             :  * Add support for UTF-8 quotes. Right now only quotes of 1 byte will
     161             :  * work.
     162             :  *
     163             :  * \param[in] s  The string to unquote.
     164             :  * \param[in] pairs  A list of accepted quotes.
     165             :  *
     166             :  * \return The unquoted string.
     167             :  */
     168        1059 : std::string unquote(std::string const & s, std::string const & pairs)
     169             : {
     170        1059 :     if(s.length() >= 2)
     171             :     {
     172         935 :         std::string::size_type const max(pairs.length() - 1);
     173        2696 :         for(std::string::size_type pos(0); pos < max; pos += 2)
     174             :         {
     175        3666 :             if(s.front() == pairs[pos + 0]
     176        1833 :             && s.back()  == pairs[pos + 1])
     177             :             {
     178          72 :                 return s.substr(1, s.length() - 2);
     179             :             }
     180             :         }
     181             :     }
     182             : 
     183         987 :     return s;
     184             : }
     185             : 
     186             : 
     187             : /** \brief The converse of unquote.
     188             :  *
     189             :  * This function adds quotes around a string.
     190             :  *
     191             :  * \param[in] s  The string to be quoted.
     192             :  * \param[in] q  The quotes to use to quote this string.
     193             :  *
     194             :  * \return The input string quoted with \p quote.
     195             :  */
     196           0 : std::string quote(std::string s, char q)
     197             : {
     198           0 :     std::string result;
     199             : 
     200           0 :     result += q;
     201           0 :     for(auto const c : s)
     202             :     {
     203           0 :         if(c == q)
     204             :         {
     205           0 :             result += '\\';
     206             :         }
     207           0 :         result += c;
     208             :     }
     209           0 :     result += q;
     210             : 
     211           0 :     return result;
     212             : }
     213             : 
     214             : 
     215             : /** \brief Split a string in sub-strings separated by \p separators.
     216             :  *
     217             :  * This function searches for any of the \p separators in \p str and
     218             :  * split at those locations.
     219             :  *
     220             :  * For example, to split a comma separated list of strings, use the
     221             :  * following:
     222             :  *
     223             :  * \code
     224             :  *     string_list_t result;
     225             :  *     option_info::split_string(string_to_split, result, {","});
     226             :  * \endcode
     227             :  *
     228             :  * If `string_to_split` is set to "a, b, c", then the `result` vector
     229             :  * will have three strings as a result: `a`, `b`, and `c`. Note that
     230             :  * the function automatically trims all strings and it never keeps
     231             :  * empty strings. So two separators one after another is accepted and
     232             :  * no empty string results.
     233             :  *
     234             :  * The trimming happens after the split occurs. This allows for the
     235             :  * list of separators to include spaces as separators.
     236             :  *
     237             :  * The function does not clear the result vector. This allows you to
     238             :  * call this function multiple times with various strings and the
     239             :  * results will be cumulated.
     240             :  *
     241             :  * \note
     242             :  * This function is a static so it can be used from anywhere to split
     243             :  * strings as required. You do not need to have an option_info instance.
     244             :  *
     245             :  * \todo
     246             :  * See to fix the fact that `a"b"c` becomes `{"a", "b", "c"}` when
     247             :  * there are not separators between `a`, `"b"`, and `c`. To the minimum
     248             :  * we may want to generate an error when such is found (i.e. when a
     249             :  * quote is found and `start < pos` is true.
     250             :  *
     251             :  * \param[in] str  The string to split.
     252             :  * \param[in] result  The vector where the split strings are saved.
     253             :  * \param[in] separators  The vector of strings used as separators.
     254             :  */
     255         201 : void split_string(std::string const & str
     256             :                 , string_list_t & result
     257             :                 , string_list_t const & separators)
     258             : {
     259         201 :     std::string::size_type pos(0);
     260         201 :     std::string::size_type start(0);
     261       19385 :     while(pos < str.length())
     262             :     {
     263        9592 :         if(str[pos] == '\'' || str[pos] == '"')
     264             :         {
     265           9 :             if(start < pos)
     266             :             {
     267           8 :                 std::string const v(snapdev::trim_string(str.substr(start, pos - start)));
     268           4 :                 if(!v.empty())
     269             :                 {
     270           4 :                     result.push_back(v);
     271             :                 }
     272           4 :                 start = pos;
     273             :             }
     274             : 
     275             :             // quoted parameters are handled without the separators
     276             :             //
     277           9 :             char const quote(str[pos]);
     278           9 :             for(++pos; pos < str.length() && str[pos] != quote; ++pos);
     279             : 
     280          18 :             std::string const v(str.substr(start + 1, pos - (start + 1)));
     281           9 :             if(!v.empty())
     282             :             {
     283           6 :                 result.push_back(v);
     284             :             }
     285           9 :             if(pos < str.length())
     286             :             {
     287             :                 // skip the closing quote
     288             :                 //
     289           7 :                 ++pos;
     290             :             }
     291           9 :             start = pos;
     292             :         }
     293             :         else
     294             :         {
     295        9583 :             bool found(false);
     296       18578 :             for(auto const & sep : separators)
     297             :             {
     298        9600 :                 if(str.length() - pos >= sep.length())
     299             :                 {
     300        9600 :                     if(str.compare(pos, sep.length(), sep) == 0)
     301             :                     {
     302             :                         // match! cut here
     303             :                         //
     304         605 :                         if(start < pos)
     305             :                         {
     306        1170 :                             std::string const v(snapdev::trim_string(str.substr(start, pos - start)));
     307         585 :                             if(!v.empty())
     308             :                             {
     309         585 :                                 result.push_back(v);
     310             :                             }
     311             :                         }
     312         605 :                         pos += sep.length();
     313         605 :                         start = pos;
     314         605 :                         found = true;
     315         605 :                         break;
     316             :                     }
     317             :                 }
     318             :             }
     319             : 
     320        9583 :             if(!found)
     321             :             {
     322        8978 :                 ++pos;
     323             :             }
     324             :         }
     325             :     }
     326             : 
     327         201 :     if(start < pos)
     328             :     {
     329         394 :         std::string const v(snapdev::trim_string(str.substr(start, pos - start)));
     330         197 :         if(!v.empty())
     331             :         {
     332         197 :             result.push_back(v);
     333             :         }
     334             :     }
     335         201 : }
     336             : 
     337             : 
     338             : /** \brief Insert the group (or project) name in the filename.
     339             :  *
     340             :  * This function inserts the name of the group in the specified full path
     341             :  * filename. It gets added right before the basename. So for example you
     342             :  * have a path such as:
     343             :  *
     344             :  *     /etc/snapwebsites/advgetopt.conf
     345             :  *
     346             :  * and a group name such as:
     347             :  *
     348             :  *     adventure
     349             :  *
     350             :  * The resulting path is:
     351             :  *
     352             :  *     /etc/snapwebsites/adventure.d/advgetopt.conf
     353             :  *
     354             :  * Notice that the function adds a ".d" as well.
     355             :  *
     356             :  * If the group name is empty or null, then the project name is used. If
     357             :  * both are empty, then nothing happens (the function returns an empty list).
     358             :  *
     359             :  * \exception getopt_root_filename
     360             :  * The \p filename parameter cannot be a file in the root directory.
     361             :  *
     362             :  * \param[in] filename  The filename where the project name gets injected.
     363             :  * \param[in] group_name  The name of the group to inject in the filename.
     364             :  * \param[in] project_name  The name of the project to inject in the filename.
     365             :  *
     366             :  * \return The list of filenames or an empty list if no group or project name
     367             :  *         or filename were specified.
     368             :  */
     369         536 : string_list_t insert_group_name(
     370             :           std::string const & filename
     371             :         , char const * group_name
     372             :         , char const * project_name)
     373             : {
     374         536 :     if(filename.empty())
     375             :     {
     376           5 :         return string_list_t();
     377             :     }
     378             : 
     379        1062 :     std::string name;
     380         531 :     if(group_name == nullptr
     381         163 :     || *group_name == '\0')
     382             :     {
     383         372 :         if(project_name == nullptr
     384         370 :         || *project_name == '\0')
     385             :         {
     386           4 :             return string_list_t();
     387             :         }
     388         368 :         name = project_name;
     389             :     }
     390             :     else
     391             :     {
     392         159 :         name = group_name;
     393             :     }
     394             : 
     395        1054 :     std::string pattern;
     396         527 :     std::string::size_type const pos(filename.find_last_of('/'));
     397         527 :     if(pos == 0)
     398             :     {
     399           1 :         throw getopt_root_filename("filename \"" + filename + "\" last slash (/) is at the start, which is not allowed.");
     400             :     }
     401         526 :     if(pos != std::string::npos
     402         250 :     && pos > 0)
     403             :     {
     404        1250 :         pattern = filename.substr(0, pos + 1)
     405         750 :                 + name
     406         750 :                 + ".d/[0-9][0-9]-"
     407        1000 :                 + filename.substr(pos + 1);
     408             :     }
     409             :     else
     410             :     {
     411         552 :         pattern = name
     412         552 :                 + (".d/[0-9][0-9]-" + filename);
     413             :     }
     414             : 
     415             :     // we use an std::set so the resulting list is sorted
     416             :     //
     417        1052 :     snapdev::glob_to_list<std::set<std::string>> glob;
     418             : 
     419             :     // the glob() function is not thread safe
     420             :     {
     421        1052 :         cppthread::guard lock(get_global_mutex());
     422         526 :         snapdev::NOT_USED(glob.read_path<snapdev::glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS>(pattern));
     423             :     }
     424             : 
     425             :     // we add the default name if none other exists
     426             :     //
     427         526 :     if(glob.empty())
     428             :     {
     429         504 :         glob.insert(default_group_name(
     430             :                   filename
     431             :                 , group_name
     432             :                 , project_name));
     433             :     }
     434             : 
     435         526 :     return string_list_t(glob.begin(), glob.end());
     436             : }
     437             : 
     438             : 
     439             : /** \brief Generate the default filename (the ".../50-...")
     440             :  *
     441             :  * This function generates the default filename as the insert_group_name()
     442             :  * expects to find in the configuration sub-directory.
     443             :  *
     444             :  * The name is formed as follow:
     445             :  *
     446             :  *     <path> / <directory> ".d" / <priority> "-" <basename>
     447             :  *
     448             :  * Where `<path>` is the path found in \p filename. If no path is defined in
     449             :  * \p filename, then the `<path> /` part is not prepended:
     450             :  *
     451             :  *     <directory> ".d" / <priority> "-" <basename>
     452             :  *
     453             :  * Where `<directory>` is the \p group_name if defined, otherwise it uses
     454             :  * the \p project_name. This is why if neither is defined, then the function
     455             :  * immediately returns an empty string.
     456             :  *
     457             :  * Where `<priority>` is a number from 0 to 99 inclusive. This is used to
     458             :  * sort the files before processing them. File with lower priorities are
     459             :  * loaded first. Parameters found in files with higher priorities overwrite
     460             :  * the values of parameters found in files with lower priorities.
     461             :  *
     462             :  * Where `<basename>` is the end of \p filename, the part after the last
     463             :  * slash (`/`). If \p filename is not empty and it does not include a slash
     464             :  * then the entire \p filename is taken as the `<basename>`. Note that
     465             :  * \p filename is expected to include an extension such as `.conf`. The
     466             :  * extension is not modified in any way.
     467             :  *
     468             :  * Since the result is not viable when \p filename is empty, the function
     469             :  * immediately returns an empty string in that situation.
     470             :  *
     471             :  * \exception getopt_root_filename
     472             :  * The \p filename parameter cannot be a file in the root directory.
     473             :  *
     474             :  * \param[in] filename  The filename where the project name gets injected.
     475             :  * \param[in] group_name  The name of the group to inject in the filename.
     476             :  * \param[in] project_name  The name of the project to inject in the filename.
     477             :  * \param[in] priority  The priority of the new file (0 to 99).
     478             :  *
     479             :  * \return The default filenames or an empty list if no group or project
     480             :  *         or file name were specified.
     481             :  */
     482         666 : std::string default_group_name(
     483             :           std::string const & filename
     484             :         , char const * group_name
     485             :         , char const * project_name
     486             :         , int priority)
     487             : {
     488         666 :     if(priority < 0 || priority >= 100)
     489             :     {
     490             :         throw getopt_invalid_parameter(
     491             :               "priority must be a number between 0 and 99 inclusive; "
     492          80 :             + std::to_string(priority)
     493         120 :             + " is invalid.");
     494             :     }
     495             : 
     496         626 :     if(filename.empty())
     497             :     {
     498           5 :         return std::string();
     499             :     }
     500             : 
     501         621 :     char const * name(nullptr);
     502         621 :     if(group_name == nullptr
     503         269 :     || *group_name == '\0')
     504             :     {
     505         358 :         if(project_name == nullptr
     506         356 :         || *project_name == '\0')
     507             :         {
     508           4 :             return std::string();
     509             :         }
     510         354 :         name = project_name;
     511             :     }
     512             :     else
     513             :     {
     514         263 :         name = group_name;
     515             :     }
     516             : 
     517         617 :     std::string::size_type const pos(filename.find_last_of('/'));
     518         617 :     if(pos == 0)
     519             :     {
     520           1 :         throw getopt_root_filename("filename \"" + filename + "\" starts with a slash (/), which is not allowed.");
     521             :     }
     522             : 
     523        1232 :     std::string result;
     524         616 :     result.reserve(filename.length() + strlen(name) + 6);
     525         616 :     if(pos != std::string::npos)
     526             :     {
     527         338 :         result = filename.substr(0, pos + 1);
     528             :     }
     529         616 :     result += name;
     530         616 :     result += ".d/";
     531         616 :     if(priority < 10)
     532             :     {
     533          10 :         result += '0';
     534             :     }
     535         616 :     result += std::to_string(priority);
     536         616 :     result += '-';
     537         616 :     if(pos == std::string::npos)
     538             :     {
     539         278 :         result += filename;
     540             :     }
     541             :     else
     542             :     {
     543         338 :         result += filename.substr(pos + 1);
     544             :     }
     545             : 
     546         616 :     return result;
     547             : }
     548             : 
     549             : 
     550             : /** \brief Replace a starting `~/...` with the contents of the \$HOME variable.
     551             :  *
     552             :  * This function checks the beginning of \p filename. If it starts with `'~/'`
     553             :  * then it replaces the `'~'` character with the contents of the \$HOME
     554             :  * environment variable.
     555             :  *
     556             :  * If \p filename is just `"~"`, then the function returns the contents of
     557             :  * the \$HOME environment variable by itself.
     558             :  *
     559             :  * If somehow the \$HOME environment variable is empty, the function does
     560             :  * nothing.
     561             :  *
     562             :  * \todo
     563             :  * Add support for "~<user name>/..." so that way a service could use its
     564             :  * own home folder even when run from a different user (a.k.a. root). This
     565             :  * requires that we load the user database and get the home folder from that
     566             :  * data.
     567             :  *
     568             :  * \param[in] filename  The filename to check for a tilde (~).
     569             :  *
     570             :  * \return The input as is unless the \$HOME path can be prepended to replace
     571             :  *         the tilde (~) character.
     572             :  */
     573         574 : std::string handle_user_directory(std::string const & filename)
     574             : {
     575        1148 :     if(!filename.empty()
     576         574 :     && filename[0] == '~'
     577         637 :     && (filename.length() == 1 || filename[1] == '/'))
     578             :     {
     579          63 :         char const * const home(getenv("HOME"));
     580          63 :         if(home != nullptr
     581          59 :         && *home != '\0')
     582             :         {
     583          57 :             return home + filename.substr(1);
     584             :         }
     585             :     }
     586             : 
     587         517 :     return filename;
     588             : }
     589             : 
     590             : 
     591             : /** \brief Check whether a value represents "true".
     592             :  *
     593             :  * This function checks a string to see whether it is one of:
     594             :  *
     595             :  * * "true"
     596             :  * * "on"
     597             :  * * "yes"
     598             :  * * "1"
     599             :  *
     600             :  * If so, then the function returns true.
     601             :  *
     602             :  * \param[in] s  The string to be checked.
     603             :  *
     604             :  * \return true if the string represents "true".
     605             :  */
     606           9 : bool is_true(std::string s)
     607             : {
     608           9 :     return s == "true" || s == "on" || s == "yes" | s == "1";
     609             : }
     610             : 
     611             : 
     612             : /** \brief Check whether a value represents "false".
     613             :  *
     614             :  * This function checks a string to see whether it is one of:
     615             :  *
     616             :  * * "false"
     617             :  * * "off"
     618             :  * * "no"
     619             :  * * "0"
     620             :  *
     621             :  * If so, then the function returns true.
     622             :  *
     623             :  * \param[in] s  The string to be checked.
     624             :  *
     625             :  * \return true if the string represents "false".
     626             :  */
     627          11 : bool is_false(std::string s)
     628             : {
     629          11 :     return s == "false" || s == "off" || s == "no" || s == "0";
     630             : }
     631             : 
     632             : 
     633             : /** \brief Retrieve the width of one line in your console.
     634             :  *
     635             :  * This function retrieves the width of the console in number of characters.
     636             :  *
     637             :  * If the process is not connected to a TTY, then the function returns 80.
     638             :  *
     639             :  * If the width is less than 40, the function returns 40.
     640             :  *
     641             :  * \return The width of the console screen.
     642             :  */
     643         291 : size_t get_screen_width()
     644             : {
     645         291 :     std::int64_t cols(80);
     646             : 
     647         291 :     if(isatty(STDOUT_FILENO))
     648             :     {
     649             :         // when running coverage, the output is redirected for logging purposes
     650             :         // which means that isatty() returns false -- so at this time I just
     651             :         // exclude those since they are unreachable from my standard Unit Tests
     652             :         //
     653             :         winsize w;                                                          // LCOV_EXCL_LINE
     654             :         if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1)                      // LCOV_EXCL_LINE
     655             :         {
     656             :             cols = std::max(static_cast<unsigned short>(40), w.ws_col);     // LCOV_EXCL_LINE
     657             :         }
     658             :     }
     659             : 
     660         291 :     return cols;
     661             : }
     662             : 
     663             : 
     664             : /** \brief Breakup a string on multiple lines.
     665             :  *
     666             :  * This function breaks up the specified \p line of text in one or more
     667             :  * strings to fit your output.
     668             :  *
     669             :  * The \p line_width represents the maximum number of characters that get
     670             :  * printed in a row.
     671             :  *
     672             :  * The \p option_width parameter is the number of characters in the left
     673             :  * margin. When dealing with a very long argument, this width is 3 characters.
     674             :  * When dealing with the help itself, it is expected to be around 30.
     675             :  *
     676             :  * \note
     677             :  * This function always makes sure that the resulting string ends with
     678             :  * a newline character unless the input \p line string is empty.
     679             :  *
     680             :  * \param[in] line  The line to breakup.
     681             :  * \param[in] option_width  The number of characters in the left margin.
     682             :  * \param[in] line_width  The total number of characters in the output.
     683             :  *
     684             :  * \return The broken up line as required.
     685             :  */
     686         562 : std::string breakup_line(
     687             :       std::string line
     688             :     , size_t const option_width
     689             :     , size_t const line_width)
     690             : {
     691        1124 :     std::stringstream ss;
     692             : 
     693         562 :     size_t const width(line_width - option_width);
     694             : 
     695             :     // TODO: once we have C++17, avoid substr() using std::string_view instead
     696             :     //
     697             :     for(;;)
     698             :     {
     699        2022 :         std::string l;
     700        1292 :         std::string::size_type const nl(line.find('\n'));
     701        1292 :         if(nl != std::string::npos
     702         456 :         && nl < width)
     703             :         {
     704         288 :             l = line.substr(0, nl);
     705         288 :             line = line.substr(nl + 1);
     706             :         }
     707        1004 :         else if(line.size() <= width)
     708             :         {
     709         562 :             break;
     710             :         }
     711         442 :         else if(std::isspace(line[width]))
     712             :         {
     713             :             // special case when the space is right at the edge
     714             :             //
     715          38 :             l = line.substr(0, width);
     716          38 :             size_t pos(width);
     717           2 :             do
     718             :             {
     719          40 :                 ++pos;
     720             :             }
     721          40 :             while(std::isspace(line[pos]));
     722          38 :             line = line.substr(pos);
     723             :         }
     724             :         else
     725             :         {
     726             :             // search for the last space before the edge of the screen
     727             :             //
     728         404 :             std::string::size_type pos(line.find_last_of(' ', width));
     729         404 :             if(pos == std::string::npos)
     730             :             {
     731             :                 // no space found, cut right at the edge...
     732             :                 // (this should be really rare)
     733             :                 //
     734          78 :                 l = line.substr(0, width);
     735          78 :                 line = line.substr(width);
     736             :             }
     737             :             else
     738             :             {
     739             :                 // we found a space, write everything up to that space
     740             :                 //
     741         326 :                 l = line.substr(0, pos);
     742             : 
     743             :                 // remove additional spaces from the start of the next line
     744             :                 do  // LCOV_EXCL_LINE
     745             :                 {
     746         326 :                     ++pos;
     747             :                 }
     748         326 :                 while(std::isspace(line[pos]));
     749         326 :                 line = line.substr(pos);
     750             :             }
     751             :         }
     752             : 
     753         730 :         ss << l
     754         730 :            << std::endl;
     755             : 
     756             :         // more to print? if so we need the indentation
     757             :         //
     758        1460 :         if(!line.empty()
     759         730 :         && option_width > 0)
     760             :         {
     761         202 :             ss << std::setw(option_width) << " ";
     762             :         }
     763         730 :     }
     764             : 
     765             :     // some leftover?
     766             :     //
     767         562 :     if(!line.empty())
     768             :     {
     769         550 :         ss << line << std::endl;
     770             :     }
     771             : 
     772        1124 :     return ss.str();
     773             : }
     774             : 
     775             : 
     776             : /** \brief Format a help string to make it fit on a given width.
     777             :  *
     778             :  * This function properly wraps a set of help strings so they fit in
     779             :  * your console. The width has to be given by you at the moment.
     780             :  *
     781             :  * The function takes two strings, the argument with it's options
     782             :  * and the actual help string for that argument. If the argument
     783             :  * is short enough, it will appear on the first line with the
     784             :  * first line of help. If not, then one whole line is reserved
     785             :  * just for the argument and the help starts on the next line.
     786             :  *
     787             :  * \param[in] argument  The option name with -- and arguments.
     788             :  * \param[in] help  The help string for this argument.
     789             :  * \param[in] option_width  Number of characters reserved for the option.
     790             :  * \param[in] line_width  The maximum number of characters to display in width.
     791             :  *
     792             :  * \return A help string formatted for display.
     793             :  */
     794         326 : std::string format_usage_string(
     795             :                       std::string const & argument
     796             :                     , std::string const & help
     797             :                     , size_t const option_width
     798             :                     , size_t const line_width)
     799             : {
     800         652 :     std::stringstream ss;
     801             : 
     802         326 :     ss << "   ";
     803             : 
     804         326 :     if(argument.size() < option_width - 3)
     805             :     {
     806             :         // enough space on a single line
     807             :         //
     808             :         ss << argument
     809         266 :            << std::setw(option_width - 3 - argument.size())
     810         532 :            << " ";
     811             :     }
     812          60 :     else if(argument.size() >= line_width - 4)
     813             :     {
     814             :         // argument too long for even one line on the screen!?
     815             :         // call the function to break it up with indentation of 3
     816             :         //
     817           2 :         ss << breakup_line(argument, 3, line_width);
     818             : 
     819           4 :         if(!help.empty()
     820           2 :         && option_width > 0)
     821             :         {
     822           2 :             ss << std::setw(option_width) << " ";
     823             :         }
     824             :     }
     825             :     else
     826             :     {
     827             :         // argument too long for the help to follow immediately
     828             :         //
     829          58 :         ss << argument
     830          58 :            << std::endl
     831             :            << std::setw(option_width)
     832          58 :            << " ";
     833             :     }
     834             : 
     835         326 :     ss << breakup_line(help, option_width, line_width);
     836             : 
     837         652 :     return ss.str();
     838             : }
     839             : 
     840             : 
     841             : /** \brief Escape special characters from a shell argument.
     842             :  *
     843             :  * This function goes through the supplied argument. If it includes one
     844             :  * or more character other than `[-+0-9A-Za-z_]`, then it gets \em escaped.
     845             :  * This means we add single quotes at the start and end, and escape any
     846             :  * single quote within the argument.
     847             :  *
     848             :  * So the function may return the input string as is.
     849             :  *
     850             :  * \param[in] arg  The argument to escape.
     851             :  *
     852             :  * \return The escaped argument.
     853             :  */
     854         110 : std::string escape_shell_argument(std::string const & arg)
     855             : {
     856         110 :     if(arg.empty())
     857             :     {
     858           1 :         return std::string(g_empty_string);
     859             :     }
     860             : 
     861         109 :     std::string::size_type const pos(arg.find_first_not_of(g_simple_characters));
     862         109 :     if(pos == std::string::npos)
     863             :     {
     864         106 :         return arg;
     865             :     }
     866             : 
     867           6 :     std::string result;
     868             : 
     869           3 :     result += g_single_quote;
     870           3 :     std::string::size_type p1(0);
     871           9 :     while(p1 < arg.length())
     872             :     {
     873           5 :         std::string::size_type const p2(arg.find('\'', p1));
     874           5 :         if(p2 == std::string::npos)
     875             :         {
     876           2 :             result += arg.substr(p1);
     877           2 :             break;
     878             :         }
     879           3 :         result += arg.substr(p1, p2 - p1);
     880           3 :         result += g_escaped_single_quotes;
     881           3 :         p1 = p2 + 1;                            // skip the '
     882             :     }
     883           3 :     result += g_single_quote;
     884             : 
     885           3 :     return result;
     886             : }
     887             : 
     888             : 
     889             : /** \brief Generate a string describing whether we're using the sanitizer.
     890             :  *
     891             :  * This function determines whether this library was compiled with the
     892             :  * sanitizer extension. If so, then it will return detail about which
     893             :  * feature was compiled in.
     894             :  *
     895             :  * If no sanitizer options were compiled in, then it returns a
     896             :  * message saying so.
     897             :  *
     898             :  * \return A string with details about the sanitizer.
     899             :  */
     900           2 : std::string sanitizer_details()
     901             : {
     902           2 :     std::string result;
     903             : #if defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__)
     904             : #if defined(__SANITIZE_ADDRESS__)
     905           2 :     result += "The address sanitizer is compiled in.\n";
     906             : #endif
     907             : #if defined(__SANITIZE_THREAD__)
     908             :     result += "The thread sanitizer is compiled in.\n";
     909             : #endif
     910             : #else
     911             :     result += "The address and thread sanitizers are not compiled in.\n";
     912             : #endif
     913           2 :     return result;
     914             : }
     915             : 
     916             : 
     917             : /** \brief Retrieve the height of your console.
     918             :  *
     919             :  * This function retrieves the height of the console in number of characters.
     920             :  * This is also called the number of rows.
     921             :  *
     922             :  * If the process is not connected to a TTY, then the function returns 25.
     923             :  *
     924             :  * If the height is less than 2, the function returns 2.
     925             :  *
     926             :  * \note
     927             :  * The get_screen_width() and get_screen_height() should be combined.
     928             :  *
     929             :  * \return The width of the console screen.
     930             :  */
     931           0 : size_t get_screen_height()
     932             : {
     933           0 :     std::int64_t rows(25);
     934             : 
     935           0 :     if(isatty(STDOUT_FILENO))
     936             :     {
     937             :         // when running coverage, the output is redirected for logging purposes
     938             :         // which means that isatty() returns false -- so at this time I just
     939             :         // exclude those since they are unreachable from my standard Unit Tests
     940             :         //
     941             :         winsize w;                                                          // LCOV_EXCL_LINE
     942             :         if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1)                      // LCOV_EXCL_LINE
     943             :         {
     944             :             rows = std::max(static_cast<unsigned short>(2), w.ws_row);      // LCOV_EXCL_LINE
     945             :         }
     946             :     }
     947             : 
     948           0 :     return rows;
     949             : }
     950             : 
     951             : 
     952             : /** \brief Print out a string to the console or use less.
     953             :  *
     954             :  * If the \p data string to be output is too large for the screen (too
     955             :  * many lines; we assume the width was already "fixed") then use less
     956             :  * to show the data. If less is not available, use more. If neither
     957             :  * is available, fall back to printing everything at once.
     958             :  *
     959             :  * \param[in,out] out  The output stream where the data has to be written.
     960             :  * \param[in] data  The data to be written to stream.
     961             :  */
     962           9 : void less(std::basic_ostream<char> & out, std::string const & data)
     963             : {
     964           9 :     if(snapdev::isatty(out))
     965             :     {
     966           0 :         auto const lines(std::count(data.begin(), data.end(), '\n'));
     967           0 :         size_t const height(get_screen_height());
     968           0 :         if(lines > static_cast<std::remove_const_t<decltype(lines)>>(height))
     969             :         {
     970           0 :             struct stat s;
     971           0 :             if(stat("/bin/less", &s) == 0)
     972             :             {
     973           0 :                 FILE * f(popen("/bin/less", "w"));
     974           0 :                 if(f != nullptr)
     975             :                 {
     976           0 :                     fwrite(data.c_str(), sizeof(char), data.length(), f);
     977           0 :                     pclose(f);
     978           0 :                     return;
     979             :                 }
     980             :             }
     981           0 :             else if(stat("/bin/more", &s) == 0)
     982             :             {
     983           0 :                 FILE * f(popen("/bin/more", "w"));
     984           0 :                 if(f != nullptr)
     985             :                 {
     986           0 :                     fwrite(data.c_str(), sizeof(char), data.length(), f);
     987           0 :                     pclose(f);
     988           0 :                     return;
     989             :                 }
     990             :             }
     991             :         }
     992             :     }
     993             : 
     994             :     // fallback, just print everything to the console as is
     995             :     //
     996           9 :     out << data << std::endl;
     997             : }
     998             : 
     999             : 
    1000             : 
    1001           6 : }   // namespace advgetopt
    1002             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13