LCOV - code coverage report
Current view: top level - snapwebsites - snap_version.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 1 611 0.2 %
Date: 2019-12-15 17:13:15 Functions: 2 40 5.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Snap Websites Server -- verify and manage version and names in filenames
       2             : // Copyright (c) 2014-2019  Made to Order Software Corp.  All Rights Reserved
       3             : //
       4             : // This program is free software; you can redistribute it and/or modify
       5             : // it under the terms of the GNU General Public License as published by
       6             : // the Free Software Foundation; either version 2 of the License, or
       7             : // (at your option) any later version.
       8             : //
       9             : // This program is distributed in the hope that it will be useful,
      10             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             : // GNU General Public License for more details.
      13             : //
      14             : // You should have received a copy of the GNU General Public License
      15             : // along with this program; if not, write to the Free Software
      16             : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      17             : 
      18             : 
      19             : // self
      20             : //
      21             : #include "snapwebsites/snap_version.h"
      22             : 
      23             : 
      24             : // snapwebsites
      25             : //
      26             : #include "snapwebsites/minmax.h"
      27             : #include "snapwebsites/snap_string_list.h"
      28             : 
      29             : 
      30             : // snapdev
      31             : //
      32             : #include <snapdev/not_reached.h>
      33             : 
      34             : 
      35             : // last include
      36             : //
      37             : #include <snapdev/poison.h>
      38             : 
      39             : 
      40             : 
      41             : namespace snap
      42             : {
      43             : namespace snap_version
      44             : {
      45             : 
      46             : 
      47             : namespace
      48             : {
      49             : /** \brief List of operators.
      50             :  *
      51             :  * Operators are 1 or 2 characters. This string lists all the operators.
      52             :  * It is safe because the operators are used from a limited_auto_init
      53             :  * variable. Each string here is exactly 3 characters and thus we
      54             :  * can use the index x 3 to access the string operator.
      55             :  */
      56             : char const * g_operators = "\0\0\0"  // OPERATOR_UNORDERED
      57             :                            "=\0\0"   // OPERATOR_EQUAL
      58             :                            "!=\0"    // OPERATOR_EXCEPT
      59             :                            "<\0\0"   // OPERATOR_EARLIER
      60             :                            ">\0\0"   // OPERATOR_LATER
      61             :                            "<=\0"    // OPERATOR_EARLIER_OR_EQUAL
      62             :                            ">=\0";   // OPERATOR_LATER_OR_EQUAL
      63             : } // no name namespace
      64             : 
      65             : 
      66             : /** \brief Find the extension used from a list of extension.
      67             :  *
      68             :  * This function checks the end of the \p filename for a match with one
      69             :  * of the specified extensions and returns the extension that matches.
      70             :  *
      71             :  * Note that the list of extensions MUST be sorted from the longest
      72             :  * extension first to the shortest last.
      73             :  *
      74             :  * The extensions are all expected
      75             :  *
      76             :  * \param[in] filename  The name of the file to check.
      77             :  * \param[in] extensions  A nullptr terminated array of extensions.
      78             :  *
      79             :  * \return The pointer of the extensions that matched or nullptr.
      80             :  */
      81           0 : char const * find_extension(QString const& filename, char const **extensions)
      82             : {
      83             : #ifdef DEBUG
      84             :     // we expect at least one extensions in the list
      85           0 :     size_t length(strlen(extensions[0]));
      86             : #endif
      87           0 :     do
      88             :     {
      89             : #ifdef DEBUG
      90           0 :         size_t const l(strlen(*extensions));
      91           0 :         if(l > length)
      92             :         {
      93           0 :             throw snap_logic_exception(QString("Extension \"%1\" is longer than the previous extension \"%2\".")
      94           0 :                     .arg(*extensions).arg(extensions[-1]));
      95             :         }
      96           0 :         length = l;
      97             : #endif
      98           0 :         if(filename.endsWith(*extensions))
      99             :         {
     100           0 :             return *extensions;
     101             :         }
     102             : 
     103             :         // not that one, move on
     104           0 :         ++extensions;
     105             :     }
     106           0 :     while(*extensions != nullptr);
     107             : 
     108           0 :     return nullptr;
     109             : }
     110             : 
     111             : 
     112             : /** \brief Verify that the specified name is valid.
     113             :  *
     114             :  * A basic name must be composed of letters ([a-z]), digits ([0-9]), and
     115             :  * dashes (-). This function makes sure that the name only includes
     116             :  * those characters. The minimum size must also be two characters.
     117             :  *
     118             :  * Also, the name cannot have two or more dashes in a row, it also must
     119             :  * start with a letter and cannot end with a dash.
     120             :  *
     121             :  * Note that only lower case ASCII letters are accepted ([a-z]).
     122             :  *
     123             :  * The following is the regular expression representing a basic name:
     124             :  *
     125             :  * \code
     126             :  *  [a-z][-a-z0-9]*[a-z0-9]
     127             :  * \endcode
     128             :  *
     129             :  * \param[in] name  The name being checked.
     130             :  * \param[out] error  A string where the error is saved if one is discovered.
     131             :  *
     132             :  * \return true if the name is considered valid, false if an error is
     133             :  *         found and the error string is set to that error.
     134             :  */
     135           0 : bool validate_basic_name(QString const & name, QString & error)
     136             : {
     137           0 :     error.clear();
     138             : 
     139             :     // length constraint
     140           0 :     int const max_length(name.length());
     141           0 :     if(max_length < 2)
     142             :     {
     143             :         // name must be at least 2 characters
     144           0 :         error = QString("the name or browser in a versioned filename must be at least two characters. \"%1\" is not valid.").arg(name);
     145           0 :         return false;
     146             :     }
     147             : 
     148             :     // first character constraint
     149           0 :     ushort c(name[0].unicode());
     150           0 :     if(c < 'a' || c > 'z')
     151             :     {
     152             :         // name cannot start with dash (-) or a digit ([0-9])
     153           0 :         error = QString("the name or browser of a versioned filename must start with a letter [a-z]. \"%1\" is not valid.").arg(name);
     154           0 :         return false;
     155             :     }
     156             : 
     157             :     // inside constraints
     158           0 :     ushort p(c);
     159           0 :     for(int i(0); i < max_length; ++i, p = c)
     160             :     {
     161           0 :         c = name.at(i).unicode();
     162           0 :         if(c == '-')
     163             :         {
     164             :             // two '-' in a row constraint
     165           0 :             if(p == '-')
     166             :             {
     167             :                 // found two '-' in a row
     168           0 :                 error = QString("a name or browser versioned filename cannot include two dashes (--) one after another. \"%1\" is not valid.").arg(name);
     169           0 :                 return false;
     170             :             }
     171             :         }
     172           0 :         else if((c < '0' || c > '9')
     173           0 :              && (c < 'a' || c > 'z'))
     174             :         {
     175             :             // name can only include [a-z0-9] and dashes (-)
     176           0 :             error = QString("a name or browser versioned filename can only include letters (a-z), digits (0-9), and dashes (-). \"%1\" is not valid.").arg(name);
     177           0 :             return false;
     178             :         }
     179             :     }
     180             : 
     181             :     // no ending '-' constraint
     182           0 :     if(c == '-') // c and p have the value, the last character
     183             :     {
     184           0 :         error = QString("a versioned name or browser cannot end with a dash (-) or a colon (:). \"%1\" is not valid.").arg(name);
     185           0 :         return false;
     186             :     }
     187             : 
     188           0 :     return true;
     189             : }
     190             : 
     191             : 
     192             : /** \brief Verify that the name or browser strings are valid.
     193             :  *
     194             :  * The \p name parameter is checked for validity. It may be composed
     195             :  * of a namespace and a name separated by the namespace scope operator (::).
     196             :  *
     197             :  * If no scope operator is found in the name, then no namespace is defined
     198             :  * and that parameter remains empty.
     199             :  *
     200             :  * \code
     201             :  *      [<namespace>::]<name>
     202             :  * \endcode
     203             :  *
     204             :  * The namespace and name parts must both be valid basic names.
     205             :  *
     206             :  * The names must exclusively be composed of lowercase letters. This will
     207             :  * allow, one day, to run Snap! on computers that do not distinguish
     208             :  * between case (i.e. Mac OS/X.)
     209             :  *
     210             :  * \note
     211             :  * This function is used to verify the name and the browser strings.
     212             :  *
     213             :  * \param[in,out] name  The name to be checked against the name pattern.
     214             :  * \param[out] error  The error message in case an error occurs.
     215             :  * \param[out] namespace_string  The namespace if one was defined.
     216             :  *
     217             :  * \return true if the name matches, false otherwise.
     218             :  *
     219             :  * \sa validate_basic_name()
     220             :  */
     221           0 : bool validate_name(QString & name, QString & error, QString & namespace_string)
     222             : {
     223           0 :     error.clear();
     224           0 :     namespace_string.clear();
     225             : 
     226           0 :     int const pos(name.indexOf("::"));
     227           0 :     if(pos != -1)
     228             :     {
     229             :         // name includes a namespace
     230           0 :         namespace_string = name.mid(0, pos);
     231           0 :         name = name.mid(pos + 2);
     232           0 :         if(!validate_basic_name(namespace_string, error))
     233             :         {
     234           0 :             return false;
     235             :         }
     236             :     }
     237             : 
     238           0 :     return validate_basic_name(name, error);
     239             : }
     240             : 
     241             : 
     242             : /** \brief Validate a version.
     243             :  *
     244             :  * This function validates a version string and returns the result.
     245             :  *
     246             :  * The validation includes three steps:
     247             :  *
     248             :  * \li Parse the input \p version_string parameter in separate numbers.
     249             :  * \li Save those numbers in the \p version vector.
     250             :  * \li Canonicalized the \p version vector by removing ending zeroes.
     251             :  *
     252             :  * The function only supports sets of numbers in the version. Something
     253             :  * similar to 1.2.3. The regex of \p version_string looks like this:
     254             :  *
     255             :  * \code
     256             :  * [0-9]+(\.[0-9]+)*
     257             :  * \endcode
     258             :  *
     259             :  * The versions are viewed as:
     260             :  *
     261             :  * \li Major Release Version (public)
     262             :  * \li Minor Release Version (public)
     263             :  * \li Patch Version (still public)
     264             :  * \li Development or Build Version (not public)
     265             :  *
     266             :  * While in development, each change should be reflected by incrementing
     267             :  * the development (or build) version number by 1. That way your browser
     268             :  * will automatically reload the new file.
     269             :  *
     270             :  * Once the development is over and a new version is to be released,
     271             :  * remove the development version or reset it to zero and increase the
     272             :  * Patch Version, or one of the Release Versions as appropriate.
     273             :  *
     274             :  * If you are trying to install a 3rd party JavaScript library which uses
     275             :  * a different scheme for their version, learn of their scheme and adapt
     276             :  * it to our versions. For example, a version defined as:
     277             :  *
     278             :  * \code
     279             :  * <major-version>.<minor-version>[<patch>]
     280             :  * \endcode
     281             :  *
     282             :  * where \<patch\> is a letter, can easily be converted to a 1.2.3 type of
     283             :  * version where the letters are numbered starting at 1 (if no patch letter,
     284             :  * use zero.)
     285             :  *
     286             :  * In the end the function returns an array of integers in the \p version
     287             :  * parameter. This parameter is used by subsequent compare() calls.
     288             :  *
     289             :  * \note
     290             :  * The version "0" is considered valid although maybe not useful (We
     291             :  * suggest that you do not use it, use at least 0.0.0.1)
     292             :  *
     293             :  * \note
     294             :  * Although we only mention 4 numbers in a version, this function does
     295             :  * not enforce a limit. So you could use 5 or more numbers in your
     296             :  * version definitions.
     297             :  *
     298             :  * \param[in,out] version_string  The version to be parsed.
     299             :  * \param[out] version  The array of numbers.
     300             :  * \param[out] error  The error message if an error occurs.
     301             :  *
     302             :  * \return true if the version is considered valid.
     303             :  */
     304           0 : bool validate_version(QString const & version_string, version_numbers_vector_t & version, QString & error)
     305             : {
     306           0 :     version.clear();
     307             : 
     308           0 :     int const max_length(version_string.length());
     309           0 :     if(max_length < 1)
     310             :     {
     311           0 :         error = "The version in a versioned filename is required after the name. \"" + version_string + "\" is not valid.";
     312           0 :         return false;
     313             :     }
     314           0 :     if(version_string.at(max_length - 1).unicode() == '.')
     315             :     {
     316           0 :         error = "The version in a versioned filename cannot end with a period. \"" + version_string + "\" is not valid.";
     317           0 :         return false;
     318             :     }
     319             : 
     320           0 :     for(int i(0); i < max_length;)
     321             :     {
     322             :         // force the version to have a digit at the start
     323             :         // and after each period
     324           0 :         ushort c(version_string.at(i).unicode());
     325           0 :         if(c < '0' || c > '9')
     326             :         {
     327           0 :             error = "The version of a versioned filename is expected to have a number at the start and after each period. \"" + version_string + "\" is not valid.";
     328           0 :             return false;
     329             :         }
     330           0 :         int value(c - '0');
     331             :         // start with ++i because we just checked character 'i'
     332           0 :         for(++i; i < max_length;)
     333             :         {
     334           0 :             c = version_string.at(i).unicode();
     335           0 :             ++i;
     336           0 :             if(c < '0' || c > '9')
     337             :             {
     338           0 :                 if(c != '.')
     339             :                 {
     340           0 :                     error = "The version of a versioned filename is expected to be composed of numbers and periods (.) only. \"" + version_string + "\" is not valid.";
     341           0 :                     return false;
     342             :                 }
     343           0 :                 if(i == max_length)
     344             :                 {
     345           0 :                     throw snap_logic_exception("The version_string was already checked for an ending '.' and yet we reached that case later in the function.");
     346             :                 }
     347           0 :                 break;
     348             :             }
     349           0 :             value = value * 10 + c - '0';
     350             :         }
     351           0 :         version.push_back(value);
     352             :     }
     353             : 
     354             :     // canonicalize the array
     355           0 :     while(version.size() > 1 && 0 == version[version.size() - 1])
     356             :     {
     357           0 :         version.pop_back();
     358             :     }
     359             : 
     360           0 :     return true;
     361             : }
     362             : 
     363             : 
     364             : /** \brief Validate an operator string.
     365             :  *
     366             :  * This function validates an operator string and converts it to an operator
     367             :  * enumeration.
     368             :  *
     369             :  * The function clears the error string by default. If the operator cannot
     370             :  * be converted, then an error message is saved in that string.
     371             :  *
     372             :  * Supported operators are:
     373             :  *
     374             :  * \li '=' or '=='
     375             :  * \li '!=' or '<>'
     376             :  * \li '<'
     377             :  * \li '<='
     378             :  * \li '>'
     379             :  * \li '>='
     380             :  *
     381             :  * Note that '==' and '<>' are extension. These are accepted by the
     382             :  * canonicalized version are '=' and '!=' respectively.
     383             :  *
     384             :  * \note
     385             :  * Internally the strings get canonicalized in the version_operator
     386             :  * object. The get_operator_string() function always returns a canonicalized
     387             :  * version of the operator.
     388             :  *
     389             :  * \param[in] operator_string  The operator to convert.
     390             :  * \param[out] op  The new operator.
     391             :  * \param[out] error  An error string if the operator is not valid.
     392             :  *
     393             :  * \return true if the operator is valid.
     394             :  */
     395           0 : bool validate_operator(QString const & operator_string, operator_t & op, QString & error)
     396             : {
     397           0 :     op = operator_t::OPERATOR_UNORDERED;
     398           0 :     error.clear();
     399             : 
     400           0 :     if(operator_string == "=" || operator_string == "==")
     401             :     {
     402           0 :         op = operator_t::OPERATOR_EQUAL;
     403             :     }
     404           0 :     else if(operator_string == "!=" || operator_string == "<>")
     405             :     {
     406           0 :         op = operator_t::OPERATOR_EXCEPT;
     407             :     }
     408           0 :     else if(operator_string == "<")
     409             :     {
     410             :         // support << as well, like in Debian?
     411           0 :         op = operator_t::OPERATOR_EARLIER;
     412             :     }
     413           0 :     else if(operator_string == ">")
     414             :     {
     415             :         // support >> as well, like in Debian?
     416           0 :         op = operator_t::OPERATOR_LATER;
     417             :     }
     418           0 :     else if(operator_string == "<=")
     419             :     {
     420           0 :         op = operator_t::OPERATOR_EARLIER_OR_EQUAL;
     421             :     }
     422           0 :     else if(operator_string == ">=")
     423             :     {
     424           0 :         op = operator_t::OPERATOR_LATER_OR_EQUAL;
     425             :     }
     426             :     else
     427             :     {
     428           0 :         error = "Operator " + operator_string + " is not recognized as a valid operator.";
     429           0 :         return false;
     430             :     }
     431             : 
     432           0 :     return true;
     433             : }
     434             : 
     435             : 
     436             : /** \fn name::clear()
     437             :  * \brief Clear the name.
     438             :  *
     439             :  * This is the only way to clear a name object. This function clears the
     440             :  * name (makes it an empty name) and clears the error message if there was
     441             :  * one.
     442             :  *
     443             :  * Note that set_name() cannot be used with an empty string because that is
     444             :  * not a valid entry. Names have to be at least two characters.
     445             :  *
     446             :  * By default, when a name object is constructed, the name is empty.
     447             :  */
     448             : 
     449             : 
     450             : /** \brief Set the name of the string.
     451             :  *
     452             :  * Set the name of the item. This function verifies that the name is valid,
     453             :  * if so the function returns true and saves the new name in the name object.
     454             :  * Otherwise it doesn't change anything and returns false.
     455             :  *
     456             :  * This function clears the error by default so that way if no error occurs
     457             :  * the get_error() function returns an empty string.
     458             :  *
     459             :  * \param[in] name_string  The name to save in this object.
     460             :  *
     461             :  * \return true if the name was valid.
     462             :  */
     463           0 : bool name::set_name(QString const& name_string)
     464             : {
     465           0 :     QString namespace_string;
     466           0 :     QString the_name(name_string);
     467           0 :     bool const r(validate_name(the_name, f_error, namespace_string));
     468           0 :     if(r)
     469             :     {
     470           0 :         f_name = the_name;
     471           0 :         f_namespace = namespace_string;
     472             :     }
     473           0 :     return r;
     474             : }
     475             : 
     476             : 
     477             : /** \fn name::get_name() const
     478             :  * \brief Retrieve the name.
     479             :  *
     480             :  * This function returns the last name that was set with the set_name()
     481             :  * function and was valid.
     482             :  *
     483             :  * This means only valid names or empty names are returned.
     484             :  *
     485             :  * \return The last name set with set_name().
     486             :  */
     487             : 
     488             : 
     489             : /** \fn name::is_valid() const
     490             :  * \brief Check whether this name is considered valid.
     491             :  *
     492             :  * Although the set_name() function does not change the old value when it
     493             :  * fails, it is considered invalid if the new value was invalid (had a
     494             :  * character that is not considered valid in a name, was too short, etc.)
     495             :  *
     496             :  * This function returns true if the last set_name() generated no error.
     497             :  * Note that a new empty name (or after a call to the clear() function)
     498             :  * is considered valid even though in most cases a name is mandatory.
     499             :  *
     500             :  * \return true if the name is currently considered valid.
     501             :  */
     502             : 
     503             : 
     504             : /** \fn name::get_error() const
     505             :  * \brief Retrieve the error.
     506             :  *
     507             :  * If the set_name() function returns false, then the error message will
     508             :  * be set to what happened (why the name was refused.) This error message
     509             :  * can be retrieved using this function.
     510             :  *
     511             :  * The clear() function empties the error message as well as the name.
     512             :  *
     513             :  * \return The last error message.
     514             :  */
     515             : 
     516             : 
     517             : /** \brief Compare two names together.
     518             :  *
     519             :  * This function compares two names together and returns one of the
     520             :  * following:
     521             :  *
     522             :  * \li COMPARE_INVALID -- if the current name is considered invalid
     523             :  * \li COMPARE_SMALLER -- if this is smaller than \p rhs
     524             :  * \li COMPARE_EQUAL -- if this is equal \p rhs
     525             :  * \li COMPARE_LARGER -- if this is larger than \p rhs
     526             :  *
     527             :  * The special name "any" is viewed as a pattern that matches any
     528             :  * name. This comparing "any" against, say, "editor" returns
     529             :  * COMPARE_EQUAL. "any" can appear in this name or the rhs name.
     530             :  *
     531             :  * \param[in] rhs  The right hand side name to compare with.
     532             :  *
     533             :  * \return one of the COMPARE_... values (-2, -1, 0, 1).
     534             :  */
     535           0 : compare_t name::compare(name const& rhs) const
     536             : {
     537           0 :     if(!is_valid() || !rhs.is_valid())
     538             :     {
     539           0 :         return compare_t::COMPARE_INVALID;
     540             :     }
     541             : 
     542           0 :     if(f_name == "any" || rhs.f_name == "any")
     543             :     {
     544           0 :         return compare_t::COMPARE_EQUAL;
     545             :     }
     546             : 
     547           0 :     if(f_name < rhs.f_name)
     548             :     {
     549           0 :         return compare_t::COMPARE_SMALLER;
     550             :     }
     551           0 :     if(f_name > rhs.f_name)
     552             :     {
     553           0 :         return compare_t::COMPARE_LARGER;
     554             :     }
     555           0 :     return compare_t::COMPARE_EQUAL;
     556             : }
     557             : 
     558             : 
     559             : /** \brief Set the specified version string as the new version.
     560             :  *
     561             :  * This function parses the supplied version string in an array of
     562             :  * version numbers saved internally.
     563             :  *
     564             :  * If an error occurs, the current version is not modified and
     565             :  * an error message is saved internally. The message can be retrieved
     566             :  * with the get_error() function.
     567             :  *
     568             :  * The error messages is cleared on entry so if no errore are discovered
     569             :  * in \p version_string then get_error() returns an empty string.
     570             :  *
     571             :  * \param[in] version_string  The version string to parse in a series of
     572             :  *                            numbers.
     573             :  *
     574             :  * \return true if the version is considered valid.
     575             :  */
     576           0 : bool version::set_version_string(QString const& version_string)
     577             : {
     578           0 :     version_numbers_vector_t version_vector;
     579           0 :     bool const r(validate_version(version_string, version_vector, f_error));
     580           0 :     if(r)
     581             :     {
     582           0 :         set_version(version_vector);
     583             :     }
     584           0 :     return r;
     585             : }
     586             : 
     587             : 
     588             : /** \brief Set a new version from an array of numbers.
     589             :  *
     590             :  * This function can be used to set the version directly from a set of
     591             :  * numbers. The function canonicalize the version array by removing
     592             :  * any ending zeroes.
     593             :  *
     594             :  * \param[in] version_vector  The value of the new version.
     595             :  */
     596             : #pragma GCC diagnostic push
     597             : #pragma GCC diagnostic ignored "-Wstrict-overflow"
     598           0 : void version::set_version(version_numbers_vector_t const& version_vector)
     599             : {
     600             : #pragma GCC diagnostic pop
     601           0 :     f_error.clear();    // no error possible in this case
     602             : 
     603             :     // copy and then canonicalize the array
     604           0 :     f_version = version_vector;
     605           0 :     while(f_version.size() > 1 && 0 == f_version[f_version.size() - 1])
     606             :     {
     607           0 :         f_version.pop_back();
     608             :     }
     609             : 
     610             :     // make sure that if we have a new version we get a new version
     611             :     // string that corresponds one to one
     612           0 :     f_version_string.clear();
     613           0 : }
     614             : 
     615             : 
     616             : /** \brief Set the version operator.
     617             :  *
     618             :  * By default a version has operator Unordered. In general, a version is
     619             :  * 'unordered' when not part of an expression (i.e. in a filename, the
     620             :  * version is just that and no operator is defined.) In a list of versions
     621             :  * of a dependency, the version is always defined with an operator although
     622             :  * by default the \>= operator is not specified.
     623             :  *
     624             :  * \param[in] op  The version operator.
     625             :  */
     626           0 : void version::set_operator(version_operator const& op)
     627             : {
     628           0 :     f_operator = op;
     629           0 : }
     630             : 
     631             : 
     632             : /** \fn version::get_version() const
     633             :  * \brief Retrieve the version as an array of numbers.
     634             :  *
     635             :  * This function returns the array of numbers representing this version.
     636             :  * The array will have been canonicalized, which means it will not end
     637             :  * with extra zeroes (it may be zero, if composed of one element.)
     638             :  *
     639             :  * By default, a version object is empty which means "no version".
     640             :  *
     641             :  * \return An array of version numbers.
     642             :  */
     643             : 
     644             : 
     645             : /** \fn version::get_version_string() const
     646             :  * \brief Retrieve the version as a canonicalized string.
     647             :  *
     648             :  * This function returns the version as a canonicalized string. The version
     649             :  * is canonnicalized by removing all .0 from the end of a version. So version
     650             :  * 1.2 and 1.2.0 will both return string "1.2".
     651             :  *
     652             :  * \return The canonicalized version string.
     653             :  */
     654           0 : QString const& version::get_version_string() const
     655             : {
     656           0 :     if(f_version_string.isEmpty() && !f_version.isEmpty())
     657             :     {
     658             :         // create the version string
     659           0 :         f_version_string = QString("%1").arg(static_cast<int>(f_version[0]));
     660           0 :         int const max_size(f_version.size());
     661           0 :         for(int i(1); i < max_size; ++i)
     662             :         {
     663           0 :             f_version_string += QString(".%1").arg(static_cast<int>(f_version[i]));
     664             :         }
     665             :     }
     666             : 
     667           0 :     return f_version_string;
     668             : }
     669             : 
     670             : 
     671             : /** \brief Return the operator and version as a string.
     672             :  *
     673             :  * This function returns the operator followed by the version both
     674             :  * separated by a space. If the operator is OPERATOR_UNORDERED then
     675             :  * just the version string is returned, just as if you called the
     676             :  * get_version_string() function.
     677             :  *
     678             :  * \note
     679             :  * Dependencies are always expected to include an operator along their
     680             :  * version. When a version is specified without an operator, the
     681             :  * default OPERATOR_LATER_OR_EQUAL is used.
     682             :  *
     683             :  * \return The operator and version that this version object represent.
     684             :  */
     685           0 : QString version::get_opversion_string() const
     686             : {
     687           0 :     QString v(get_version_string());
     688             : 
     689           0 :     if(f_operator.get_operator() != operator_t::OPERATOR_UNORDERED)
     690             :     {
     691           0 :         v = f_operator.get_operator_string() + (" " + v);
     692             :     }
     693             : 
     694           0 :     return v;
     695             : }
     696             : 
     697             : 
     698             : /** \fn version::get_error() const
     699             :  * \brief Get errors
     700             :  *
     701             :  * The function retrieve the last error message that happened when you
     702             :  * called the set_version() function.
     703             :  *
     704             :  * The set_version() functions clear the error message out to represent
     705             :  * a "no error state."
     706             :  *
     707             :  * \return The error message or an empty string if none.
     708             :  */
     709             : 
     710             : 
     711             : /** \brief Compare two versions against each others.
     712             :  *
     713             :  * This function compares this version against \p rhs and returns one of
     714             :  * the following:
     715             :  *
     716             :  * \li COMPARE_INVALID -- if the current name is considered invalid
     717             :  * \li COMPARE_SMALLER -- if this is smaller than \p rhs
     718             :  * \li COMPARE_EQUAL -- if this is equal \p rhs
     719             :  * \li COMPARE_LARGER -- if this is larger than \p rhs
     720             :  *
     721             :  * \param[in] rhs  The right hand side name to compare with.
     722             :  *
     723             :  * \return One of the COMPARE_... values (-2, -1, 0, 1).
     724             :  */
     725           0 : compare_t version::compare(version const& rhs) const
     726             : {
     727           0 :     if(!is_valid() || !rhs.is_valid())
     728             :     {
     729           0 :         return compare_t::COMPARE_INVALID;
     730             :     }
     731             : 
     732           0 :     int const max_size(std::max SNAP_PREVENT_MACRO_SUBSTITUTION (f_version.size(), rhs.f_version.size()));
     733           0 :     for(int i(0); i < max_size; ++i)
     734             :     {
     735           0 :         int l(i >=     f_version.size() ? 0 : static_cast<int>(    f_version[i]));
     736           0 :         int r(i >= rhs.f_version.size() ? 0 : static_cast<int>(rhs.f_version[i]));
     737           0 :         if(l < r)
     738             :         {
     739           0 :             return compare_t::COMPARE_SMALLER;
     740             :         }
     741           0 :         if(l > r)
     742             :         {
     743           0 :             return compare_t::COMPARE_LARGER;
     744             :         }
     745             :     }
     746             : 
     747           0 :     return compare_t::COMPARE_EQUAL;
     748             : }
     749             : 
     750             : 
     751             : 
     752             : 
     753             : 
     754             : /** \brief Set the operator from a string.
     755             :  *
     756             :  * This function defines a version operator from a string as found in a
     757             :  * dependency string.
     758             :  *
     759             :  * The valid operators are:
     760             :  *
     761             :  * \li = or == -- OPERATOR_EQUAL, canonicalized as "="
     762             :  * \li != or <> -- OPERATOR_EXCEPT, canonicalized as "!="
     763             :  * \li < -- OPERATOR_EARLIER
     764             :  * \li > -- OPERATOR_LATER
     765             :  * \li <= -- OPERATOR_EARLIER_OR_EQUAL
     766             :  * \li >= -- OPERATOR_LATER_OR_EQUAL (TBD should this one be canonicalized
     767             :  *           as an empty string?)
     768             :  *
     769             :  * By default a version operator object is set to OPERATOR_UNORDERED which
     770             :  * pretty much means it was not set yet.
     771             :  *
     772             :  * Note that Debian supported \<\< and \>\> as an equivalent to \<= and \>=
     773             :  * respectively. We do not support those operators since (1) Debian
     774             :  * deprecated them, and (2) they are definitively confusing.
     775             :  *
     776             :  * You can also use the set_operator() function which access an OPERATOR_...
     777             :  * enumeration.
     778             :  *
     779             :  * Note that it is possible to create a range with a shortcut in a dependency
     780             :  * declaration:
     781             :  *
     782             :  * \code
     783             :  * <smaller version> < <larger version>
     784             :  * <smaller version> <= <larger version>
     785             :  *
     786             :  * // for example:
     787             :  * 1.3.4 < 1.4.0
     788             :  * 1.3.4 <= 1.4.0
     789             :  * \endcode
     790             :  *
     791             :  * Once compiled in, this is represented using two version operators and
     792             :  * the operator is changed from \< to > and \<, and from \<= to >= and
     793             :  * \<= respectively, so the previous example becomes:
     794             :  *
     795             :  * \code
     796             :  * // This range:
     797             :  * my_lib (1.3.4 < 1.4.0)
     798             :  *
     799             :  * // is equivalent to those two entries
     800             :  * my_lib (> 1.3.4)
     801             :  * my_lib (< 1.4)
     802             :  *
     803             :  * // And that range:
     804             :  * my_lib (1.3.4 <= 1.4.0)
     805             :  *
     806             :  * // is equivalent to those two entries
     807             :  * my_lib (>= 1.3.4)
     808             :  * my_lib (<= 1.4)
     809             :  * \endcode
     810             :  *
     811             :  * \param[in] operator_string  The string to be checked.
     812             :  *
     813             :  * \return true if the operator string represents a valid operator.
     814             :  */
     815           0 : bool version_operator::set_operator_string(QString const& operator_string)
     816             : {
     817             :     operator_t op;
     818           0 :     bool const r(validate_operator(operator_string, op, f_error));
     819           0 :     if(r)
     820             :     {
     821             :         // valid, save it
     822           0 :         set_operator(op);
     823             :     }
     824           0 :     return r;
     825             : }
     826             : 
     827             : 
     828             : /** \brief Set the operator using the enumeration.
     829             :  *
     830             :  * This function sets the operator using one of the enumeration values.
     831             :  *
     832             :  * \note
     833             :  * You may use this function to reset the version operator back to
     834             :  * OPERATOR_UNORDERED. In that case the operator string becomes
     835             :  * the empty string ("").
     836             :  *
     837             :  * \param[in] op  The new operator for this version operator object.
     838             :  *
     839             :  * \return true if op is a valid operator.
     840             :  */
     841           0 : bool version_operator::set_operator(operator_t op)
     842             : {
     843           0 :     switch(op)
     844             :     {
     845           0 :     case operator_t::OPERATOR_UNORDERED:
     846             :     case operator_t::OPERATOR_EQUAL:
     847             :     case operator_t::OPERATOR_EXCEPT:
     848             :     case operator_t::OPERATOR_EARLIER:
     849             :     case operator_t::OPERATOR_LATER:
     850             :     case operator_t::OPERATOR_EARLIER_OR_EQUAL:
     851             :     case operator_t::OPERATOR_LATER_OR_EQUAL:
     852           0 :         f_operator = op;
     853           0 :         return true;
     854             : 
     855           0 :     default:
     856           0 :         return false;
     857             : 
     858             :     }
     859             : }
     860             : 
     861             : 
     862             : /** \brief Retrieve the canonicalized operator string.
     863             :  *
     864             :  * This function returns the string representing the operator. The
     865             :  * string is canonicalized, which means that it has one single
     866             :  * representation (i.e. we accept "==" which is represented as "="
     867             :  * when canonicalized.)
     868             :  *
     869             :  * \return The canonicalized operator string.
     870             :  */
     871           0 : char const * version_operator::get_operator_string() const
     872             : {
     873           0 :     return g_operators + static_cast<int>(static_cast<operator_t>(f_operator)) * 3;
     874             : }
     875             : 
     876             : 
     877             : /** \fn version_operator::get_operator() const
     878             :  * \brief Retrieve the operator.
     879             :  *
     880             :  * This function retrieves the operator as a number. The operator is used
     881             :  * to compare versions between each others while searching for dependencies.
     882             :  *
     883             :  * \return The operator, may be OPERATOR_UNORDERED if not initialized.
     884             :  */
     885             : 
     886             : 
     887             : 
     888             : 
     889             : 
     890             : /** \brief Initialize a versioned filename object.
     891             :  *
     892             :  * The versioned filename class initializes the versioned filename object
     893             :  * with an extension which is mandatory and unique.
     894             :  *
     895             :  * \note
     896             :  * The period in the extension is optional. However, the extension cannot
     897             :  * be the empty string.
     898             :  *
     899             :  * \param[in] extension  The expected extension (i.e. ".js" for JavaScript files).
     900             :  */
     901           0 : versioned_filename::versioned_filename(QString const & extension)
     902             :     //: f_valid(false) -- auto-init
     903             :     //, f_error("") -- auto-init
     904           0 :     : f_extension(extension)
     905             :     //, f_name("") -- auto-init
     906             :     //, f_version_string("") -- auto-init
     907             :     //, f_version() -- auto-init
     908             : {
     909           0 :     if(f_extension.isEmpty())
     910             :     {
     911           0 :         throw snap_version_exception_invalid_extension("the extension of a versioned filename cannot be the empty string");
     912             :     }
     913             : 
     914             :     // make sure the extension includes the period
     915           0 :     if(f_extension.at(0).unicode() != '.')
     916             :     {
     917           0 :         f_extension = "." + f_extension;
     918             :     }
     919           0 : }
     920             : 
     921             : 
     922             : /** \brief Set the name of a file through the parser.
     923             :  *
     924             :  * This function is used to setup a versioned filename from a full filename.
     925             :  * The input filename can include a path. It must end with the valid
     926             :  * extension (as defined when creating the versioned_filename object.)
     927             :  * Assuming the function returns true, the get_filename() function
     928             :  * returns the basename (i.e. the filename without the path nor the
     929             :  * extension, although you can get the extension if you ask for it.)
     930             :  *
     931             :  * The filename is then broken up in a name, a version, and browser, all of
     932             :  * which are checked for validity. If invalid, the function returns false.
     933             :  *
     934             :  * \code
     935             :  * .../some/path/name_version_browser.ext
     936             :  * \endcode
     937             :  *
     938             :  * Note that the browser part is optional. In general, if not indicated
     939             :  * it means the file is compatible with all browsers.
     940             :  *
     941             :  * \note
     942             :  * This function respects the contract: if the function returns false,
     943             :  * then the name, version, and browser information are not changed.
     944             :  *
     945             :  * However, on entry the value of f_error is set to the empty string and
     946             :  * the value of f_valid is set to false. So most of the functions will
     947             :  * continue to return the old value of the versioned filename, except
     948             :  * the compare() and relational operators.
     949             :  *
     950             :  * \param[in] filename  The filename to be parsed for a version.
     951             :  *
     952             :  * \return true if the filename was a valid versioned filename.
     953             :  */
     954           0 : bool versioned_filename::set_filename(QString const & filename)
     955             : {
     956           0 :     f_error.clear();
     957             : 
     958             :     // the extension must be exactly "extension"
     959           0 :     if(!filename.endsWith(f_extension))
     960             :     {
     961           0 :         f_error = "this filename must end with \"" + f_extension + "\" in lowercase. \"" + filename + "\" is not valid.";
     962           0 :         return false;
     963             :     }
     964             : 
     965           0 :     int const max_length(filename.length() - f_extension.length());
     966             : 
     967           0 :     int start(filename.lastIndexOf('/'));
     968           0 :     if(start == -1)
     969             :     {
     970           0 :         start = 0;
     971             :     }
     972             :     else
     973             :     {
     974           0 :         ++start;
     975             :     }
     976             : 
     977             :     // now break the name in two or three parts: <name> and <version> [and <browser>]
     978           0 :     int p1(filename.indexOf('_', start));
     979           0 :     if(p1 == -1 || p1 > max_length)
     980             :     {
     981           0 :         f_error = "a versioned filename is expected to include an underscore (_) as the name and version separator. \"" + filename + "\" is not valid.";
     982           0 :         return false;
     983             :     }
     984             :     // and check whether the <browser> part is specified
     985           0 :     int p2(filename.indexOf('_', p1 + 1));
     986           0 :     if(p2 > max_length || p2 == -1)
     987             :     {
     988           0 :         p2 = max_length;
     989             :     }
     990             :     else
     991             :     {
     992             :         // filename ends with an underscore?
     993           0 :         if(p2 + 1 >= max_length)
     994             :         {
     995           0 :             f_error = QString("a browser name must be specified in a versioned filename if you include two underscores (_). \"%1\" is not valid.").arg(filename);
     996           0 :             return false;
     997             :         }
     998             :     }
     999             : 
    1000             :     // name
    1001           0 :     QString const name(filename.mid(start, p1 - start));
    1002           0 :     QString name_string(name);
    1003             :     // TBD: can we really allow a namespace in a filename?
    1004           0 :     QString namespace_string;
    1005           0 :     if(!validate_name(name_string, f_error, namespace_string))
    1006             :     {
    1007           0 :         return false;
    1008             :     }
    1009             : 
    1010             :     // version
    1011           0 :     ++p1;
    1012           0 :     QString const version_string(filename.mid(p1, p2 - p1));
    1013           0 :     version_numbers_vector_t version;
    1014           0 :     if(!validate_version(version_string, version, f_error))
    1015             :     {
    1016           0 :         return false;
    1017             :     }
    1018             : 
    1019             :     // browser
    1020           0 :     QString browser;
    1021           0 :     if(p2 < max_length)
    1022             :     {
    1023             :         // validate only if not empty (since it is optional, empty is okay)
    1024           0 :         browser = filename.mid(p2 + 1, max_length - p2 - 1);
    1025           0 :         if(!validate_basic_name(browser, f_error))
    1026             :         {
    1027           0 :             return false;
    1028             :         }
    1029             :     }
    1030             : 
    1031             :     // save the results
    1032           0 :     f_name.set_name(name);
    1033           0 :     f_version.set_version_string(version_string);
    1034           0 :     if(browser.isEmpty())
    1035             :     {
    1036             :         // browser info is optional and if not defined we need to clear
    1037             :         // the name (because a set_name("") generates an error)
    1038           0 :         f_browser.clear();
    1039             :     }
    1040             :     else
    1041             :     {
    1042           0 :         f_browser.set_name(browser);
    1043             :     }
    1044             : 
    1045           0 :     return true;
    1046             : }
    1047             : 
    1048             : 
    1049             : /** \brief Set the name of the versioned filename object.
    1050             :  *
    1051             :  * A versioned filename is composed of a name, a version, and an optional
    1052             :  * browser reference. This function is used to replace the name.
    1053             :  *
    1054             :  * The name is checked using the \p validate_name() function.
    1055             :  *
    1056             :  * \param[in] name  The new file name.
    1057             :  *
    1058             :  * \return true if the name is valid.
    1059             :  */
    1060           0 : bool versioned_filename::set_name(QString const& name)
    1061             : {
    1062           0 :     QString namespace_string;
    1063           0 :     QString name_string(name);
    1064           0 :     bool const r(validate_name(name_string, f_error, namespace_string));
    1065           0 :     if(r)
    1066             :     {
    1067           0 :         f_name.set_name(name);
    1068             :     }
    1069             : 
    1070           0 :     return r;
    1071             : }
    1072             : 
    1073             : 
    1074             : /** \brief Set the version of the versioned filename.
    1075             :  *
    1076             :  * This function sets the version of the versioned filename. Usually, you
    1077             :  * will call the set_filename() function which sets the name, the version,
    1078             :  * and the optional browser all at once and especially let the parsing
    1079             :  * work to the versioned_filename class.
    1080             :  *
    1081             :  * \param[in] version_string  The version in the form of a string.
    1082             :  *
    1083             :  * \return true if the version was considered valid.
    1084             :  */
    1085           0 : bool versioned_filename::set_version(QString const& version_string)
    1086             : {
    1087           0 :     version_numbers_vector_t version_vector;
    1088           0 :     bool r(validate_version(version_string, version_vector, f_error));
    1089           0 :     if(r)
    1090             :     {
    1091           0 :         f_version.set_version_string(version_string);
    1092             :     }
    1093             : 
    1094           0 :     return r;
    1095             : }
    1096             : 
    1097             : 
    1098             : /** \brief Return the canonicalized filename.
    1099             :  *
    1100             :  * This function returns the canonicalized filename. This means all version
    1101             :  * numbers have leading 0's removed, ending .0 are all removed, and the
    1102             :  * path is removed.
    1103             :  *
    1104             :  * The \p extension flag can be used to get the extension appended or not.
    1105             :  *
    1106             :  * \param[in] extension  Set to true to get the extension.
    1107             :  *
    1108             :  * \return The canonicalized filename.
    1109             :  */
    1110           0 : QString versioned_filename::get_filename(bool extension) const
    1111             : {
    1112           0 :     if(!is_valid())
    1113             :     {
    1114           0 :         return "";
    1115             :     }
    1116             :     return f_name.get_name()
    1117           0 :          + "_" + f_version.get_version_string()
    1118           0 :          + (f_browser.get_name().isEmpty() ? "" : "_" + f_browser.get_name())
    1119           0 :          + (extension ? f_extension : "");
    1120             : }
    1121             : 
    1122             : 
    1123             : /** \brief Compare two versioned_filename's against each others.
    1124             :  *
    1125             :  * This function first makes sure that both filenames are considered
    1126             :  * valid, if not, the function returns COMPARE_INVALID (-2).
    1127             :  *
    1128             :  * Assuming the two filenames are valid, the function returns:
    1129             :  *
    1130             :  * \li COMPARE_SMALLER (-1) if this filename is considered to appear before rhs
    1131             :  * \li COMPARE_EQUAL (0) if both filenames are considered equal
    1132             :  * \li COMPARE_LARGER (1) if this filename is considered to appear after rhs
    1133             :  *
    1134             :  * The function first compares the name (get_name()) of each object.
    1135             :  * If not equal, return COMPARE_SMALLER or COMPARE_LARGER.
    1136             :  *
    1137             :  * When the name are equal, the function compares the browser (get_browser())
    1138             :  * of each object. If not equal, rethrn COMPARE_SMALLER or COMPARE_LARGER.
    1139             :  *
    1140             :  * When the name and the browser are equal, then the function compares the
    1141             :  * versions starting with the major release number. If a version array is
    1142             :  * longer than the other, the missing values in the smaller array are
    1143             :  * considered to be zero. That way "1.2.3" > "1.2" because "1.2" is the
    1144             :  * same as "1.2.0" and 3 > 0.
    1145             :  *
    1146             :  * \param[in] rhs  The right hand side to compare against this versioned
    1147             :  *                 filename.
    1148             :  *
    1149             :  * \return -2, -1, 0, or 1 depending on the order (or unordered status)
    1150             :  */
    1151           0 : compare_t versioned_filename::compare(versioned_filename const& rhs) const
    1152             : {
    1153           0 :     if(!is_valid())
    1154             :     {
    1155           0 :         return compare_t::COMPARE_INVALID;
    1156             :     }
    1157             : 
    1158           0 :     compare_t c(f_name.compare(rhs.f_name));
    1159           0 :     if(c != compare_t::COMPARE_EQUAL)
    1160             :     {
    1161           0 :         return c;
    1162             :     }
    1163           0 :     c = f_browser.compare(rhs.f_browser);
    1164           0 :     if(c != compare_t::COMPARE_EQUAL)
    1165             :     {
    1166           0 :         return c;
    1167             :     }
    1168             : 
    1169           0 :     return f_version.compare(rhs.f_version);
    1170             : }
    1171             : 
    1172             : 
    1173             : /** \brief Compare two filenames for equality.
    1174             :  *
    1175             :  * This function returns true if both filenames are considered equal
    1176             :  * (i.e. if the compare() function returns 0.)
    1177             :  *
    1178             :  * Note that if one or both filenames are considered unordered, the
    1179             :  * function always returns false.
    1180             :  *
    1181             :  * \param[in] rhs  The other filename to compare against.
    1182             :  *
    1183             :  * \return true if both filenames are considered equal.
    1184             :  */
    1185           0 : bool versioned_filename::operator == (versioned_filename const & rhs) const
    1186             : {
    1187           0 :     compare_t r(compare(rhs));
    1188           0 :     return r == compare_t::COMPARE_EQUAL;
    1189             : }
    1190             : 
    1191             : 
    1192             : /** \brief Compare two filenames for differences.
    1193             :  *
    1194             :  * This function returns true if both filenames are not considered equal
    1195             :  * (i.e. if the compare() function returns -1 or 1.)
    1196             :  *
    1197             :  * Note that if one or both filenames are considered unordered, the
    1198             :  * function always returns false.
    1199             :  *
    1200             :  * \param[in] rhs  The other filename to compare against.
    1201             :  *
    1202             :  * \return true if both filenames are considered different.
    1203             :  */
    1204           0 : bool versioned_filename::operator != (versioned_filename const& rhs) const
    1205             : {
    1206           0 :     compare_t r(compare(rhs));
    1207           0 :     return r == compare_t::COMPARE_SMALLER || r == compare_t::COMPARE_LARGER;
    1208             : }
    1209             : 
    1210             : 
    1211             : /** \brief Compare two filenames for inequality.
    1212             :  *
    1213             :  * This function returns true if this filename is considered to appear before
    1214             :  * \p rhs filename (i.e. if the compare() function returns -1.)
    1215             :  *
    1216             :  * Note that if one or both filenames are considered unordered, the
    1217             :  * function always returns false.
    1218             :  *
    1219             :  * \param[in] rhs  The other filename to compare against.
    1220             :  *
    1221             :  * \return true if both filenames are considered inequal.
    1222             :  */
    1223           0 : bool versioned_filename::operator <  (versioned_filename const& rhs) const
    1224             : {
    1225           0 :     compare_t r(compare(rhs));
    1226           0 :     return r == compare_t::COMPARE_SMALLER;
    1227             : }
    1228             : 
    1229             : 
    1230             : /** \brief Compare two filenames for inequality.
    1231             :  *
    1232             :  * This function returns true if this filename is considered to appear before
    1233             :  * \p rhs filename or both are equal (i.e. if the compare() function
    1234             :  * returns -1 or 0.)
    1235             :  *
    1236             :  * Note that if one or both filenames are considered unordered, the
    1237             :  * function always returns false.
    1238             :  *
    1239             :  * \param[in] rhs  The other filename to compare against.
    1240             :  *
    1241             :  * \return true if both filenames are considered inequal.
    1242             :  */
    1243           0 : bool versioned_filename::operator <= (versioned_filename const& rhs) const
    1244             : {
    1245           0 :     compare_t r(compare(rhs));
    1246           0 :     return r == compare_t::COMPARE_SMALLER || r == compare_t::COMPARE_EQUAL;
    1247             : }
    1248             : 
    1249             : 
    1250             : /** \brief Compare two filenames for inequality.
    1251             :  *
    1252             :  * This function returns true if this filename is considered to appear after
    1253             :  * \p rhs filename (i.e. if the compare() function returns 1.)
    1254             :  *
    1255             :  * Note that if one or both filenames are considered unordered, the
    1256             :  * function always returns false.
    1257             :  *
    1258             :  * \param[in] rhs  The other filename to compare against.
    1259             :  *
    1260             :  * \return true if both filenames are considered inequal.
    1261             :  */
    1262           0 : bool versioned_filename::operator >  (versioned_filename const& rhs) const
    1263             : {
    1264           0 :     compare_t r(compare(rhs));
    1265           0 :     return r > compare_t::COMPARE_EQUAL;
    1266             : }
    1267             : 
    1268             : 
    1269             : /** \brief Compare two filenames for inequality.
    1270             :  *
    1271             :  * This function returns true if this filename is considered to appear before
    1272             :  * \p rhs filename or both are equal (i.e. if the compare() function
    1273             :  * returns 0 or 1.)
    1274             :  *
    1275             :  * Note that if one or both filenames are considered unordered, the
    1276             :  * function always returns false.
    1277             :  *
    1278             :  * \param[in] rhs  The other filename to compare against.
    1279             :  *
    1280             :  * \return true if both filenames are considered inequal.
    1281             :  */
    1282           0 : bool versioned_filename::operator >= (versioned_filename const& rhs) const
    1283             : {
    1284           0 :     compare_t r(compare(rhs));
    1285           0 :     return r >= compare_t::COMPARE_EQUAL;
    1286             : }
    1287             : 
    1288             : 
    1289             : /** \brief Define a dependency from a string.
    1290             :  *
    1291             :  * This function is generally called to transform a dependency string
    1292             :  * in a name, a list of versions and operators, and a list of browsers.
    1293             :  *
    1294             :  * The format of the string is as follow in simplified yacc:
    1295             :  *
    1296             :  * \code
    1297             :  * dependency: name
    1298             :  *           | name versions
    1299             :  *           | name versions browsers
    1300             :  *           | name browsers
    1301             :  *
    1302             :  * name: NAME
    1303             :  *
    1304             :  * versions: '(' version_list ')'
    1305             :  *
    1306             :  * version_list: version_range
    1307             :  *             | version_list ',' version_range
    1308             :  *
    1309             :  * version_range: version
    1310             :  *              | version '<=' version
    1311             :  *              | version '<' version
    1312             :  *              | op version
    1313             :  *
    1314             :  * version: VERSION
    1315             :  *
    1316             :  * op: '='
    1317             :  *   | '!='
    1318             :  *   | '<'
    1319             :  *   | '<='
    1320             :  *   | '>'
    1321             :  *   | '>='
    1322             :  *
    1323             :  * browsers: '[' browser_list ']'
    1324             :  *
    1325             :  * browser_list: name
    1326             :  *             | browser_list ',' name
    1327             :  * \endcode
    1328             :  */
    1329           0 : bool dependency::set_dependency(QString const& dependency_string)
    1330             : {
    1331           0 :     f_error.clear();
    1332             : 
    1333             :     // trim spaces
    1334           0 :     QString const d(dependency_string.simplified());
    1335             : 
    1336             :     // get the end of the name
    1337           0 :     int space_pos(d.indexOf(' '));
    1338           0 :     if(space_pos < 0)
    1339             :     {
    1340           0 :         space_pos = d.length();
    1341             :     }
    1342           0 :     int paren_pos(d.indexOf('('));
    1343           0 :     if(paren_pos < 0)
    1344             :     {
    1345           0 :         paren_pos = d.length();
    1346             :     }
    1347           0 :     int bracket_pos(d.indexOf('['));
    1348           0 :     if(bracket_pos < 0)
    1349             :     {
    1350           0 :         bracket_pos = d.length();
    1351             :     }
    1352           0 :     if(paren_pos != d.length() && paren_pos > bracket_pos)
    1353             :     {
    1354             :         // cannot have versions after browsers
    1355           0 :         f_error = "version dependency syntax error, '[' found before '('";
    1356           0 :         return false;
    1357             :     }
    1358           0 :     int pos(std::min SNAP_PREVENT_MACRO_SUBSTITUTION (space_pos, std::min SNAP_PREVENT_MACRO_SUBSTITUTION (paren_pos, bracket_pos)));
    1359           0 :     QString const dep_name(d.left(pos));
    1360           0 :     QString namespace_string;
    1361           0 :     QString name_string(dep_name);
    1362           0 :     if(!validate_name(name_string, f_error, namespace_string))
    1363             :     {
    1364           0 :         return false;
    1365             :     }
    1366           0 :     f_name.set_name(dep_name);
    1367             : 
    1368             :     // skip the spaces (because of the simplied there should be 1 at most)
    1369           0 :     while(pos < d.length() && isspace(d.at(pos).unicode()))
    1370             :     {
    1371           0 :         ++pos;
    1372             :     }
    1373             : 
    1374             :     // read list of versions and operators
    1375           0 :     if(pos < d.length() && d.at(pos) == QChar('('))
    1376             :     {
    1377           0 :         ++pos;
    1378           0 :         int end(d.indexOf(')', pos));
    1379           0 :         if(end == -1)
    1380             :         {
    1381           0 :             f_error = "version dependency syntax error, ')' not found";
    1382           0 :             return false;
    1383             :         }
    1384           0 :         QString const version_list(d.mid(pos, end - pos));
    1385           0 :         snap_string_list version_strings(version_list.split(',', QString::SkipEmptyParts));
    1386           0 :         int const max_versions(version_strings.size());
    1387           0 :         for(int i(0); i < max_versions; ++i)
    1388             :         {
    1389           0 :             QString const vonly(version_strings[i].trimmed());
    1390           0 :             if(vonly.isEmpty())
    1391             :             {
    1392             :                 // this happens because of the trimmed()
    1393           0 :                 continue;
    1394             :             }
    1395           0 :             version_operator vo;
    1396           0 :             QByteArray utf8(vonly.toUtf8());
    1397           0 :             char const *s(utf8.data());
    1398           0 :             char const *start(s);
    1399           0 :             for(; (*s >= '0' && *s <= '9') || *s == '.'; ++s);
    1400           0 :             if(s == start)
    1401             :             {
    1402             :                 // we assume an operator at the start
    1403           0 :                 for(; (*s < '0' || *s > '9') && *s != '\0'; ++s);
    1404           0 :                 QString const op(QString::fromUtf8(start, static_cast<int>(s - start)).trimmed());
    1405           0 :                 if(!vo.set_operator_string(op))
    1406             :                 {
    1407           0 :                     f_error = vo.get_error();
    1408           0 :                     return false;
    1409             :                 }
    1410             :                 // skip spaces after the operator
    1411           0 :                 for(; isspace(*s); ++s);
    1412           0 :                 start = s;
    1413           0 :                 for(; (*s >= '0' && *s <= '9') || *s == '.'; ++s);
    1414             :             }
    1415             : 
    1416             :             // got a version, verify it
    1417           0 :             QString const version_string(QString::fromUtf8(start, static_cast<int>(s - start)));
    1418           0 :             version v;
    1419           0 :             if(!v.set_version_string(version_string))
    1420             :             {
    1421           0 :                 f_error = v.get_error();
    1422           0 :                 return false;
    1423             :             }
    1424           0 :             for(; isspace(*s); ++s);
    1425           0 :             if(*s != '\0')
    1426             :             {
    1427             :                 // not end of the version, check for an operator
    1428           0 :                 if(vo.get_operator() != operator_t::OPERATOR_UNORDERED)
    1429             :                 {
    1430           0 :                     f_error = "a version specification in a dependency can only include one operator, two found in \"" + vonly + "\" (missing ',' or ')' maybe?)";
    1431           0 :                     return false;
    1432             :                 }
    1433             :                 // we assume an operator in between two versions
    1434             :                 // (i.e. version <= version)
    1435           0 :                 start = s;
    1436           0 :                 for(; (*s < '0' || *s > '9') && *s != '\0'; ++s);
    1437           0 :                 QString const op(QString::fromUtf8(start, static_cast<int>(s - start)).trimmed());
    1438           0 :                 if(!vo.set_operator_string(op))
    1439             :                 {
    1440           0 :                     f_error = vo.get_error();
    1441           0 :                     return false;
    1442             :                 }
    1443           0 :                 operator_t const range_op(vo.get_operator());
    1444           0 :                 switch(range_op)
    1445             :                 {
    1446           0 :                 case operator_t::OPERATOR_UNORDERED:
    1447             :                 case operator_t::OPERATOR_EQUAL:
    1448             :                 case operator_t::OPERATOR_EXCEPT:
    1449           0 :                     f_error = "unsupported operator \"" + QString(vo.get_operator_string()) + "\" for a range";
    1450           0 :                     return false;
    1451             : 
    1452           0 :                 default:
    1453             :                     // otherwise we're good
    1454           0 :                     break;
    1455             : 
    1456             :                 }
    1457             :                 // skip spaces after the operator
    1458           0 :                 for(; isspace(*s); ++s);
    1459           0 :                 start = s;
    1460           0 :                 for(; (*s >= '0' && *s <= '9') || *s == '.'; ++s);
    1461           0 :                 if(*s != '\0')
    1462             :                 {
    1463           0 :                     f_error = "a version range can have two versions separated by an operator, \"" + vonly + "\" is not valid";
    1464           0 :                     return false;
    1465             :                 }
    1466           0 :                 QString const rhs_version(QString::fromUtf8(start, static_cast<int>(s - start)));
    1467           0 :                 version rhs_v;
    1468           0 :                 if(!rhs_v.set_version_string(rhs_version))
    1469             :                 {
    1470           0 :                     f_error = rhs_v.get_error();
    1471           0 :                     return false;
    1472             :                 }
    1473           0 :                 switch(range_op)
    1474             :                 {
    1475           0 :                 case operator_t::OPERATOR_EARLIER:
    1476           0 :                     vo.set_operator(operator_t::OPERATOR_LATER);
    1477           0 :                     v.set_operator(vo);
    1478           0 :                     f_versions.push_back(v);
    1479           0 :                     vo.set_operator(operator_t::OPERATOR_EARLIER);
    1480           0 :                     rhs_v.set_operator(vo);
    1481           0 :                     f_versions.push_back(rhs_v);
    1482           0 :                     if(v.compare(rhs_v) >= compare_t::COMPARE_EQUAL)
    1483             :                     {
    1484           0 :                         f_error = "versions are not in the correct order in range \"" + vonly + "\" since " + v.get_version_string() + " >= " + rhs_v.get_version_string();
    1485           0 :                         return false;
    1486             :                     }
    1487           0 :                     break;
    1488             : 
    1489           0 :                 case operator_t::OPERATOR_EARLIER_OR_EQUAL:
    1490           0 :                     vo.set_operator(operator_t::OPERATOR_LATER_OR_EQUAL);
    1491           0 :                     v.set_operator(vo);
    1492           0 :                     f_versions.push_back(v);
    1493           0 :                     vo.set_operator(operator_t::OPERATOR_EARLIER_OR_EQUAL);
    1494           0 :                     rhs_v.set_operator(vo);
    1495           0 :                     f_versions.push_back(rhs_v);
    1496           0 :                     if(v.compare(rhs_v) >= compare_t::COMPARE_EQUAL)
    1497             :                     {
    1498           0 :                         f_error = "versions are not in the correct order in range \"" + vonly + "\" since " + v.get_version_string() + " >= " + rhs_v.get_version_string();
    1499           0 :                         return false;
    1500             :                     }
    1501           0 :                     break;
    1502             : 
    1503           0 :                 case operator_t::OPERATOR_LATER:
    1504           0 :                     vo.set_operator(operator_t::OPERATOR_LATER);
    1505           0 :                     rhs_v.set_operator(vo);
    1506           0 :                     f_versions.push_back(rhs_v);
    1507           0 :                     vo.set_operator(operator_t::OPERATOR_EARLIER);
    1508           0 :                     v.set_operator(vo);
    1509           0 :                     f_versions.push_back(v);
    1510           0 :                     if(v.compare(rhs_v) <= compare_t::COMPARE_EQUAL)
    1511             :                     {
    1512           0 :                         f_error = "versions are not in the correct order in range \"" + vonly + "\" since " + v.get_version_string() + " <= " + rhs_v.get_version_string();
    1513           0 :                         return false;
    1514             :                     }
    1515           0 :                     break;
    1516             : 
    1517           0 :                 case operator_t::OPERATOR_LATER_OR_EQUAL:
    1518           0 :                     vo.set_operator(operator_t::OPERATOR_LATER_OR_EQUAL);
    1519           0 :                     rhs_v.set_operator(vo);
    1520           0 :                     f_versions.push_back(rhs_v);
    1521           0 :                     vo.set_operator(operator_t::OPERATOR_EARLIER_OR_EQUAL);
    1522           0 :                     v.set_operator(vo);
    1523           0 :                     f_versions.push_back(v);
    1524           0 :                     if(v.compare(rhs_v) <= compare_t::COMPARE_EQUAL)
    1525             :                     {
    1526           0 :                         f_error = "versions are not in the correct order in range \"" + vonly + "\" since " + v.get_version_string() + " <= " + rhs_v.get_version_string();
    1527           0 :                         return false;
    1528             :                     }
    1529           0 :                     break;
    1530             : 
    1531           0 :                 default:
    1532           0 :                     throw snap_logic_exception("unexpected operators in this second switch(range_op) statement");
    1533             : 
    1534             :                 }
    1535             :             }
    1536             :             else
    1537             :             {
    1538           0 :                 if(vo.get_operator() == operator_t::OPERATOR_UNORDERED)
    1539             :                 {
    1540             :                     // default operator is '>='
    1541           0 :                     vo.set_operator(operator_t::OPERATOR_LATER_OR_EQUAL);
    1542             :                 }
    1543           0 :                 v.set_operator(vo);
    1544           0 :                 f_versions.push_back(v);
    1545             :             }
    1546             :         }
    1547             : 
    1548             :         // skip the version including the ')'
    1549           0 :         pos = end + 1;
    1550             : 
    1551             :         // skip the spaces (because of the simplied there should be 1 at most)
    1552           0 :         while(pos < d.length() && isspace(d.at(pos).unicode()))
    1553             :         {
    1554           0 :             ++pos;
    1555             :         }
    1556             :     }
    1557             : 
    1558             :     // read list of browsers
    1559           0 :     if(pos < d.length() && d.at(pos) == '[')
    1560             :     {
    1561           0 :         ++pos;
    1562           0 :         int end(d.indexOf(']'));
    1563           0 :         if(end == -1)
    1564             :         {
    1565             :             // we could just emit some kind of a warning but then we may not
    1566             :             // be able to support additional features later...
    1567           0 :             f_error = "Invalid browser dependency list, the list of browsers must end with a ']'";
    1568           0 :             return false;
    1569             :         }
    1570           0 :         QString const browsers(d.mid(pos, end - pos));
    1571           0 :         snap_string_list const browser_list(browsers.split(',', QString::SkipEmptyParts));
    1572           0 :         int const max_size(browser_list.size());
    1573           0 :         for(int i(0); i < max_size; ++i)
    1574             :         {
    1575           0 :             QString const bn(browser_list[i].trimmed());
    1576           0 :             if(bn.isEmpty())
    1577             :             {
    1578             :                 // this happens because of the trimmed()
    1579           0 :                 continue;
    1580             :             }
    1581           0 :             name_string = bn;
    1582           0 :             if(!validate_name(name_string, f_error, namespace_string))
    1583             :             {
    1584           0 :                 return false;
    1585             :             }
    1586           0 :             name browser;
    1587           0 :             browser.set_name(bn);
    1588           0 :             f_browsers.push_back(browser);
    1589             :         }
    1590             : 
    1591           0 :         pos = end + 1;
    1592             : 
    1593             :         // skip the spaces (because of the simplied there should be none here)
    1594           0 :         while(pos < d.length() && isspace(d.at(pos).unicode()))
    1595             :         {
    1596           0 :             ++pos;
    1597             :         }
    1598             :     }
    1599             : 
    1600           0 :     if(pos != d.length())
    1601             :     {
    1602           0 :         f_error = QString("left over data at the end of the dependency string \"%1\"").arg(dependency_string);
    1603           0 :         return false;
    1604             :     }
    1605             : 
    1606           0 :     return true;
    1607             : }
    1608             : 
    1609             : 
    1610             : /** \brief Get the canonicalized dependency string.
    1611             :  *
    1612             :  * When you set the dependency string with set_dependency() the string
    1613             :  * may miss some spaces or include additional spaces, some versions may
    1614             :  * end with ".0" or some numbers start with 0 (i.e. "5.03") and
    1615             :  * additional commas may be found in lists of versions and browsers.
    1616             :  *
    1617             :  * This function returned a fully cleaned up string with the dependency
    1618             :  * information as intended by the specification.
    1619             :  */
    1620           0 : QString dependency::get_dependency_string() const
    1621             : {
    1622           0 :     QString dep;
    1623             :     
    1624           0 :     if(!f_name.get_namespace().isEmpty())
    1625             :     {
    1626           0 :         dep = QString("%1::").arg(f_name.get_namespace());
    1627             :     }
    1628             : 
    1629           0 :     dep += f_name.get_name();
    1630             : 
    1631           0 :     int const max_versions(f_versions.count());
    1632           0 :     if(max_versions > 0)
    1633             :     {
    1634           0 :         dep += " (" + f_versions[0].get_opversion_string();
    1635           0 :         for(int i(1); i < max_versions; ++i)
    1636             :         {
    1637           0 :             dep += ", " + f_versions[i].get_opversion_string();
    1638             :         }
    1639           0 :         dep += ")";
    1640             :     }
    1641             : 
    1642           0 :     int const max_browsers(f_browsers.count());
    1643           0 :     if(max_browsers > 0)
    1644             :     {
    1645           0 :         dep += " [" + f_browsers[0].get_name();
    1646           0 :         for(int i(1); i < max_browsers; ++i)
    1647             :         {
    1648           0 :             dep += ", " + f_browsers[i].get_name();
    1649             :         }
    1650           0 :         dep += "]";
    1651             :     }
    1652             : 
    1653           0 :     return dep;
    1654             : }
    1655             : 
    1656             : 
    1657             : /** \brief Check the validity of a dependency declaration.
    1658             :  *
    1659             :  * This function retrieves the validity of the dependency.
    1660             :  *
    1661             :  * This includes the validity of the dependency object itself, the name,
    1662             :  * all the versions, and all the browser names.
    1663             :  *
    1664             :  * \return true if all the information is considered valid.
    1665             :  */
    1666           0 : bool dependency::is_valid() const
    1667             : {
    1668           0 :     if(!f_error.isEmpty() || !f_name.is_valid())
    1669             :     {
    1670           0 :         return false;
    1671             :     }
    1672             : 
    1673             :     // loop through all the versions
    1674           0 :     int const max_versions(f_versions.size());
    1675           0 :     for(int i(0); i < max_versions; ++i)
    1676             :     {
    1677           0 :         if(!f_versions[i].is_valid())
    1678             :         {
    1679           0 :             return false;
    1680             :         }
    1681             :     }
    1682             : 
    1683             :     // loop through all the browsers
    1684           0 :     int const max_browsers(f_browsers.size());
    1685           0 :     for(int i(0); i < max_browsers; ++i)
    1686             :     {
    1687           0 :         if(!f_browsers[i].is_valid())
    1688             :         {
    1689           0 :             return false;
    1690             :         }
    1691             :     }
    1692             : 
    1693           0 :     return true;
    1694             : }
    1695             : 
    1696             : 
    1697             : /** \brief Function to quickly find the Version and Browsers fields.
    1698             :  *
    1699             :  * This function initializes the Quick Find Version in Source object
    1700             :  * with a pointer to the input data and the size of the buffer.
    1701             :  *
    1702             :  * The find_version() function is expected to be called afterward to
    1703             :  * get the Version and Browsers fields. The validity of those fields
    1704             :  * is also getting checked when found. The Browsers field is optional,
    1705             :  * however the Version field is mandatory.
    1706             :  *
    1707             :  * The source is expected to be UTF-8.
    1708             :  */
    1709           0 : quick_find_version_in_source::quick_find_version_in_source()
    1710             :     //: f_data(nullptr) -- auto-init
    1711             :     //, f_end(nullptr) -- auto-init
    1712             :     //, f_name() -- auto-init
    1713             :     //, f_layout() -- auto-init
    1714             :     //, f_version("") -- auto-init
    1715             :     //, f_browsers("") -- auto-init
    1716             :     //, f_error("") -- auto-init
    1717             :     //, f_description("") -- auto-init
    1718             :     //, f_depends() -- auto-init
    1719             : {
    1720           0 : }
    1721             : 
    1722             : 
    1723             : /** \brief Search for the Version and other fields.
    1724             :  *
    1725             :  * This function reads the file. It must start with a C-like comment (a.
    1726             :  * slash (/) and an asterisk (*)).
    1727             :  *
    1728             :  * The C-like comment can include any number of fields. On a line you want
    1729             :  * to include a field name, a colon, followed by a value. For example, the
    1730             :  * version field is defined as:
    1731             :  *
    1732             :  * Version: 1.2.3
    1733             :  *
    1734             :  * And the browsers field is defined as a list of browser names:
    1735             :  *
    1736             :  * Browsers: ie, firefox, opera
    1737             :  *
    1738             :  * The list of browsers is used to select code using a C-like preprocessor
    1739             :  * in the .js and .css files. That allows us to not have to use tricks to
    1740             :  * support different browsers with very similar but different enough code
    1741             :  * for different browsers.
    1742             :  *
    1743             :  * \param[in] data  The pointer to the string to be parsed.
    1744             :  * \param[in] size  The size (number of bytes) of the string to be parsed.
    1745             :  *
    1746             :  * \return true if the function succeeded, false otherwise and an error is
    1747             :  *         set which can be retrieved with get_error()
    1748             :  */
    1749           0 : bool quick_find_version_in_source::find_version(char const *data, int const size)
    1750             : {
    1751             :     class field_t
    1752             :     {
    1753             :     public:
    1754           0 :         field_t(char const *name)
    1755           0 :             : f_name(name)
    1756             :         {
    1757           0 :         }
    1758             : 
    1759           0 :         bool check(QString const & line, QString & value)
    1760             :         {
    1761             :             // find the field name if available
    1762             : 
    1763             :             // skip spaces at the beginning of the line
    1764           0 :             unsigned int const max_length(line.length());
    1765             :             unsigned int pos;
    1766           0 :             for(pos = 0; pos < max_length; ++pos)
    1767             :             {
    1768           0 :                 if(!isspace(line.at(pos).unicode()))
    1769             :                 {
    1770           0 :                     break;
    1771             :                 }
    1772             :             }
    1773             : 
    1774             :             // C-like comments most often have " * " at the start of the line
    1775           0 :             if(line.at(pos).unicode() == '*')
    1776             :             {
    1777             :                 // skip spaces after the '*'
    1778           0 :                 for(++pos; pos < max_length; ++pos)
    1779             :                 {
    1780           0 :                     if(!isspace(line.at(pos).unicode()))
    1781             :                     {
    1782           0 :                         break;
    1783             :                     }
    1784             :                 }
    1785             :             }
    1786             : 
    1787             :             // compare with the name of this field
    1788           0 :             char const *n(f_name);
    1789           0 :             for(; pos < max_length && *n != '\0'; ++pos, ++n)
    1790             :             {
    1791           0 :                 int c(line.at(pos).unicode());
    1792           0 :                 if(c >= 'a' && c <= 'z')
    1793             :                 {
    1794             :                     // uppercase
    1795           0 :                     c &= 0x5F;
    1796             :                 }
    1797           0 :                 if(c != *n)
    1798             :                 {
    1799             :                     // no equal...
    1800           0 :                     return false;
    1801             :                 }
    1802             :             }
    1803             : 
    1804             :             // make sure there is a colon after the name
    1805           0 :             if(pos >= max_length
    1806           0 :             || line.at(pos).unicode() != ':')
    1807             :             {
    1808           0 :                 return false;
    1809             :             }
    1810             : 
    1811             :             // got a field, save it in value
    1812           0 :             value = line.mid(pos + 1).simplified();
    1813             : 
    1814           0 :             return true;
    1815             :         }
    1816             : 
    1817             :     private:
    1818             :         char const *    f_name;
    1819             :     };
    1820             : 
    1821           0 :     f_data = data;
    1822           0 :     f_end = data + size;
    1823             : 
    1824           0 :     QString l(get_line());
    1825           0 :     if(!l.startsWith("/*"))
    1826             :     {
    1827             :         // C comment must appear first
    1828           0 :         f_error = "file does not start with a C-like comment";
    1829           0 :         return false;
    1830             :     }
    1831             : 
    1832             :     // note: field names are case insensitive
    1833           0 :     field_t field_name("NAME");
    1834           0 :     field_t field_layout("LAYOUT");
    1835           0 :     field_t field_version("VERSION");
    1836           0 :     field_t field_browsers("BROWSERS");
    1837           0 :     field_t field_description("DESCRIPTION");
    1838           0 :     field_t field_depends("DEPENDS");
    1839             :     for(;;)
    1840             :     {
    1841           0 :         QString value;
    1842           0 :         if(field_name.check(l, value))
    1843             :         {
    1844           0 :             if(!f_name.get_name().isEmpty())
    1845             :             {
    1846           0 :                 f_error = "name field cannot be defined more than once";
    1847           0 :                 return false;
    1848             :             }
    1849           0 :             if(!f_name.set_name(value))
    1850             :             {
    1851           0 :                 f_error = f_name.get_error();
    1852           0 :                 return false;
    1853             :             }
    1854             :         }
    1855           0 :         if(field_layout.check(l, value))
    1856             :         {
    1857           0 :             if(!f_layout.get_name().isEmpty())
    1858             :             {
    1859           0 :                 f_error = "layout field cannot be defined more than once";
    1860           0 :                 return false;
    1861             :             }
    1862           0 :             if(!f_layout.set_name(value))
    1863             :             {
    1864           0 :                 f_error = f_layout.get_error();
    1865           0 :                 return false;
    1866             :             }
    1867             :         }
    1868           0 :         else if(field_version.check(l, value))
    1869             :         {
    1870           0 :             if(!f_version.get_version_string().isEmpty())
    1871             :             {
    1872             :                 // more than one Version field
    1873           0 :                 f_error = "version field cannot be defined more than once";
    1874           0 :                 return false;
    1875             :             }
    1876           0 :             if(!f_version.set_version_string(value))
    1877             :             {
    1878           0 :                 f_error = f_version.get_error();
    1879           0 :                 return false;
    1880             :             }
    1881             :         }
    1882           0 :         else if(field_browsers.check(l, value))
    1883             :         {
    1884           0 :             if(!f_browsers.isEmpty())
    1885             :             {
    1886             :                 // more than one Browsers field
    1887           0 :                 f_error = "browser field cannot be defined more than once";
    1888           0 :                 return false;
    1889             :             }
    1890           0 :             snap_string_list browser_list(value.split(','));
    1891           0 :             int const max_size(browser_list.size());
    1892           0 :             for(int i(0); i < max_size; ++i)
    1893             :             {
    1894           0 :                 name browser;
    1895           0 :                 if(!browser.set_name(browser_list[i].trimmed()))
    1896             :                 {
    1897           0 :                     f_error = browser.get_error();
    1898           0 :                     return false;
    1899             :                 }
    1900           0 :                 f_browsers.push_back(browser);
    1901             :             }
    1902             :         }
    1903           0 :         else if(field_description.check(l, value))
    1904             :         {
    1905           0 :             if(!f_description.isEmpty())
    1906             :             {
    1907             :                 // more than one Description field
    1908           0 :                 f_error = "description field cannot be defined more than once";
    1909           0 :                 return false;
    1910             :             }
    1911             :             // description can be anything
    1912           0 :             f_description = value;
    1913             :         }
    1914           0 :         else if(field_depends.check(l, value))
    1915             :         {
    1916           0 :             if(!f_depends.isEmpty())
    1917             :             {
    1918             :                 // more than one Depends field
    1919           0 :                 f_error = "depends field cannot be defined more than once";
    1920           0 :                 return false;
    1921             :             }
    1922             :             // parse dependencies one by one
    1923           0 :             if(!value.isEmpty())
    1924             :             {
    1925           0 :                 int paren(0);
    1926           0 :                 int brack(0);
    1927           0 :                 QChar const *start(value.data());
    1928           0 :                 for(QChar const *s(start);; ++s)
    1929             :                 {
    1930           0 :                     switch(s->unicode())
    1931             :                     {
    1932           0 :                     case '(':
    1933           0 :                         ++paren;
    1934           0 :                         break;
    1935             : 
    1936           0 :                     case ')':
    1937           0 :                         --paren;
    1938           0 :                         break;
    1939             : 
    1940           0 :                     case '[':
    1941           0 :                         ++brack;
    1942           0 :                         break;
    1943             : 
    1944           0 :                     case ']':
    1945           0 :                         --brack;
    1946           0 :                         break;
    1947             : 
    1948           0 :                     case ',':
    1949             :                     case '\0':
    1950           0 :                         if(paren == 0 && brack == 0)
    1951             :                         {
    1952             :                             // got one!
    1953           0 :                             size_t const len(s - start);
    1954           0 :                             if(len > 0) // ignore empty entries
    1955             :                             {
    1956           0 :                                 dependency d;
    1957           0 :                                 QString dep(start, static_cast<int>(s - start));
    1958           0 :                                 d.set_dependency(dep);
    1959           0 :                                 f_depends.push_back(d);
    1960             :                             }
    1961           0 :                             start = s;
    1962           0 :                             if(s->unicode() == ',')
    1963             :                             {
    1964             :                                 // skip the comma
    1965           0 :                                 ++start;
    1966           0 :                             }
    1967             :                         }
    1968           0 :                         else if(s->unicode() == '\0')
    1969             :                         {
    1970             :                             // parenthesis or brackets mismatched
    1971           0 :                             f_error = "depends field () or [] mismatch";
    1972           0 :                             return false;
    1973             :                         }
    1974           0 :                         break;
    1975             : 
    1976             :                     }
    1977             :                     // by doing this test here we avoid having to duplicate
    1978             :                     // the case when we save a dependency
    1979           0 :                     if(s->unicode() == '\0')
    1980             :                     {
    1981           0 :                         break;
    1982             :                     }
    1983           0 :                 }
    1984             :             }
    1985             :         }
    1986           0 :         if(l.contains("*/"))
    1987             :         {
    1988             :             // stop with the end of the comment
    1989             :             // return true only if the version was specified
    1990           0 :             bool result(!f_version.get_version_string().isEmpty());
    1991           0 :             if(result && f_browsers.isEmpty())
    1992             :             {
    1993             :                 // always have some browsers, "all" if nothing else
    1994           0 :                 name browser;
    1995           0 :                 browser.set_name("all");
    1996           0 :                 f_browsers.push_back(browser);
    1997             :             }
    1998           0 :             return result;
    1999             :         }
    2000           0 :         l = get_line();
    2001           0 :     }
    2002             : }
    2003             : 
    2004             : 
    2005             : /** \brief Get one character from the input file.
    2006             :  *
    2007             :  * This function reads one byte from the file and returns it.
    2008             :  *
    2009             :  * \return The next character or EOF at the end of the file.
    2010             :  */
    2011           0 : int quick_find_version_in_source::getc()
    2012             : {
    2013           0 :     if(f_data >= f_end)
    2014             :     {
    2015           0 :         return EOF;
    2016             :     }
    2017             :     // note: UTF-8 can be ignored because what we are interested
    2018             :     //       in is using ASCII only
    2019           0 :     return *f_data++;
    2020             : }
    2021             : 
    2022             : 
    2023             : /** \brief Get a line of text.
    2024             :  *
    2025             :  * This function reads the next line. Empty lines are skipped and not
    2026             :  * returned unless the end of the file is reached. In that case, the
    2027             :  * function returns anyway.
    2028             :  *
    2029             :  * \return The line just read or an empty string if the end of the line.
    2030             :  */
    2031           0 : QString quick_find_version_in_source::get_line()
    2032             : {
    2033           0 :     int c(0);
    2034             :     for(;;)
    2035             :     {
    2036           0 :         std::string raw;
    2037             :         for(;;)
    2038             :         {
    2039           0 :             c = getc();
    2040           0 :             if(c == '\n' || c == '\r' || c == EOF)
    2041             :             {
    2042             :                 break;
    2043             :             }
    2044           0 :             raw += static_cast<char>(c);
    2045             :         }
    2046             :         // we need to support UTF-8 properly for descriptions
    2047           0 :         QString line(QString::fromUtf8(raw.c_str()).trimmed());
    2048           0 :         if(!line.isEmpty() || c == EOF)
    2049             :         {
    2050             :             // do not return empty line unless we reached the
    2051             :             // end of the file
    2052           0 :             return line;
    2053             :         }
    2054           0 :     }
    2055             :     NOTREACHED();
    2056             :     return "";
    2057             : }
    2058             : 
    2059             : 
    2060             : /** \brief Check whether the object is valid.
    2061             :  *
    2062             :  * This function returns true if all the data in this object is valid.
    2063             :  */
    2064           0 : bool quick_find_version_in_source::is_valid() const
    2065             : {
    2066             :     // first check internal values
    2067           0 :     if(!f_error.isEmpty()
    2068           0 :     || !f_name.is_valid()
    2069           0 :     || !f_version.is_valid())
    2070             :     {
    2071           0 :         return false;
    2072             :     }
    2073             : 
    2074             :     // check each browser name
    2075           0 :     int const max_size(f_browsers.size());
    2076           0 :     for(int i(0); i < max_size; ++i)
    2077             :     {
    2078           0 :         if(!f_browsers[i].is_valid())
    2079             :         {
    2080           0 :             return false;
    2081             :         }
    2082             :     }
    2083             : 
    2084           0 :     return true;
    2085             : }
    2086             : 
    2087             : 
    2088             : } // namespace snap_version
    2089           6 : } // namespace snap
    2090             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13