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