summaryrefslogtreecommitdiff
path: root/src/versioncomparator.cpp
blob: 6116a97ebb2c70c85c60c5daff9e552c2bc6812d (plain)
    1 ////////////////////////////////////////////////////////////////////////
    2 // FILE:        versioncomparator.cpp
    3 // AUTHOR:      Johannes Winkelmann, jw@tks6.net
    4 // COPYRIGHT:   (c) 2004 by Johannes Winkelmann
    5 // ---------------------------------------------------------------------
    6 //  This program is free software; you can redistribute it and/or modify
    7 //  it under the terms of the GNU General Public License as published by
    8 //  the Free Software Foundation; either version 2 of the License, or
    9 //  (at your option) any later version.
   10 ////////////////////////////////////////////////////////////////////////
   11 
   12 // Get a test application with the following command:
   13 // g++ -o vcomp -DTEST stringhelper.cpp versioncomparator.cpp
   14 
   15 
   16 #include <string>
   17 #include <cctype>
   18 #include <vector>
   19 #include <iostream>
   20 #include <stdlib.h>
   21 #include <cstring>
   22 using namespace std;
   23 
   24 #include "stringhelper.h"
   25 using namespace StringHelper;
   26 
   27 #include "versioncomparator.h"
   28 
   29 namespace VersionComparator
   30 {
   31 
   32 // this is a one day hack; I'll refactor that to be nicer, clearer and
   33 // hopefully faster. It should work pretty well though
   34 
   35 COMP_RESULT compareVersions(const string& v1, const string& v2)
   36 {
   37     vector<string> blocks1;
   38     vector<string> blocks2;
   39 
   40     tokenizeIntoBlocks(v1, blocks1);
   41     tokenizeIntoBlocks(v2, blocks2);
   42     int blockLen = normalizeVectors(blocks1, blocks2);
   43 
   44     for (int i = 0; i < blockLen; ++i) {
   45         vector<string> tokens1;
   46         vector<string> tokens2;
   47         split(blocks1[i], '.', tokens1);
   48         split(blocks2[i], '.', tokens2);
   49         int tokLen = normalizeVectors(tokens1, tokens2);
   50 
   51         for (int j = 0; j < tokLen; ++j) {
   52             if (tokens1[j] == tokens2[j]) {
   53                 continue;
   54             }
   55 
   56             char* error1 = 0;
   57             char* error2 = 0;
   58             error1 = 0;
   59             error2 = 0;
   60             long vl1 = strtol(tokens1[j].c_str(), &error1, 10);
   61             long vl2 = strtol(tokens2[j].c_str(), &error2, 10);
   62 
   63             if (*error1 != 0 || *error2 != 0) {
   64                 // subtokenize
   65                 vector<string> subtokens1;
   66                 vector<string> subtokens2;
   67 
   68                 tokenizeMixed(tokens1[j], subtokens1);
   69                 tokenizeMixed(tokens2[j], subtokens2);
   70                 int subTokLen = normalizeVectors(subtokens1, subtokens2);
   71                 for (int k = 0; k < subTokLen; ++k) {
   72                     long sl1 = strtol(subtokens1[k].c_str(), &error1, 10);
   73                     long sl2 = strtol(subtokens2[k].c_str(), &error2, 10);
   74                     if (*error1 == 0 && *error2 == 0) {
   75                         if (sl1 < sl2) {
   76                             return LESS;
   77                         } else if (sl1 > sl2) {
   78                             return GREATER;
   79                         }
   80                     } else {
   81 
   82                         // string tokens
   83                         if (subtokens1[k][1] == 0 && subtokens2[k][1] == 0) {
   84 
   85                             if (subtokens1[k][0] < subtokens2[k][0]) {
   86                                 return LESS;
   87                             } else if (subtokens1[k][0] > subtokens2[k][0]) {
   88                                 return GREATER;
   89                             }
   90                         } else {
   91                             // smart guessing...
   92                             // leaving out 'test', 'pre' and 'rc'
   93                             static const string versions =
   94                                 "alpha beta gamma delta";
   95                             string::size_type pos1 =
   96                                 versions.find(subtokens1[k]);
   97                             string::size_type pos2 =
   98                                 versions.find(subtokens2[k]);
   99                             if (pos1 != string::npos && pos2 != string::npos) {
  100                                 if (pos1 < pos2) {
  101                                     return LESS;
  102                                 } else if (pos1 > pos2) {
  103                                     return GREATER;
  104                                 }
  105                             }
  106                         }
  107 
  108                         if (subtokens1[k] != subtokens2[k]) {
  109                             return UNDEFINED;
  110                         }
  111                     }
  112                 }
  113 
  114             } else if (vl1 < vl2) {
  115                 return LESS;
  116             } else if (vl1 > vl2) {
  117                 return GREATER;
  118             }
  119         }
  120     }
  121 
  122 
  123     return EQUAL;
  124 }
  125 
  126 int normalizeVectors(vector<string>& v1, vector<string>& v2)
  127 {
  128     int length = max(v1.size(), v2.size());
  129 
  130     while (v1.size() < length) {
  131         v1.push_back("-1");
  132     }
  133     while (v2.size() < length) {
  134         v2.push_back("-1");
  135     }
  136 
  137     return length;
  138 }
  139 
  140 void tokenizeMixed(const string& s, vector<string>& tokens)
  141 {
  142     vector<bool> digitMask;
  143     for (int i = 0; i < s.length(); ++i) {
  144         digitMask.push_back(isdigit(s[i]));
  145     }
  146 
  147     bool state = digitMask[0];
  148     string tok;
  149     tok = s[0];
  150     for (int i = 1; i < digitMask.size(); ++i) {
  151         if (digitMask[i] != state) {
  152             tokens.push_back(tok);
  153             tok = s[i];
  154             state = digitMask[i];
  155         } else {
  156             tok += s[i];
  157         }
  158     }
  159     if (tok.length() > 0) {
  160         tokens.push_back(tok);
  161     }
  162 }
  163 
  164 /*
  165   find last - (for release) -> version, release
  166   subdivide version in blocks, where a block is separated by any of
  167   the following: [-_]
  168 
  169   -> list of blocks, e.g.
  170   .  1.4.2-pre1-2 ->  [ (1.4.2) (-pre1) (2) ]
  171   .  1.4.2pre1-1  ->  [ (1.4.2pre1) (-1) ]
  172   .  1_2_2pre2-2  ->  [ (1) (2) (2pre2) (2)
  173 */
  174 void tokenizeIntoBlocks(const string& version, vector<string>& blocks)
  175 {
  176     string v = version;
  177     v = replaceAll(v, "-", "_");
  178     split(v, '_', blocks);
  179 }
  180 
  181 }
  182 
  183 
  184 #ifdef TEST
  185 
  186 void check(const string& v1, const string& v2,
  187            VersionComparator::COMP_RESULT expected, bool compare=true)
  188 {
  189     VersionComparator::COMP_RESULT result =
  190         VersionComparator::compareVersions(v1, v2);
  191 
  192     if (compare) {
  193         cout << ((result == expected) ? "OK   " : "FAIL ");
  194     }
  195 
  196     cout << v1 << " ";
  197     switch (result)
  198     {
  199         case VersionComparator::LESS:
  200             cout << "<";
  201             break;
  202         case VersionComparator::GREATER:
  203             cout << ">";
  204             break;
  205         case VersionComparator::EQUAL:
  206             cout << "=";
  207             break;
  208         case VersionComparator::UNDEFINED:
  209             cout << "?";
  210             break;
  211     }
  212 
  213     cout << " " << v2 << endl;
  214 }
  215 
  216 
  217 
  218 int main(int argc, char** argv)
  219 {
  220     using namespace VersionComparator;
  221 
  222     if (argc < 3) {
  223         check("1", "2", LESS);
  224         check("1.1", "1.2", LESS);
  225         check("1.1pre1", "1.1pre2", LESS);
  226         check("1.1pre1", "1.2pre1", LESS);
  227         check("1.1-pre1", "1.1-pre2", LESS);
  228         check("1.1_2", "1.1.2", LESS);
  229         check("1.1", "1.1", EQUAL);
  230 
  231         check("1.0PR1", "1.0PR1", EQUAL);
  232         check("1.0PR1", "1.0PR2", LESS);
  233         check("1.0PR1", "1.0RC1", UNDEFINED);
  234 
  235         check("1.2.3-2", "1.2.3-1", GREATER);
  236         check("1.0.0", "0.9", GREATER);
  237 
  238         check("1.4.2_3-1", "1.4.3-2", LESS);
  239         check("1.4.2_3-1", "1.4.2_3-2", LESS);
  240         check("1.4.2_3-1", "1.4.2_1-1", GREATER);
  241 
  242         check("1.4.2-alpha2", "1.4.2-beta1", LESS);
  243         check("1.4.2a-2", "1.4.2a-3", LESS);
  244         check("1.4.2a-2", "1.4.2b-2", LESS);
  245         check("1.4.2aa-2", "1.4.2bb-2", UNDEFINED);
  246         check("1.4.2a1-2", "1.4.2a2-2", LESS);
  247         check("1.4.2b1-2", "1.4.2a2-2", GREATER);
  248         check("1.4.2beta3", "1.4.2alpha2", GREATER);
  249         check("1.4.2-some", "1.4.2-1", UNDEFINED);
  250         check("1.4.2-1", "1.4.2-some", UNDEFINED);
  251 
  252         check("7.0r63-3", "7.0r68-1", LESS);
  253         check("27", "28e", LESS);
  254         
  255     } else {
  256         check(argv[1], argv[2], UNDEFINED, false);
  257     }
  258 }
  259 #endif

Generated by cgit