1 /*********************************************************
2 * *
3 * (c) 2011-2015 Marko Lindqvist *
4 * *
5 * Version contributed to Freeciv has been made available *
6 * under GNU Public License; either version 2, or *
7 * (at your option) any later version. *
8 * *
9 *********************************************************/
10
11 #ifdef HAVE_CONFIG_H
12 #include <fc_config.h>
13 #endif
14
15 #include <assert.h>
16 #include <ctype.h>
17 #include <stdbool.h>
18 #include <stdlib.h>
19 #include <string.h>
20
21 #include "cvercmp.h"
22
23 static char **cvercmp_ver_tokenize(const char *ver);
24 static int cvercmp_next_token(const char *str);
25 static char **cvercmp_ver_subtokenize(const char *ver);
26 static int cvercmp_next_subtoken(const char *str);
27
28 enum cvercmp_prever
29 {
30 CVERCMP_PRE_DEV,
31 CVERCMP_PRE_ALPHA,
32 CVERCMP_PRE_BETA,
33 CVERCMP_PRE_PRE,
34 CVERCMP_PRE_RC,
35 CVERCMP_PRE_NONE
36 };
37
38 struct preverstr
39 {
40 const char *str;
41 enum cvercmp_prever prever;
42 };
43
44 struct preverstr preverstrs[] =
45 {
46 { "dev", CVERCMP_PRE_DEV },
47 { "alpha", CVERCMP_PRE_ALPHA },
48 { "beta", CVERCMP_PRE_BETA },
49 { "pre", CVERCMP_PRE_PRE },
50 { "candidate", CVERCMP_PRE_RC },
51 { "rc", CVERCMP_PRE_RC },
52 { NULL, CVERCMP_PRE_NONE }
53 };
54
55 static enum cvercmp_prever cvercmp_parse_prever(const char *ver);
56
cvercmp(const char * ver1,const char * ver2,enum cvercmp_type type)57 bool cvercmp(const char *ver1, const char *ver2, enum cvercmp_type type)
58 {
59 typedef bool (*cmpfunc)(const char *ver1, const char *ver2);
60
61 cmpfunc cmpfuncs[] =
62 {
63 cvercmp_equal,
64 cvercmp_nonequal,
65 cvercmp_greater,
66 cvercmp_lesser,
67 cvercmp_min,
68 cvercmp_max
69 };
70
71 return cmpfuncs[type](ver1, ver2);
72 }
73
cvercmp_equal(const char * ver1,const char * ver2)74 bool cvercmp_equal(const char *ver1, const char *ver2)
75 {
76 return cvercmp_cmp(ver1, ver2) == CVERCMP_EQUAL;
77 }
78
cvercmp_nonequal(const char * ver1,const char * ver2)79 bool cvercmp_nonequal(const char *ver1, const char *ver2)
80 {
81 return cvercmp_cmp(ver1, ver2) != CVERCMP_EQUAL;
82 }
83
cvercmp_greater(const char * ver1,const char * ver2)84 bool cvercmp_greater(const char *ver1, const char *ver2)
85 {
86 return cvercmp_cmp(ver1, ver2) == CVERCMP_GREATER;
87 }
88
cvercmp_lesser(const char * ver1,const char * ver2)89 bool cvercmp_lesser(const char *ver1, const char *ver2)
90 {
91 return cvercmp_cmp(ver1, ver2) == CVERCMP_LESSER;
92 }
93
cvercmp_min(const char * ver1,const char * ver2)94 bool cvercmp_min(const char *ver1, const char *ver2)
95 {
96 return cvercmp_cmp(ver1, ver2) != CVERCMP_LESSER;
97 }
98
cvercmp_max(const char * ver1,const char * ver2)99 bool cvercmp_max(const char *ver1, const char *ver2)
100 {
101 return cvercmp_cmp(ver1, ver2) != CVERCMP_GREATER;
102 }
103
cvercmp_tokens(const char * token1,const char * token2)104 static enum cvercmp_type cvercmp_tokens(const char *token1, const char *token2)
105 {
106 char **t1 = cvercmp_ver_subtokenize(token1);
107 char **t2 = cvercmp_ver_subtokenize(token2);
108 int i;
109 enum cvercmp_type result = CVERCMP_EQUAL;
110 bool solution = false;
111
112 for (i = 0; (t1[i] != NULL && t2[i] != NULL) && !solution; i++) {
113 int t1c0 = t1[i][0];
114 int t2c0 = t2[i][0];
115 bool d1 = isdigit(t1c0);
116 bool d2 = isdigit(t2c0);
117
118 if (d1 && !d2) {
119 /* Numbers are always greater than alpha, so don't need to check prever */
120 result = CVERCMP_GREATER;
121 solution = true;
122 } else if (!d1 && d2) {
123 result = CVERCMP_LESSER;
124 solution = true;
125 } else if (d1) {
126 int val1 = atoi(t1[i]);
127 int val2 = atoi(t2[i]);
128
129 if (val1 > val2) {
130 result = CVERCMP_GREATER;
131 solution = true;
132 } else if (val1 < val2) {
133 result = CVERCMP_LESSER;
134 solution = true;
135 }
136 } else {
137 int alphacmp = strcasecmp(t1[i], t2[i]);
138
139 if (alphacmp) {
140 enum cvercmp_prever pre1 = cvercmp_parse_prever(t1[i]);
141 enum cvercmp_prever pre2 = cvercmp_parse_prever(t2[i]);
142
143 if (pre1 > pre2) {
144 result = CVERCMP_GREATER;
145 } else if (pre1 < pre2) {
146 result = CVERCMP_LESSER;
147 } else if (alphacmp < 0) {
148 result = CVERCMP_LESSER;
149 } else if (alphacmp > 0) {
150 result = CVERCMP_GREATER;
151 } else {
152 assert(false);
153 }
154
155 solution = true;
156 }
157 }
158 }
159
160 if (!solution) {
161 /* Got to the end of one token.
162 * Longer token is greater, unless the next subtoken is
163 * a prerelease string. */
164 if (t1[i] != NULL && t2[i] == NULL) {
165 if (cvercmp_parse_prever(t1[i]) != CVERCMP_PRE_NONE) {
166 result = CVERCMP_LESSER;
167 } else {
168 result = CVERCMP_GREATER;
169 }
170 } else if (t1[i] == NULL && t2[i] != NULL) {
171 if (cvercmp_parse_prever(t2[i]) != CVERCMP_PRE_NONE) {
172 result = CVERCMP_GREATER;
173 } else {
174 result = CVERCMP_LESSER;
175 }
176 } else {
177 /* Both ran out at the same time. */
178 result = CVERCMP_EQUAL;
179 }
180 }
181
182 for (i = 0; t1[i] != NULL; i++) {
183 free(t1[i]);
184 }
185 for (i = 0; t2[i] != NULL; i++) {
186 free(t2[i]);
187 }
188 free(t1);
189 free(t2);
190
191 return result;
192 }
193
cvercmp_cmp(const char * ver1,const char * ver2)194 enum cvercmp_type cvercmp_cmp(const char *ver1, const char *ver2)
195 {
196 enum cvercmp_type result = CVERCMP_EQUAL;
197 bool solution = false;
198 int i;
199 char **tokens1 = cvercmp_ver_tokenize(ver1);
200 char **tokens2 = cvercmp_ver_tokenize(ver2);
201
202 for (i = 0; (tokens1[i] != NULL && tokens2[i] != NULL) && !solution; i++) {
203 if (strcasecmp(tokens1[i], tokens2[i])) {
204 /* Parts are not equal */
205 result = cvercmp_tokens(tokens1[i], tokens2[i]);
206 solution = true;
207 }
208 }
209
210 if (!solution) {
211 /* Ran out of tokens in one string. */
212 result = cvercmp_tokens(tokens1[i], tokens2[i]);
213 }
214
215 for (i = 0; tokens1[i] != NULL; i++) {
216 free(tokens1[i]);
217 }
218 for (i = 0; tokens2[i] != NULL; i++) {
219 free(tokens2[i]);
220 }
221 free(tokens1);
222 free(tokens2);
223
224 return result;
225 }
226
cvercmp_ver_tokenize(const char * ver)227 static char **cvercmp_ver_tokenize(const char *ver)
228 {
229 int num = 0;
230 int idx;
231 int verlen = strlen(ver);
232 char **tokens;
233 int i;
234 int tokenlen;
235
236 for (idx = 0; idx < verlen; idx += cvercmp_next_token(ver + idx) + 1) {
237 num++;
238 }
239
240 tokens = calloc(sizeof(char *), num + 1);
241
242 for (i = 0, idx = 0; i < num; i++) {
243 tokenlen = cvercmp_next_token(ver + idx);
244 tokens[i] = malloc(sizeof(char) * (tokenlen + 1));
245 strncpy(tokens[i], ver + idx, tokenlen);
246 tokens[i][tokenlen] = '\0';
247 idx += tokenlen + 1; /* Skip character separating tokens. */
248 }
249
250 return tokens;
251 }
252
cvercmp_next_token(const char * str)253 static int cvercmp_next_token(const char *str)
254 {
255 int i;
256
257 for (i = 0;
258 str[i] != '\0' && str[i] != '.' && str[i] != '-' && str[i] != '_';
259 i++) {
260 }
261
262 return i;
263 }
264
cvercmp_ver_subtokenize(const char * ver)265 static char **cvercmp_ver_subtokenize(const char *ver)
266 {
267 int num = 0;
268 int idx;
269 int verlen;
270 char **tokens;
271 int i;
272 int tokenlen;
273
274 /* Treat NULL string as empty string */
275 if (!ver) {
276 ver = "";
277 }
278 verlen = strlen(ver);
279
280 for (idx = 0; idx < verlen; idx += cvercmp_next_subtoken(ver + idx)) {
281 num++;
282 }
283
284 tokens = calloc(sizeof(char *), num + 1);
285
286 for (i = 0, idx = 0; i < num; i++) {
287 tokenlen = cvercmp_next_subtoken(ver + idx);
288 tokens[i] = malloc(sizeof(char) * (tokenlen + 1));
289 if (tokenlen > 0) {
290 strncpy(tokens[i], ver + idx, tokenlen);
291 }
292 tokens[i][tokenlen] = '\0';
293 idx += tokenlen;
294 }
295
296 return tokens;
297 }
298
cvercmp_next_subtoken(const char * str)299 static int cvercmp_next_subtoken(const char *str)
300 {
301 int i;
302 bool alpha;
303 int sc0 = str[0];
304 int sci;
305
306 if (isdigit(sc0)) {
307 alpha = false;
308 } else {
309 alpha = true;
310 }
311
312 for (i = 0; sci = str[i],
313 sci != '\0'
314 && ((alpha && !isdigit(sci)) || (!alpha && isdigit(sci)));
315 i++) {
316 }
317
318 return i;
319 }
320
cvercmp_parse_prever(const char * ver)321 static enum cvercmp_prever cvercmp_parse_prever(const char *ver)
322 {
323 int i;
324
325 for (i = 0; preverstrs[i].str != NULL; i++) {
326 if (!strcasecmp(ver, preverstrs[i].str)) {
327 return preverstrs[i].prever;
328 }
329 }
330
331 return CVERCMP_PRE_NONE;
332 }
333