3556916be2f7f8ea4833eaa2e4ac092c40d525bd
[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 "versioncmp.h"
34
35 /*----- Main code ---------------------------------------------------------*/
36
37 /* --- @versioncmp@ --- *
38 *
39 * Arguments: @const char *va, *vb@ = two version strings
40 *
41 * Returns: Less than, equal to, or greater than zero, according to
42 * whether @va@ is less than, equal to, or greater than @vb@.
43 *
44 * Use: Compares version number strings.
45 *
46 * The algorithm is an extension of the Debian version
47 * comparison algorithm. A version number consists of three
48 * components:
49 *
50 * [EPOCH :] MAIN [- SUB]
51 *
52 * The MAIN part may contain colons or hyphens if there is an
53 * EPOCH or SUB, respectively. Version strings are compared
54 * componentwise: first epochs, then main parts, and finally
55 * subparts.
56 *
57 * The component comparison is done as follows. First, the
58 * initial subsequence of nondigit characters is extracted from
59 * each string, and these are compared lexicographically, using
60 * ASCII ordering, except that letters precede non-letters. If
61 * both are the same, an initial sequence of digits is extracted
62 * from the remaining parts of the version strings, and these
63 * are compared numerically (an empty sequence being considered
64 * to have the value zero). This process is repeated until we
65 * have a winner or until both strings are exhausted.
66 */
67
68 struct vinfo {
69 const char *e, *el;
70 const char *m, *ml;
71 const char *s, *sl;
72 };
73
74 static int vint(const char **vv, const char *vl)
75 {
76 int n = 0;
77 const char *v = *vv;
78 int ch;
79
80 while (v < vl) {
81 ch = *v;
82 if (!isdigit((unsigned char)ch))
83 break;
84 v++;
85 n = n * 10 + (ch - '0');
86 }
87 *vv = v;
88 return (n);
89 }
90
91 static const char *vchr(const char **vv, const char *vl)
92 {
93 const char *v = *vv;
94 const char *b = v;
95 int ch;
96
97 while (v < vl) {
98 ch = *v;
99 if (isdigit((unsigned char)ch))
100 break;
101 v++;
102 }
103 *vv = v;
104 return (b);
105 }
106
107 #define CMP(x, y) ((x) < (y) ? -1 : +1)
108
109 static int vcmp(const char *va, const char *val,
110 const char *vb, const char *vbl)
111 {
112 const char *pa, *pb;
113 int ia, ib;
114
115 for (;;) {
116
117 /* --- See if we're done --- */
118
119 if (va == val && vb == vbl)
120 return (0);
121
122 /* --- Compare nondigit portions --- */
123
124 pa = vchr(&va, val); pb = vchr(&vb, vbl);
125 for (;;) {
126 if (pa == va) ia = 1;
127 else if (isalpha((unsigned char)*pa)) ia = 2;
128 else if (*pa == '~') ia = 0;
129 else ia = 3;
130
131 if (pb == vb) ib = 1;
132 else if (isalpha((unsigned char)*pb)) ib = 2;
133 else if (*pb == '~') ib = 0;
134 else ib = 3;
135
136 if (ia != ib) return (CMP(ia, ib));
137 else if (pa == va && pb == vb) break;
138 else if (*pa != *pb) return (CMP(*pa, *pb));
139 pa++; pb++;
140 }
141
142 /* --- Compare digit portions --- */
143
144 ia = vint(&va, val); ib = vint(&vb, vbl);
145 if (ia != ib) return (CMP(ia, ib));
146 }
147 }
148
149 static void vsplit(const char *v, struct vinfo *vi)
150 {
151 const char *p;
152 size_t n;
153
154 if ((p = strchr(v, ':')) == 0)
155 vi->e = vi->el = 0;
156 else {
157 vi->e = v;
158 vi->el = p;
159 v = p + 1;
160 }
161
162 n = strlen(v);
163 if ((p = strrchr(v, '-')) == 0)
164 vi->s = vi->sl = 0;
165 else {
166 vi->s = p + 1;
167 vi->sl = v + n;
168 n = p - v;
169 }
170
171 vi->m = v;
172 vi->ml = v + n;
173 }
174
175 int versioncmp(const char *va, const char *vb)
176 {
177 struct vinfo via, vib;
178 int rc;
179
180 vsplit(va, &via); vsplit(vb, &vib);
181 if ((rc = vcmp(via.e, via.el, vib.e, vib.el)) != 0 ||
182 (rc = vcmp(via.m, via.ml, vib.m, vib.ml)) != 0 ||
183 (rc = vcmp(via.s, via.sl, vib.s, vib.sl)) != 0)
184 return (rc);
185 return (0);
186 }
187
188 /*----- That's all, folks -------------------------------------------------*/