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 size_t blockLen = normalizeVectors(blocks1, blocks2);
43
44 for (size_t i = 0; i < blockLen; ++i) {
45 vector<string> tokens1;
46 vector<string> tokens2;
47 split(blocks1[i], '.', tokens1);
48 split(blocks2[i], '.', tokens2);
49 size_t tokLen = normalizeVectors(tokens1, tokens2);
50
51 for (size_t 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 size_t subTokLen = normalizeVectors(subtokens1, subtokens2);
71 for (size_t 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 size_t normalizeVectors(vector<string>& v1, vector<string>& v2)
127 {
128 size_t 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 (size_t 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 (size_t 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
|