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