chiark / gitweb /
struct/buf.c: Add functions for serializing and deserializing `kludge64'.
[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 "macros.h"
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
69 struct vinfo {
70   const char *e, *el;
71   const char *m, *ml;
72   const char *s, *sl;
73 };
74
75 static 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;
83     if (!ISDIGIT(ch))
84       break;
85     v++;
86     n = n * 10 + (ch - '0');
87   }
88   *vv = v;
89   return (n);
90 }
91
92 static 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;
100     if (ISDIGIT(ch))
101       break;
102     v++;
103   }
104   *vv = v;
105   return (b);
106 }
107
108 #define CMP(x, y) ((x) < (y) ? -1 : +1)
109
110 static 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 (;;) {
127       if (pa == va) ia = 1;
128       else if (ISALPHA(*pa)) ia = 2;
129       else if (*pa == '~') ia = 0;
130       else ia = 3;
131
132       if (pb == vb) ib = 1;
133       else if (ISALPHA(*pb)) ib = 2;
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++;
141     }
142
143     /* --- Compare digit portions --- */
144
145     ia = vint(&va, val); ib = vint(&vb, vbl);
146     if (ia != ib) return (CMP(ia, ib));
147   }
148 }
149
150 static 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;
166   else {
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
176 int 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 -------------------------------------------------*/