LCOV - code coverage report
Current view: top level - advgetopt - utils.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 124 124 100.0 %
Date: 2022-07-08 21:04:10 Functions: 10 10 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/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++
      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        8553 : cppthread::mutex & get_global_mutex()
     109             : {
     110             :     {
     111       17106 :         cppthread::guard lock(*cppthread::g_system_mutex);
     112             : 
     113        8553 :         if(g_mutex == nullptr)
     114             :         {
     115           1 :             g_mutex = new cppthread::mutex();
     116             :         }
     117             :     }
     118             : 
     119        8553 :     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        1070 : std::string unquote(std::string const & s, std::string const & pairs)
     154             : {
     155        1070 :     if(s.length() >= 2)
     156             :     {
     157         946 :         std::string::size_type const max(pairs.length() - 1);
     158        2707 :         for(std::string::size_type pos(0); pos < max; pos += 2)
     159             :         {
     160        3688 :             if(s.front() == pairs[pos + 0]
     161        1844 :             && s.back()  == pairs[pos + 1])
     162             :             {
     163          83 :                 return s.substr(1, s.length() - 2);
     164             :             }
     165             :         }
     166             :     }
     167             : 
     168         987 :     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         161 : void split_string(std::string const & str
     213             :                 , string_list_t & result
     214             :                 , string_list_t const & separators)
     215             : {
     216         161 :     std::string::size_type pos(0);
     217         161 :     std::string::size_type start(0);
     218       18257 :     while(pos < str.length())
     219             :     {
     220        9048 :         if(str[pos] == '\'' || str[pos] == '"')
     221             :         {
     222           9 :             if(start < pos)
     223             :             {
     224           8 :                 std::string const v(snapdev::trim_string(str.substr(start, pos - start)));
     225           4 :                 if(!v.empty())
     226             :                 {
     227           4 :                     result.push_back(v);
     228             :                 }
     229           4 :                 start = pos;
     230             :             }
     231             : 
     232             :             // quoted parameters are handled without the separators
     233             :             //
     234           9 :             char const quote(str[pos]);
     235           9 :             for(++pos; pos < str.length() && str[pos] != quote; ++pos);
     236             : 
     237          18 :             std::string const v(str.substr(start + 1, pos - (start + 1)));
     238           9 :             if(!v.empty())
     239             :             {
     240           6 :                 result.push_back(v);
     241             :             }
     242           9 :             if(pos < str.length())
     243             :             {
     244             :                 // skip the closing quote
     245             :                 //
     246           7 :                 ++pos;
     247             :             }
     248           9 :             start = pos;
     249             :         }
     250             :         else
     251             :         {
     252        9039 :             bool found(false);
     253       17633 :             for(auto const & sep : separators)
     254             :             {
     255        9056 :                 if(str.length() - pos >= sep.length())
     256             :                 {
     257        9056 :                     if(str.compare(pos, sep.length(), sep) == 0)
     258             :                     {
     259             :                         // match! cut here
     260             :                         //
     261         462 :                         if(start < pos)
     262             :                         {
     263         884 :                             std::string const v(snapdev::trim_string(str.substr(start, pos - start)));
     264         442 :                             if(!v.empty())
     265             :                             {
     266         442 :                                 result.push_back(v);
     267             :                             }
     268             :                         }
     269         462 :                         pos += sep.length();
     270         462 :                         start = pos;
     271         462 :                         found = true;
     272         462 :                         break;
     273             :                     }
     274             :                 }
     275             :             }
     276             : 
     277        9039 :             if(!found)
     278             :             {
     279        8577 :                 ++pos;
     280             :             }
     281             :         }
     282             :     }
     283             : 
     284         161 :     if(start < pos)
     285             :     {
     286         314 :         std::string const v(snapdev::trim_string(str.substr(start, pos - start)));
     287         157 :         if(!v.empty())
     288             :         {
     289         157 :             result.push_back(v);
     290             :         }
     291             :     }
     292         161 : }
     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         536 : string_list_t insert_group_name(
     327             :           std::string const & filename
     328             :         , char const * group_name
     329             :         , char const * project_name)
     330             : {
     331         536 :     if(filename.empty())
     332             :     {
     333           5 :         return string_list_t();
     334             :     }
     335             : 
     336        1062 :     std::string name;
     337         531 :     if(group_name == nullptr
     338         163 :     || *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         159 :         name = group_name;
     350             :     }
     351             : 
     352        1054 :     std::string pattern;
     353         527 :     std::string::size_type const pos(filename.find_last_of('/'));
     354         527 :     if(pos == 0)
     355             :     {
     356           1 :         throw getopt_root_filename("filename \"" + filename + "\" last slash (/) is at the start, 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 :         glob.insert(default_group_name(
     387             :                   filename
     388             :                 , group_name
     389             :                 , project_name));
     390             :     }
     391             : 
     392         526 :     return string_list_t(glob.begin(), glob.end());
     393             : }
     394             : 
     395             : 
     396             : /** \brief Generate the default filename (the ".../50-...")
     397             :  *
     398             :  * This function generates the default filename as the insert_group_name()
     399             :  * expects to find in the configuration sub-directory.
     400             :  *
     401             :  * The name is formed as follow:
     402             :  *
     403             :  *     <path> / <directory> ".d" / <priority> "-" <basename>
     404             :  *
     405             :  * Where `<path>` is the path found in \p filename. If no path is defined in
     406             :  * \p filename, then the `<path> /` part is not prepended:
     407             :  *
     408             :  *     <directory> ".d" / <priority> "-" <basename>
     409             :  *
     410             :  * Where `<directory>` is the \p group_name if defined, otherwise it uses
     411             :  * the \p project_name. This is why if neither is defined, then the function
     412             :  * immediately returns an empty string.
     413             :  *
     414             :  * Where `<priority>` is a number from 0 to 99 inclusive. This is used to
     415             :  * sort the files before processing them. File with lower priorities are
     416             :  * loaded first. Parameters found in files with higher priorities overwrite
     417             :  * the values of parameters found in files with lower priorities.
     418             :  *
     419             :  * Where `<basename>` is the end of \p filename, the part after the last
     420             :  * slash (`/`). If \p filename is not empty and it does not include a slash
     421             :  * then the entire \p filename is taken as the `<basename>`. Note that
     422             :  * \p filename is expected to include an extension such as `.conf`. The
     423             :  * extension is not modified in any way.
     424             :  *
     425             :  * Since the result is not viable when \p filename is empty, the function
     426             :  * immediately returns an empty string in that situation.
     427             :  *
     428             :  * \exception getopt_root_filename
     429             :  * The \p filename parameter cannot be a file in the root directory.
     430             :  *
     431             :  * \param[in] filename  The filename where the project name gets injected.
     432             :  * \param[in] group_name  The name of the group to inject in the filename.
     433             :  * \param[in] project_name  The name of the project to inject in the filename.
     434             :  * \param[in] priority  The priority of the new file (0 to 99).
     435             :  *
     436             :  * \return The default filenames or an empty list if no group or project
     437             :  *         or file name were specified.
     438             :  */
     439         666 : std::string default_group_name(
     440             :           std::string const & filename
     441             :         , char const * group_name
     442             :         , char const * project_name
     443             :         , int priority)
     444             : {
     445         666 :     if(priority < 0 || priority >= 100)
     446             :     {
     447             :         throw getopt_invalid_parameter(
     448             :               "priority must be a number between 0 and 99 inclusive; "
     449          80 :             + std::to_string(priority)
     450         120 :             + " is invalid.");
     451             :     }
     452             : 
     453         626 :     if(filename.empty())
     454             :     {
     455           5 :         return std::string();
     456             :     }
     457             : 
     458         621 :     char const * name(nullptr);
     459         621 :     if(group_name == nullptr
     460         269 :     || *group_name == '\0')
     461             :     {
     462         358 :         if(project_name == nullptr
     463         356 :         || *project_name == '\0')
     464             :         {
     465           4 :             return std::string();
     466             :         }
     467         354 :         name = project_name;
     468             :     }
     469             :     else
     470             :     {
     471         263 :         name = group_name;
     472             :     }
     473             : 
     474         617 :     std::string::size_type const pos(filename.find_last_of('/'));
     475         617 :     if(pos == 0)
     476             :     {
     477           1 :         throw getopt_root_filename("filename \"" + filename + "\" starts with a slash (/), which is not allowed.");
     478             :     }
     479             : 
     480        1232 :     std::string result;
     481         616 :     result.reserve(filename.length() + strlen(name) + 6);
     482         616 :     if(pos != std::string::npos)
     483             :     {
     484         338 :         result = filename.substr(0, pos + 1);
     485             :     }
     486         616 :     result += name;
     487         616 :     result += ".d/";
     488         616 :     if(priority < 10)
     489             :     {
     490          10 :         result += '0';
     491             :     }
     492         616 :     result += std::to_string(priority);
     493         616 :     result += '-';
     494         616 :     if(pos == std::string::npos)
     495             :     {
     496         278 :         result += filename;
     497             :     }
     498             :     else
     499             :     {
     500         338 :         result += filename.substr(pos + 1);
     501             :     }
     502             : 
     503         616 :     return result;
     504             : }
     505             : 
     506             : 
     507             : /** \brief Replace a starting `~/...` with the contents of the \$HOME variable.
     508             :  *
     509             :  * This function checks the beginning of \p filename. If it starts with `'~/'`
     510             :  * then it replaces the `'~'` character with the contents of the \$HOME
     511             :  * environment variable.
     512             :  *
     513             :  * If \p filename is just `"~"`, then the function returns the contents of
     514             :  * the \$HOME environment variable by itself.
     515             :  *
     516             :  * If somehow the \$HOME environment variable is empty, the function does
     517             :  * nothing.
     518             :  *
     519             :  * \todo
     520             :  * Add support for "~<user name>/..." so that way a service could use its
     521             :  * own home folder even when run from a different user (a.k.a. root). This
     522             :  * requires that we load the user database and get the home folder from that
     523             :  * data.
     524             :  *
     525             :  * \param[in] filename  The filename to check for a tilde (~).
     526             :  *
     527             :  * \return The input as is unless the \$HOME path can be prepended to replace
     528             :  *         the tilde (~) character.
     529             :  */
     530         574 : std::string handle_user_directory(std::string const & filename)
     531             : {
     532        1148 :     if(!filename.empty()
     533         574 :     && filename[0] == '~'
     534         637 :     && (filename.length() == 1 || filename[1] == '/'))
     535             :     {
     536          63 :         char const * const home(getenv("HOME"));
     537          63 :         if(home != nullptr
     538          59 :         && *home != '\0')
     539             :         {
     540          57 :             return home + filename.substr(1);
     541             :         }
     542             :     }
     543             : 
     544         517 :     return filename;
     545             : }
     546             : 
     547             : 
     548             : /** \brief Check whether a value represents "true".
     549             :  *
     550             :  * This function checks a string to see whether it is one of:
     551             :  *
     552             :  * * "true"
     553             :  * * "on"
     554             :  * * "yes"
     555             :  * * "1"
     556             :  *
     557             :  * If so, then the function returns true.
     558             :  *
     559             :  * \param[in] s  The string to be checked.
     560             :  *
     561             :  * \return true if the string represents "true".
     562             :  */
     563           9 : bool is_true(std::string s)
     564             : {
     565           9 :     return s == "true" || s == "on" || s == "yes" | s == "1";
     566             : }
     567             : 
     568             : 
     569             : /** \brief Check whether a value represents "false".
     570             :  *
     571             :  * This function checks a string to see whether it is one of:
     572             :  *
     573             :  * * "false"
     574             :  * * "off"
     575             :  * * "no"
     576             :  * * "0"
     577             :  *
     578             :  * If so, then the function returns true.
     579             :  *
     580             :  * \param[in] s  The string to be checked.
     581             :  *
     582             :  * \return true if the string represents "false".
     583             :  */
     584          11 : bool is_false(std::string s)
     585             : {
     586          11 :     return s == "false" || s == "off" || s == "no" || s == "0";
     587             : }
     588             : 
     589             : 
     590             : 
     591           6 : }   // namespace advgetopt
     592             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13