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

Generated by: LCOV version 1.13