utils/macros.h: Add <ctype.h> and `foocmp' helper macros.
[mLib] / utils / versioncmp.c
1 /* -*-c-*-
2 *
3 * Compare version numbers using the Debian algorithm
4 *
5 * (c) 2007 Straylight/Edgeware
6 */
7
8 /*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the mLib utilities library.
11 *
12 * mLib is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU Library General Public License as
14 * published by the Free Software Foundation; either version 2 of the
15 * License, or (at your option) any later version.
16 *
17 * mLib is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Library General Public License for more details.
21 *
22 * You should have received a copy of the GNU Library General Public
23 * License along with mLib; if not, write to the Free
24 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25 * MA 02111-1307, USA.
26 */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include <ctype.h>
31 #include <string.h>
32
33 #include "macros.h"
34 #include "versioncmp.h"
35
36 /*----- Main code ---------------------------------------------------------*/
37
38 /* --- @versioncmp@ --- *
39 *
40 * Arguments: @const char *va, *vb@ = two version strings
41 *
42 * Returns: Less than, equal to, or greater than zero, according to
43 * whether @va@ is less than, equal to, or greater than @vb@.
44 *
45 * Use: Compares version number strings.
46 *
47 * The algorithm is an extension of the Debian version
48 * comparison algorithm. A version number consists of three
49 * components:
50 *
51 * [EPOCH :] MAIN [- SUB]
52 *
53 * The MAIN part may contain colons or hyphens if there is an
54 * EPOCH or SUB, respectively. Version strings are compared
55 * componentwise: first epochs, then main parts, and finally
56 * subparts.
57 *
58 * The component comparison is done as follows. First, the
59 * initial subsequence of nondigit characters is extracted from
60 * each string, and these are compared lexicographically, using
61 * ASCII ordering, except that letters precede non-letters. If
62 * both are the same, an initial sequence of digits is extracted
63 * from the remaining parts of the version strings, and these
64 * are compared numerically (an empty sequence being considered
65 * to have the value zero). This process is repeated until we
66 * have a winner or until both strings are exhausted.
67 */
68
69 struct vinfo {
70 const char *e, *el;
71 const char *m, *ml;
72 const char *s, *sl;
73 };
74
75 static int vint(const char **vv, const char *vl)
76 {
77 int n = 0;
78 const char *v = *vv;
79 int ch;
80
81 while (v < vl) {
82 ch = *v;
83 if (!ISDIGIT(ch))
84 break;
85 v++;
86 n = n * 10 + (ch - '0');
87 }
88 *vv = v;
89 return (n);
90 }
91
92 static const char *vchr(const char **vv, const char *vl)
93 {
94 const char *v = *vv;
95 const char *b = v;
96 int ch;
97
98 while (v < vl) {
99 ch = *v;
100 if (ISDIGIT(ch))
101 break;
102 v++;
103 }
104 *vv = v;
105 return (b);
106 }
107
108 #define CMP(x, y) ((x) < (y) ? -1 : +1)
109
110 static int vcmp(const char *va, const char *val,
111 const char *vb, const char *vbl)
112 {
113 const char *pa, *pb;
114 int ia, ib;
115
116 for (;;) {
117
118 /* --- See if we're done --- */
119
120 if (va == val && vb == vbl)
121 return (0);
122
123 /* --- Compare nondigit portions --- */
124
125 pa = vchr(&va, val); pb = vchr(&vb, vbl);
126 for (;;) {
127 if (pa == va) ia = 1;
128 else if (ISALPHA(*pa)) ia = 2;
129 else if (*pa == '~') ia = 0;
130 else ia = 3;
131
132 if (pb == vb) ib = 1;
133 else if (ISALPHA(*pb)) ib = 2;
134 else if (*pb == '~') ib = 0;
135 else ib = 3;
136
137 if (ia != ib) return (CMP(ia, ib));
138 else if (pa == va && pb == vb) break;
139 else if (*pa != *pb) return (CMP(*pa, *pb));
140 pa++; pb++;
141 }
142
143 /* --- Compare digit portions --- */
144
145 ia = vint(&va, val); ib = vint(&vb, vbl);
146 if (ia != ib) return (CMP(ia, ib));
147 }
148 }
149
150 static void vsplit(const char *v, struct vinfo *vi)
151 {
152 const char *p;
153 size_t n;
154
155 if ((p = strchr(v, ':')) == 0)
156 vi->e = vi->el = 0;
157 else {
158 vi->e = v;
159 vi->el = p;
160 v = p + 1;
161 }
162
163 n = strlen(v);
164 if ((p = strrchr(v, '-')) == 0)
165 vi->s = vi->sl = 0;
166 else {
167 vi->s = p + 1;
168 vi->sl = v + n;
169 n = p - v;
170 }
171
172 vi->m = v;
173 vi->ml = v + n;
174 }
175
176 int versioncmp(const char *va, const char *vb)
177 {
178 struct vinfo via, vib;
179 int rc;
180
181 vsplit(va, &via); vsplit(vb, &vib);
182 if ((rc = vcmp(via.e, via.el, vib.e, vib.el)) != 0 ||
183 (rc = vcmp(via.m, via.ml, vib.m, vib.ml)) != 0 ||
184 (rc = vcmp(via.s, via.sl, vib.s, vib.sl)) != 0)
185 return (rc);
186 return (0);
187 }
188
189 /*----- That's all, folks -------------------------------------------------*/