chiark / gitweb /
Release 2.3.3.1.
[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 -------------------------------------------------*/