chiark / gitweb /
335098b88f1ea192fa4e1e7c67f90c6213f277fb
[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 && pb == vb)
129         break;
130       else if (pa == va)
131         return (-1);
132       else if (pb == vb)
133         return (+1);
134       else if (*pa == *pb) {
135         pa++; pb++;
136         continue;
137       } else if (isalpha((unsigned char)*pa) == isalpha((unsigned char)*pb))
138         return (CMP(*pa, *pb));
139       else if (isalpha((unsigned char)*pa))
140         return (-1);
141       else
142         return (+1);
143     }
144
145     /* --- Compare digit portions --- */
146
147     ia = vint(&va, val); ib = vint(&vb, vbl);
148     if (ia != ib)
149       return (CMP(ia, ib));
150   }
151 }
152
153 static void vsplit(const char *v, struct vinfo *vi)
154 {
155   const char *p;
156   size_t n;
157
158   if ((p = strchr(v, ':')) == 0)
159     vi->e = vi->el = 0;
160   else {
161     vi->e = v;
162     vi->el = p;
163     v = p + 1;
164   }
165
166   n = strlen(v);
167   if ((p = strrchr(v, '-')) == 0)
168     vi->s = vi->sl = 0;
169   else {    
170     vi->s = p + 1;
171     vi->sl = v + n;
172     n = p - v;
173   }
174
175   vi->m = v;
176   vi->ml = v + n;
177 }
178
179 int versioncmp(const char *va, const char *vb)
180 {
181   struct vinfo via, vib;
182   int rc;
183
184   vsplit(va, &via); vsplit(vb, &vib);
185   if ((rc = vcmp(via.e, via.el, vib.e, vib.el)) != 0 ||
186       (rc = vcmp(via.m, via.ml, vib.m, vib.ml)) != 0 ||
187       (rc = vcmp(via.s, via.sl, vib.s, vib.sl)) != 0)
188     return (rc);
189   return (0);
190 }
191
192 /*----- That's all, folks -------------------------------------------------*/