LCOV - code coverage report
Current view: top level - advgetopt - utils.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 83 83 100.0 %
Date: 2019-09-16 03:06:47 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * License:
       3             :  *    Copyright (c) 2006-2019  Made to Order Software Corp.  All Rights Reserved
       4             :  *
       5             :  *    https://snapwebsites.org/
       6             :  *    contact@m2osw.com
       7             :  *
       8             :  *    This program is free software; you can redistribute it and/or modify
       9             :  *    it under the terms of the GNU General Public License as published by
      10             :  *    the Free Software Foundation; either version 2 of the License, or
      11             :  *    (at your option) any later version.
      12             :  *
      13             :  *    This program is distributed in the hope that it will be useful,
      14             :  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
      15             :  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16             :  *    GNU General Public License for more details.
      17             :  *
      18             :  *    You should have received a copy of the GNU General Public License along
      19             :  *    with this program; if not, write to the Free Software Foundation, Inc.,
      20             :  *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      21             :  *
      22             :  * Authors:
      23             :  *    Alexis Wilke   alexis@m2osw.com
      24             :  *    Doug Barbieri  doug@m2osw.com
      25             :  */
      26             : 
      27             : 
      28             : /** \file
      29             :  * \brief Implementation of utility functions.
      30             :  *
      31             :  * This file includes various utility functions that are not specifically
      32             :  * attached to a class.
      33             :  */
      34             : 
      35             : // self
      36             : //
      37             : #include "advgetopt/utils.h"
      38             : 
      39             : 
      40             : // snapdev lib
      41             : //
      42             : #include <snapdev/glob_to_list.h>
      43             : #include <snapdev/not_used.h>
      44             : 
      45             : 
      46             : // boost lib
      47             : //
      48             : #include <boost/algorithm/string/trim.hpp>
      49             : 
      50             : 
      51             : // last include
      52             : //
      53             : #include <snapdev/poison.h>
      54             : 
      55             : 
      56             : 
      57             : namespace advgetopt
      58             : {
      59             : 
      60             : 
      61             : 
      62             : /** \brief Remove single (') or double (") quotes from a string.
      63             :  *
      64             :  * If a string starts and ends with the same quotation mark, then it
      65             :  * gets removed.
      66             :  *
      67             :  * If no quotes appear, then the function returns a copy of the input as is.
      68             :  *
      69             :  * The \p pairs parameter must have an even size (or the last character
      70             :  * gets ignored). By default, it is set to the double and single quotes:
      71             :  *
      72             :  * \code
      73             :  *     "\"\"''"
      74             :  * \endcode
      75             :  *
      76             :  * To remove square, angle, curly brackets:
      77             :  *
      78             :  * \code
      79             :  *     "[]<>{}"
      80             :  * \endcode
      81             :  *
      82             :  * \todo
      83             :  * Add support for UTF-8 quotes. Right now only quotes of 1 byte will
      84             :  * work.
      85             :  *
      86             :  * \param[in] s  The string to unquote.
      87             :  * \param[in] pairs  A list of accepted quotes.
      88             :  *
      89             :  * \return The unquoted string.
      90             :  */
      91         174 : std::string unquote(std::string const & s, std::string const & pairs)
      92             : {
      93         174 :     if(s.length() >= 2)
      94             :     {
      95         138 :         std::string::size_type const max(pairs.length() - 1);
      96         398 :         for(std::string::size_type pos(0); pos < max; pos += 2)
      97             :         {
      98         582 :             if(s.front() == pairs[pos + 0]
      99         291 :             && s.back()  == pairs[pos + 1])
     100             :             {
     101          31 :                 return s.substr(1, s.length() - 2);
     102             :             }
     103             :         }
     104             :     }
     105             : 
     106         143 :     return s;
     107             : }
     108             : 
     109             : 
     110             : /** \brief Split a string in sub-strings separated by \p separators.
     111             :  *
     112             :  * This function searches for any of the \p separators in \p str and
     113             :  * split at those locations.
     114             :  *
     115             :  * For example, to split a comma separated list of strings, use the
     116             :  * following:
     117             :  *
     118             :  * \code
     119             :  *     string_list_t result;
     120             :  *     option_info::split_string(string_to_split, result, {","});
     121             :  * \endcode
     122             :  *
     123             :  * If `string_to_split` is set to "a, b, c", then the `result` vector
     124             :  * will have three strings as a result: `a`, `b`, and `c`. Note that
     125             :  * the function automatically trims all strings and it never keeps
     126             :  * empty strings. So two separators one after another is accepted and
     127             :  * no empty string results.
     128             :  *
     129             :  * The trimming happens after the split occurs. This allows for the
     130             :  * list of separators to include spaces as separators.
     131             :  *
     132             :  * The function does not clear the result vector. This allows you to
     133             :  * call this function multiple times with various strings and the
     134             :  * results will be cumulated.
     135             :  *
     136             :  * \note
     137             :  * This function is a static so it can be used from anywhere to split
     138             :  * strings as required. You do not need to have an option_info instance.
     139             :  *
     140             :  * \todo
     141             :  * See to fix the fact that `a"b"c` becomes `{"a", "b", "c"}` when
     142             :  * there are not separators between `a`, `"b"`, and `c`. To the minimum
     143             :  * we may want to generate an error when such is found (i.e. when a
     144             :  * quote is found and `start < pos` is true.
     145             :  *
     146             :  * \param[in] str  The string to split.
     147             :  * \param[in] result  The vector where the split strings are saved.
     148             :  * \param[in] separators  The vector of strings used as separators.
     149             :  */
     150          91 : void split_string(std::string const & str
     151             :                 , string_list_t & result
     152             :                 , string_list_t const & separators)
     153             : {
     154          91 :     std::string::size_type pos(0);
     155          91 :     std::string::size_type start(0);
     156       11399 :     while(pos < str.length())
     157             :     {
     158        5654 :         if(str[pos] == '\'' || str[pos] == '"')
     159             :         {
     160          14 :             if(start < pos)
     161             :             {
     162          12 :                 std::string v(str.substr(start, pos - start));
     163           6 :                 boost::trim(v);
     164           6 :                 if(!v.empty())
     165             :                 {
     166           4 :                     result.push_back(v);
     167             :                 }
     168           6 :                 start = pos;
     169             :             }
     170             : 
     171             :             // quoted parameters are handled without the separators
     172             :             //
     173          14 :             char const quote(str[pos]);
     174          14 :             for(++pos; pos < str.length() && str[pos] != quote; ++pos);
     175             : 
     176          28 :             std::string const v(str.substr(start + 1, pos - (start + 1)));
     177          14 :             if(!v.empty())
     178             :             {
     179          11 :                 result.push_back(v);
     180             :             }
     181          14 :             if(pos < str.length())
     182             :             {
     183             :                 // skip the closing quote
     184             :                 //
     185          12 :                 ++pos;
     186             :             }
     187          14 :             start = pos;
     188             :         }
     189             :         else
     190             :         {
     191        5640 :             bool found(false);
     192       11365 :             for(auto const & sep : separators)
     193             :             {
     194        5990 :                 if(str.length() - pos >= sep.length())
     195             :                 {
     196        5990 :                     if(str.compare(pos, sep.length(), sep) == 0)
     197             :                     {
     198             :                         // match! cut here
     199             :                         //
     200         265 :                         if(start < pos)
     201             :                         {
     202         486 :                             std::string v(str.substr(start, pos - start));
     203         243 :                             boost::trim(v);
     204         243 :                             if(!v.empty())
     205             :                             {
     206         243 :                                 result.push_back(v);
     207             :                             }
     208             :                         }
     209         265 :                         pos += sep.length();
     210         265 :                         start = pos;
     211         265 :                         found = true;
     212         265 :                         break;
     213             :                     }
     214             :                 }
     215             :             }
     216             : 
     217        5640 :             if(!found)
     218             :             {
     219        5375 :                 ++pos;
     220             :             }
     221             :         }
     222             :     }
     223             : 
     224          91 :     if(start < pos)
     225             :     {
     226         168 :         std::string v(str.substr(start, pos - start));
     227          84 :         boost::trim(v);
     228          84 :         if(!v.empty())
     229             :         {
     230          84 :             result.push_back(v);
     231             :         }
     232             :     }
     233          91 : }
     234             : 
     235             : 
     236             : /** \brief Insert the project name in the filename.
     237             :  *
     238             :  * This function inserts the name of the project in the specified full path
     239             :  * filename. It gets added right before the basename. So for example you
     240             :  * have a path such as:
     241             :  *
     242             :  *     /etc/snapwebsites/advgetopt.conf
     243             :  *
     244             :  * and a project name such as:
     245             :  *
     246             :  *     adventure
     247             :  *
     248             :  * The resulting path is:
     249             :  *
     250             :  *     /etc/snapwebsites/adventure.d/advgetopt.conf
     251             :  *
     252             :  * Notice that the function adds a ".d" as well.
     253             :  *
     254             :  * \param[in] filename  The filename where the project name gets injected.
     255             :  * \param[in] project_name  The name of the project to inject in the filename.
     256             :  *
     257             :  * \return The new filename or an empty string if no project name or filename
     258             :  *         are specified.
     259             :  */
     260         509 : string_list_t insert_project_name(
     261             :           std::string const & filename
     262             :         , char const * project_name)
     263             : {
     264         509 :     if(project_name == nullptr
     265         508 :     || *project_name == '\0'
     266        1016 :     || filename.empty())
     267             :     {
     268           3 :         return string_list_t();
     269             :     }
     270             : 
     271        1012 :     std::string pattern;
     272             : 
     273         506 :     std::string::size_type const pos(filename.find_last_of('/'));
     274         506 :     if(pos != std::string::npos
     275         230 :     && pos > 0)
     276             :     {
     277         460 :         pattern = filename.substr(0, pos + 1)
     278         460 :                 + project_name
     279         460 :                 + ".d/[0-9][0-9]-"
     280         690 :                 + filename.substr(pos + 1);
     281             :     }
     282             :     else
     283             :     {
     284             :         pattern = project_name
     285         276 :                 + (".d/[0-9][0-9]-" + filename);
     286             :     }
     287             : 
     288        1012 :     snap::glob_to_list<string_list_t> glob;
     289         506 :     snap::NOTUSED(glob.read_path<snap::glob_to_list_flag_t::GLOB_FLAG_IGNORE_ERRORS>(pattern));
     290             : 
     291             :     // we add the default name if none other exists
     292             :     //
     293         506 :     if(glob.empty())
     294             :     {
     295         487 :         if(pos != std::string::npos
     296         211 :         && pos > 0)
     297             :         {
     298         422 :             glob.push_back(filename.substr(0, pos + 1)
     299         422 :                     + project_name
     300         422 :                     + ".d/50-"
     301         633 :                     + filename.substr(pos + 1));
     302             :         }
     303             :         else
     304             :         {
     305             :             glob.push_back(project_name
     306         276 :                     + (".d/50-" + filename));
     307             :         }
     308             :     }
     309             : 
     310         506 :     return glob;
     311             : }
     312             : 
     313             : 
     314             : /** \brief Replace a starting `~/...` with the contents of the \$HOME variable.
     315             :  *
     316             :  * This function checks the beginning of \p filename. If it starts with `'~/'`
     317             :  * then it replaces the `'~'` character with the contents of the \$HOME
     318             :  * environment variable.
     319             :  *
     320             :  * If \p filename is just `"~"`, then the function returns the contents of
     321             :  * the \$HOME environment variable by itself.
     322             :  *
     323             :  * If somehow the \$HOME environment variable is empty, the function does
     324             :  * nothing.
     325             :  *
     326             :  * \param[in] filename  The filename to check for a tilde (~).
     327             :  *
     328             :  * \return The input as is unless the \$HOME path can be prepended to replace
     329             :  *         the tilde (~) character.
     330             :  */
     331         562 : std::string handle_user_directory(std::string const & filename)
     332             : {
     333         562 :     char const * const home(getenv("HOME"));
     334         562 :     if(home != nullptr
     335         404 :     && *home != '\0')
     336             :     {
     337         804 :         if(!filename.empty()
     338         402 :         && filename[0] == '~'
     339         458 :         && (filename.length() == 1 || filename[1] == '/'))
     340             :         {
     341          56 :             return home + filename.substr(1);
     342             :         }
     343             :     }
     344             : 
     345         506 :     return filename;
     346             : }
     347             : 
     348             : 
     349             : 
     350           6 : }   // namespace advgetopt
     351             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.12