LCOV - code coverage report
Current view: top level - advgetopt - utils.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 99 131 75.6 %
Date: 2022-05-26 21:41:34 Functions: 9 10 90.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/not_used.h>
      39             : #include    <snapdev/trim_string.h>
      40             : 
      41             : 
      42             : // cppthread
      43             : //
      44             : #include    <cppthread/guard.h>
      45             : #include    <cppthread/mutex.h>
      46             : 
      47             : 
      48             : // C++ lib
      49             : //
      50             : #include    <set>
      51             : 
      52             : 
      53             : // C
      54             : //
      55             : #include    <string.h>
      56             : 
      57             : 
      58             : // last include
      59             : //
      60             : #include    <snapdev/poison.h>
      61             : 
      62             : 
      63             : 
      64             : namespace advgetopt
      65             : {
      66             : 
      67             : 
      68             : 
      69             : namespace
      70             : {
      71             : 
      72             : 
      73             : 
      74             : /** \brief The configuration file mutex.
      75             :  *
      76             :  * This options are generally viewed as read-only global variables. They
      77             :  * get setup once early on and then used and reused as many times as
      78             :  * required.
      79             :  *
      80             :  * This mutex makes sure that access between multiple thread happens in
      81             :  * a safe manner.
      82             :  */
      83             : cppthread::mutex *      g_mutex;
      84             : 
      85             : 
      86             : 
      87             : }
      88             : // no name namespace
      89             : 
      90             : 
      91             : 
      92             : /** \brief Get a global mutex.
      93             :  *
      94             :  * This function returns a global mutex we can use to lock the advgetopt
      95             :  * whenever multithread functionality is required (i.e. a global is used.)
      96             :  *
      97             :  * It is safe to call this function early (i.e. before main was ever
      98             :  * called.)
      99             :  *
     100             :  * Usage:
     101             :  *
     102             :  * \code
     103             :  *    cppthread::guard lock(get_global_mutex());
     104             :  * \endcode
     105             :  *
     106             :  * \return A reference to our global mutex.
     107             :  */
     108        7977 : cppthread::mutex & get_global_mutex()
     109             : {
     110             :     {
     111       15954 :         cppthread::guard lock(*cppthread::g_system_mutex);
     112             : 
     113        7977 :         if(g_mutex == nullptr)
     114             :         {
     115           1 :             g_mutex = new cppthread::mutex();
     116             :         }
     117             :     }
     118             : 
     119        7977 :     return *g_mutex;
     120             : }
     121             : 
     122             : 
     123             : 
     124             : /** \brief Remove single (') or double (") quotes from a string.
     125             :  *
     126             :  * If a string starts and ends with the same quotation mark, then it
     127             :  * gets removed.
     128             :  *
     129             :  * If no quotes appear, then the function returns a copy of the input as is.
     130             :  *
     131             :  * The \p pairs parameter must have an even size (or the last character
     132             :  * gets ignored). By default, it is set to the double and single quotes:
     133             :  *
     134             :  * \code
     135             :  *     "\"\"''"
     136             :  * \endcode
     137             :  *
     138             :  * To remove square, angle, curly brackets:
     139             :  *
     140             :  * \code
     141             :  *     "[]<>{}"
     142             :  * \endcode
     143             :  *
     144             :  * \todo
     145             :  * Add support for UTF-8 quotes. Right now only quotes of 1 byte will
     146             :  * work.
     147             :  *
     148             :  * \param[in] s  The string to unquote.
     149             :  * \param[in] pairs  A list of accepted quotes.
     150             :  *
     151             :  * \return The unquoted string.
     152             :  */
     153         898 : std::string unquote(std::string const & s, std::string const & pairs)
     154             : {
     155         898 :     if(s.length() >= 2)
     156             :     {
     157         828 :         std::string::size_type const max(pairs.length() - 1);
     158        2356 :         for(std::string::size_type pos(0); pos < max; pos += 2)
     159             :         {
     160        3216 :             if(s.front() == pairs[pos + 0]
     161        1608 :             && s.back()  == pairs[pos + 1])
     162             :             {
     163          80 :                 return s.substr(1, s.length() - 2);
     164             :             }
     165             :         }
     166             :     }
     167             : 
     168         818 :     return s;
     169             : }
     170             : 
     171             : 
     172             : /** \brief Split a string in sub-strings separated by \p separators.
     173             :  *
     174             :  * This function searches for any of the \p separators in \p str and
     175             :  * split at those locations.
     176             :  *
     177             :  * For example, to split a comma separated list of strings, use the
     178             :  * following:
     179             :  *
     180             :  * \code
     181             :  *     string_list_t result;
     182             :  *     option_info::split_string(string_to_split, result, {","});
     183             :  * \endcode
     184             :  *
     185             :  * If `string_to_split` is set to "a, b, c", then the `result` vector
     186             :  * will have three strings as a result: `a`, `b`, and `c`. Note that
     187             :  * the function automatically trims all strings and it never keeps
     188             :  * empty strings. So two separators one after another is accepted and
     189             :  * no empty string results.
     190             :  *
     191             :  * The trimming happens after the split occurs. This allows for the
     192             :  * list of separators to include spaces as separators.
     193             :  *
     194             :  * The function does not clear the result vector. This allows you to
     195             :  * call this function multiple times with various strings and the
     196             :  * results will be cumulated.
     197             :  *
     198             :  * \note
     199             :  * This function is a static so it can be used from anywhere to split
     200             :  * strings as required. You do not need to have an option_info instance.
     201             :  *
     202             :  * \todo
     203             :  * See to fix the fact that `a"b"c` becomes `{"a", "b", "c"}` when
     204             :  * there are not separators between `a`, `"b"`, and `c`. To the minimum
     205             :  * we may want to generate an error when such is found (i.e. when a
     206             :  * quote is found and `start < pos` is true.
     207             :  *
     208             :  * \param[in] str  The string to split.
     209             :  * \param[in] result  The vector where the split strings are saved.
     210             :  * \param[in] separators  The vector of strings used as separators.
     211             :  */
     212         147 : void split_string(std::string const & str
     213             :                 , string_list_t & result
     214             :                 , string_list_t const & separators)
     215             : {
     216         147 :     std::string::size_type pos(0);
     217         147 :     std::string::size_type start(0);
     218       17021 :     while(pos < str.length())
     219             :     {
     220        8437 :         if(str[pos] == '\'' || str[pos] == '"')
     221             :         {
     222          14 :             if(start < pos)
     223             :             {
     224          12 :                 std::string const v(snapdev::trim_string(str.substr(start, pos - start)));
     225           6 :                 if(!v.empty())
     226             :                 {
     227           4 :                     result.push_back(v);
     228             :                 }
     229           6 :                 start = pos;
     230             :             }
     231             : 
     232             :             // quoted parameters are handled without the separators
     233             :             //
     234          14 :             char const quote(str[pos]);
     235          14 :             for(++pos; pos < str.length() && str[pos] != quote; ++pos);
     236             : 
     237          28 :             std::string const v(str.substr(start + 1, pos - (start + 1)));
     238          14 :             if(!v.empty())
     239             :             {
     240          11 :                 result.push_back(v);
     241             :             }
     242          14 :             if(pos < str.length())
     243             :             {
     244             :                 // skip the closing quote
     245             :                 //
     246          12 :                 ++pos;
     247             :             }
     248          14 :             start = pos;
     249             :         }
     250             :         else
     251             :         {
     252        8423 :             bool found(false);
     253       16415 :             for(auto const & sep : separators)
     254             :             {
     255        8440 :                 if(str.length() - pos >= sep.length())
     256             :                 {
     257        8440 :                     if(str.compare(pos, sep.length(), sep) == 0)
     258             :                     {
     259             :                         // match! cut here
     260             :                         //
     261         448 :                         if(start < pos)
     262             :                         {
     263         852 :                             std::string const v(snapdev::trim_string(str.substr(start, pos - start)));
     264         426 :                             if(!v.empty())
     265             :                             {
     266         426 :                                 result.push_back(v);
     267             :                             }
     268             :                         }
     269         448 :                         pos += sep.length();
     270         448 :                         start = pos;
     271         448 :                         found = true;
     272         448 :                         break;
     273             :                     }
     274             :                 }
     275             :             }
     276             : 
     277        8423 :             if(!found)
     278             :             {
     279        7975 :                 ++pos;
     280             :             }
     281             :         }
     282             :     }
     283             : 
     284         147 :     if(start < pos)
     285             :     {
     286         280 :         std::string const v(snapdev::trim_string(str.substr(start, pos - start)));
     287         140 :         if(!v.empty())
     288             :         {
     289         140 :             result.push_back(v);
     290             :         }
     291             :     }
     292         147 : }
     293             : 
     294             : 
     295             : /** \brief Insert the group (or project) name in the filename.
     296             :  *
     297             :  * This function inserts the name of the group in the specified full path
     298             :  * filename. It gets added right before the basename. So for example you
     299             :  * have a path such as:
     300             :  *
     301             :  *     /etc/snapwebsites/advgetopt.conf
     302             :  *
     303             :  * and a group name such as:
     304             :  *
     305             :  *     adventure
     306             :  *
     307             :  * The resulting path is:
     308             :  *
     309             :  *     /etc/snapwebsites/adventure.d/advgetopt.conf
     310             :  *
     311             :  * Notice that the function adds a ".d" as well.
     312             :  *
     313             :  * If the group name is empty or null, then the project name is used. If
     314             :  * both are empty, then nothing happens (the function returns an empty list).
     315             :  *
     316             :  * \exception getopt_root_filename
     317             :  * The \p filename parameter cannot be a file in the root directory.
     318             :  *
     319             :  * \param[in] filename  The filename where the project name gets injected.
     320             :  * \param[in] group_name  The name of the group to inject in the filename.
     321             :  * \param[in] project_name  The name of the project to inject in the filename.
     322             :  *
     323             :  * \return The list of filenames or an empty list if no group or project name
     324             :  *         or filename were specified.
     325             :  */
     326         535 : string_list_t insert_group_name(
     327             :           std::string const & filename
     328             :         , char const * group_name
     329             :         , char const * project_name)
     330             : {
     331         535 :     if(filename.empty())
     332             :     {
     333           5 :         return string_list_t();
     334             :     }
     335             : 
     336        1060 :     std::string name;
     337         530 :     if(group_name == nullptr
     338         162 :     || *group_name == '\0')
     339             :     {
     340         372 :         if(project_name == nullptr
     341         370 :         || *project_name == '\0')
     342             :         {
     343           4 :             return string_list_t();
     344             :         }
     345         368 :         name = project_name;
     346             :     }
     347             :     else
     348             :     {
     349         158 :         name = group_name;
     350             :     }
     351             : 
     352        1052 :     std::string pattern;
     353         526 :     std::string::size_type const pos(filename.find_last_of('/'));
     354         526 :     if(pos == 0)
     355             :     {
     356           0 :         throw getopt_root_filename("filename \"" + filename + "\" starts with a slash (/), which is not allowed.");
     357             :     }
     358         526 :     if(pos != std::string::npos
     359         250 :     && pos > 0)
     360             :     {
     361        1250 :         pattern = filename.substr(0, pos + 1)
     362         750 :                 + name
     363         750 :                 + ".d/[0-9][0-9]-"
     364        1000 :                 + filename.substr(pos + 1);
     365             :     }
     366             :     else
     367             :     {
     368         552 :         pattern = name
     369         552 :                 + (".d/[0-9][0-9]-" + filename);
     370             :     }
     371             : 
     372             :     // we use an std::set so the resulting list is sorted
     373             :     //
     374        1052 :     snapdev::glob_to_list<std::set<std::string>> glob;
     375             : 
     376             :     // the glob() function is not thread safe
     377             :     {
     378        1052 :         cppthread::guard lock(get_global_mutex());
     379         526 :         snapdev::NOT_USED(glob.read_path<snapdev::glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS>(pattern));
     380             :     }
     381             : 
     382             :     // we add the default name if none other exists
     383             :     //
     384         526 :     if(glob.empty())
     385             :     {
     386         504 :         if(pos != std::string::npos
     387         228 :         && pos > 0)
     388             :         {
     389        1140 :             glob.insert(filename.substr(0, pos + 1)
     390         684 :                     + name
     391         684 :                     + ".d/50-"
     392         912 :                     + filename.substr(pos + 1));
     393             :         }
     394             :         else
     395             :         {
     396         552 :             glob.insert(name
     397         552 :                     + (".d/50-" + filename));
     398             :         }
     399             :     }
     400             : 
     401         526 :     return string_list_t(glob.begin(), glob.end());
     402             : }
     403             : 
     404             : 
     405             : /** \brief Generate the default filename (the ".../50-...")
     406             :  *
     407             :  * This function generates the default filename as the insert_group_name()
     408             :  * expects to find in the configuration sub-directory.
     409             :  *
     410             :  * The name is formed as follow:
     411             :  *
     412             :  *     <path> / <directory> ".d" / <priority> "-" <basename>
     413             :  *
     414             :  * Where `<path>` is the path found in \p filename. If no path is defined in
     415             :  * \p filename, then the `<path> /` part is not prepended:
     416             :  *
     417             :  *     <directory> ".d" / <priority> "-" <basename>
     418             :  *
     419             :  * Where `<directory>` is the \p group_name if defined, otherwise it uses
     420             :  * the \p project_name. This is why if neither is defined, then the function
     421             :  * immediately returns an empty string.
     422             :  *
     423             :  * Where `<priority>` is a number from 0 to 99 inclusive. This is used to
     424             :  * sort the files before processing them. File with lower priorities are
     425             :  * loaded first. Parameters found in files with higher priorities overwrite
     426             :  * the values of parameters found in files with lower priorities.
     427             :  *
     428             :  * Where `<basename>` is the end of \p filename, the part after the last
     429             :  * slash (`/`). If \p filename is not empty and it does not include a slash
     430             :  * then the entire \p filename is taken as the `<basename>`. Note that
     431             :  * \p filename is expected to include an extension such as `.conf`. The
     432             :  * extension is not modified in any way.
     433             :  *
     434             :  * Since the result is not viable when \p filename is empty, the function
     435             :  * immediately returns an empty string in that situation.
     436             :  *
     437             :  * \exception getopt_root_filename
     438             :  * The \p filename parameter cannot be a file in the root directory.
     439             :  *
     440             :  * \param[in] filename  The filename where the project name gets injected.
     441             :  * \param[in] group_name  The name of the group to inject in the filename.
     442             :  * \param[in] project_name  The name of the project to inject in the filename.
     443             :  * \param[in] priority  The priority of the new file (0 to 99).
     444             :  *
     445             :  * \return The default filenames or an empty list if no group or project
     446             :  *         or file name were specified.
     447             :  */
     448           0 : std::string default_group_name(
     449             :           std::string const & filename
     450             :         , char const * group_name
     451             :         , char const * project_name
     452             :         , int priority)
     453             : {
     454           0 :     if(priority < 0 || priority >= 100)
     455             :     {
     456             :         throw getopt_invalid_parameter(
     457             :               "priority must be a number between 0 and 99 inclusive; "
     458           0 :             + std::to_string(priority)
     459           0 :             + " is invalid.");
     460             :     }
     461             : 
     462           0 :     if(filename.empty())
     463             :     {
     464           0 :         return std::string();
     465             :     }
     466             : 
     467           0 :     char const * name(nullptr);
     468           0 :     if(group_name == nullptr
     469           0 :     || *group_name == '\0')
     470             :     {
     471           0 :         if(project_name == nullptr
     472           0 :         || *project_name == '\0')
     473             :         {
     474           0 :             return std::string();
     475             :         }
     476           0 :         name = project_name;
     477             :     }
     478             :     else
     479             :     {
     480           0 :         name = group_name;
     481             :     }
     482             : 
     483           0 :     std::string::size_type const pos(filename.find_last_of('/'));
     484           0 :     if(pos == 0)
     485             :     {
     486           0 :         throw getopt_root_filename("filename \"" + filename + "\" starts with a slash (/), which is not allowed.");
     487             :     }
     488             : 
     489           0 :     std::string result;
     490           0 :     result.reserve(filename.length() + strlen(name) + 6);
     491           0 :     if(pos != std::string::npos)
     492             :     {
     493           0 :         result = filename.substr(0, pos + 1);
     494             :     }
     495           0 :     result += name;
     496           0 :     result += ".d/";
     497           0 :     if(priority < 10)
     498             :     {
     499           0 :         result += '0';
     500             :     }
     501           0 :     result += std::to_string(priority);
     502           0 :     result += '-';
     503           0 :     if(pos == std::string::npos)
     504             :     {
     505           0 :         result += filename;
     506             :     }
     507             :     else
     508             :     {
     509           0 :         result += filename.substr(pos + 1);
     510             :     }
     511             : 
     512           0 :     return result;
     513             : }
     514             : 
     515             : 
     516             : /** \brief Replace a starting `~/...` with the contents of the \$HOME variable.
     517             :  *
     518             :  * This function checks the beginning of \p filename. If it starts with `'~/'`
     519             :  * then it replaces the `'~'` character with the contents of the \$HOME
     520             :  * environment variable.
     521             :  *
     522             :  * If \p filename is just `"~"`, then the function returns the contents of
     523             :  * the \$HOME environment variable by itself.
     524             :  *
     525             :  * If somehow the \$HOME environment variable is empty, the function does
     526             :  * nothing.
     527             :  *
     528             :  * \todo
     529             :  * Add support for "~<user name>/..." so that way a service could use its
     530             :  * own home folder even when run from a different user (a.k.a. root). This
     531             :  * requires that we load the user database and get the home folder from that
     532             :  * data.
     533             :  *
     534             :  * \param[in] filename  The filename to check for a tilde (~).
     535             :  *
     536             :  * \return The input as is unless the \$HOME path can be prepended to replace
     537             :  *         the tilde (~) character.
     538             :  */
     539         574 : std::string handle_user_directory(std::string const & filename)
     540             : {
     541        1148 :     if(!filename.empty()
     542         574 :     && filename[0] == '~'
     543         637 :     && (filename.length() == 1 || filename[1] == '/'))
     544             :     {
     545          63 :         char const * const home(getenv("HOME"));
     546          63 :         if(home != nullptr
     547          59 :         && *home != '\0')
     548             :         {
     549          57 :             return home + filename.substr(1);
     550             :         }
     551             :     }
     552             : 
     553         517 :     return filename;
     554             : }
     555             : 
     556             : 
     557             : /** \brief Check whether a value represents "true".
     558             :  *
     559             :  * This function checks a string to see whether it is one of:
     560             :  *
     561             :  * * "true"
     562             :  * * "on"
     563             :  * * "yes"
     564             :  * * "1"
     565             :  *
     566             :  * If so, then the function returns true.
     567             :  *
     568             :  * \param[in] s  The string to be checked.
     569             :  *
     570             :  * \return true if the string represents "true".
     571             :  */
     572           9 : bool is_true(std::string s)
     573             : {
     574           9 :     return s == "true" || s == "on" || s == "yes" | s == "1";
     575             : }
     576             : 
     577             : 
     578             : /** \brief Check whether a value represents "false".
     579             :  *
     580             :  * This function checks a string to see whether it is one of:
     581             :  *
     582             :  * * "false"
     583             :  * * "off"
     584             :  * * "no"
     585             :  * * "0"
     586             :  *
     587             :  * If so, then the function returns true.
     588             :  *
     589             :  * \param[in] s  The string to be checked.
     590             :  *
     591             :  * \return true if the string represents "false".
     592             :  */
     593          11 : bool is_false(std::string s)
     594             : {
     595          11 :     return s == "false" || s == "off" || s == "no" || s == "0";
     596             : }
     597             : 
     598             : 
     599             : 
     600           6 : }   // namespace advgetopt
     601             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13