utils/macros.h: Add <ctype.h> and `foocmp' helper macros.
[mLib] / utils / versioncmp.c
CommitLineData
0683223a
MW
1/* -*-c-*-
2 *
0683223a
MW
3 * Compare version numbers using the Debian algorithm
4 *
5 * (c) 2007 Straylight/Edgeware
6 */
7
d4efbcd9 8/*----- Licensing notice --------------------------------------------------*
0683223a
MW
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.
d4efbcd9 16 *
0683223a
MW
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.
d4efbcd9 21 *
0683223a
MW
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
36188114 33#include "macros.h"
0683223a
MW
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
69struct vinfo {
70 const char *e, *el;
71 const char *m, *ml;
72 const char *s, *sl;
73};
74
75static 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;
36188114 83 if (!ISDIGIT(ch))
0683223a
MW
84 break;
85 v++;
86 n = n * 10 + (ch - '0');
87 }
88 *vv = v;
89 return (n);
90}
91
92static 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;
36188114 100 if (ISDIGIT(ch))
0683223a
MW
101 break;
102 v++;
103 }
104 *vv = v;
105 return (b);
106}
107
108#define CMP(x, y) ((x) < (y) ? -1 : +1)
109
110static 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 (;;) {
3048fcf9 127 if (pa == va) ia = 1;
36188114 128 else if (ISALPHA(*pa)) ia = 2;
3048fcf9
MW
129 else if (*pa == '~') ia = 0;
130 else ia = 3;
131
132 if (pb == vb) ib = 1;
36188114 133 else if (ISALPHA(*pb)) ib = 2;
3048fcf9
MW
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++;
0683223a
MW
141 }
142
143 /* --- Compare digit portions --- */
144
145 ia = vint(&va, val); ib = vint(&vb, vbl);
3048fcf9 146 if (ia != ib) return (CMP(ia, ib));
0683223a
MW
147 }
148}
149
150static 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;
d4efbcd9 166 else {
0683223a
MW
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
176int 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 -------------------------------------------------*/