LCOV - code coverage report
Current view: top level - advgetopt - utils.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 247 251 98.4 %
Date: 2024-10-05 13:34:54 Functions: 17 17 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14

Snap C++ | List of projects | List of versions