JSWOBJ=$(JSWSRC:%.c=%.o)
LIBZPMSRC=sha256.c db.c compress.c uncompress.c zpm.c zpm_hash.c \
foreach_path.c vercmp.c findpkg.c quote.c dbquery.c script_hash.c \
- parse.c integ.c seterror.c notes.c createpkg.c
+ parse.c integ.c seterror.c notes.c createpkg.c buffer.c
LIBZPMOBJ=$(addprefix lib/, $(LIBZPMSRC:%.c=%.o))
lib/zpm.o: newdb.c
-zpm-vercmp: src/vercmp.o lib/vercmp.o
+zpm-vercmp: src/vercmp.o lib/vercmp.o lib/parse.o
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $+
-zpm-shell: sqlite/sqlite3.o sqlite/shell.o sqlite/extensions.o lib/vercmp.o
+zpm-shell: sqlite/sqlite3.o sqlite/shell.o sqlite/extensions.o lib/vercmp.o lib/parse.o
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $+
libelf.a: elf/libelf.o
-.TH zpm-vercmp 8 2018-12-10 "ZPM 0.4"
+.TH zpm-vercmp 8 2019-02-26 "ZPM 0.4"
.SH NAME
zpm-vercmp \- compare strings with version numbering
.SH SYNOPSIS
.B zpm vercmp \fR[\fB-q\fR] \fIstring1 string2\fR
.SH DESCRIPTION
-\fBzpm-vercmp\fR compares its first two arguments, using
-the usual version numbering semantics. It will write
-the result of the comparison to stdout, 0 if the arguments
-compare equal, -1 if the first is greater, and 1 if the
-second is greater.
+\fBzpm-vercmp\fR compares its arguments, using
+version numbering semantics. It can operate in
+test mode or search mode. In search mode, activated with \-G or \-L
+it will print the highest or lowest version string to stdout.
+.PP
+In test mode, it compares all the arguments, and prints '0' if they are all
+equal, '-1' if they are in version order, and 1 if they are out of order.
+Adjacent identical version strings are considered both equal and in order.
+Additionally in test mode,
+the process will exit 0 if the strings are all equal, 1 if the
+strings are not in order, and 2 if they are in order.
.SH OPTIONS
.TP
\-q
write nothing to stdout. The program will still exit with the
usual exit status
+.TP
+\-G
+Print the largest argument version string found.
+.TP
+\-L
+Print the smallest argument version string found.
.SH EXAMPLES
.PP
.nf
-zpm-vercmp a b -> "1"
+zpm-vercmp a b -> "-1"
.fi
.SH EXIT STATUS
-0 if the two strings compare equal
--1 if the first string is greater than the second
-1 if the second string is greater then the first
+.TP
+0
+if the strings are all equal
+.TP
+1
+if the strings are not in version order
+.TP
+2
+if the strings are in version order
.SH FILES
None
.SH ENVIRONMENT
#include "zpm.h"
-#include "sqlite3.h"
-
#define DMARK fprintf(stderr, "mark %s %s:%d\n", __FILE__, __func__, __LINE__)
+int zpm_parse_version(const char *pstr, struct zpm_version_info *info) {
+ int cur = 0, ch;
+
+ info->verstr = pstr;
+ info->name = 0;
+ info->namelen = 0;
+ info->version = 0;
+ info->verlen = 0;
+ info->release = -1;
+ info->rellen = 0;
+ info->relstr = 0;
+
+ if (!pstr) {
+ return 0;
+ }
+
+ /* skip leading whitespace */
+ while (isspace(pstr[cur])) {
+ cur++;
+ }
+
+ /* get the name, if any */
+ if (isalpha(pstr[cur])) {
+ info->name = pstr + cur;
+ for (ch = pstr[cur]; ch; ch = pstr[++cur]) {
+ if (ch == '-') {
+ if (isdigit(pstr[cur+1])) {
+ break;
+ }
+ }
+ if (!isgraph(ch)) {
+ break;
+ }
+ info->namelen++;
+ }
+ }
+
+ while (pstr[cur] == '-') {
+ cur++;
+ }
+
+ /* should be pointing at a digit. if not, we're done */
+ if (isdigit(pstr[cur])) {
+ info->version = pstr + cur;
+ for (ch = pstr[cur]; ch; ch = pstr[++cur]) {
+ if (ch == '-') {
+ break;
+ }
+ if (!isgraph(ch)) {
+ break;
+ }
+ info->verlen++;
+ }
+ }
+
+ while (pstr[cur] == '-') {
+ cur++;
+ }
+
+ if (isdigit(pstr[cur])) {
+ info->relstr = pstr + cur;
+ while (isdigit(pstr[cur++])) {
+ info->rellen++;
+ }
+ }
+
+ if (info->relstr) {
+ info->release = atoi(info->relstr);
+ }
+
+ return info->namelen || info->verlen || info->rellen;
+}
+
int zpm_parse_package(char *pstr, char *name, char *ver, int *rel) {
if (name) *name = 0;
if (ver) *ver = 0;
#include <string.h>
#include <ctype.h>
+#include "zpm.h"
+
+#define TEXT 1
+#define NUMERIC 2
+
struct ver {
- char str[1024]; /* string rep */
- char *s; /* start component */
+#if 0
+ char name[1024];
+ char str[1024]; /* string rep of the version */
+#endif
+ const char *str;
+ int release; /* a trailing -\d+ value */
+ const char *s; /* start component */
int cn; /* current component number */
- char *next; /* start of potential next component */
- char keep; /* character over-written with null byte */
+ int len; /* current component length */
+
+ const char *next; /* start of potential next component */
int sep; /* number of characters in separator */
int nv; /* numeric value */
};
static void init_ver(struct ver *v, const char *s) {
+#if 0
strncpy(v->str, s, 1023);
v->str[1023] = 0;
- v->s = 0;
+ v->name[1023] = 0;
+#endif
+ v->str = s;
+ v->release = 0;
+ v->s = v->str;
v->cn = 0;
v->next = v->str;
- v->keep = 0;
v->sep = 0;
+ v->len = 0;
+ v->type = 0;
+}
+
+static int cmp_strlen(const char *a, size_t alen, const char *b, size_t blen) {
+ size_t shortest;
+ int base;
+
+ if (a && !b) {
+ return 1;
+ } else if (b && !a) {
+ return -1;
+ } else if (!a && !b) {
+ return 0;
+ }
+
+ if (alen && !blen) {
+ return 1;
+ } else if (blen && !alen) {
+ return -1;
+ } else if (!alen && !blen) {
+ return 0;
+ }
+
+ shortest = alen < blen ? alen : blen;
+
+ base = strncmp(a, b, shortest);
+
+ if (base == 0) {
+ if (alen == blen) {
+ return 0;
+ }
+ return alen < blen ? -1 : 1;
+ }
+
+ return base < 0 ? -1 : 1;
}
static int ver_cmp(struct ver *a, struct ver *b) {
if (a->type != b->type) {
return a->type < b->type ? -1 : 1;
}
- if (a->type == 1) {
+
+ if (a->type == TEXT) {
int cmp;
if (a->s && ! b->s) return 1;
if (b->s && ! a->s) return -1;
}
return cmp < 0 ? -1 : 1;
}
- if (a->type == 2) {
+ if (a->type == NUMERIC) {
if (a->nv != b->nv)
return a->nv < b->nv ? -1 : 1;
}
}
static int next_comp(struct ver *v) {
- char *s;
+ const char *s, *next;
- /* restore over-written character */
- if (v->keep) {
- v->next[0] = v->keep;
- v->keep = 0;
- }
- s = v->next;
+ next = v->next;
+ v->len = 0;
+ v->type = 0;
/* skip over anything that isn't alphanumeric */
v->sep = 0;
- while (*s && !isalnum(*s)) {
- v->sep++;
- s++;
+ while (*next && !isalnum(*next)) {
+ next++;
}
- v->next = s;
/* zero return if at end of string */
- if (!*s) {
+ if (!*next) {
+ v->s = next;
return 0;
}
+
+ s = next;
+ v->s = next;
+
if (isdigit(*s)) {
- v->type = 2;
- while (isdigit(*s)) s++;
- v->keep = *s;
- *s = 0;
- v->s = v->next;
- v->nv = atoi(v->s);
+ v->type = NUMERIC;
+ v->nv = *s-'0';
+ while (isdigit(*s)) {
+ v->nv = v->nv * 10 + *s-'0';
+ v->len++;
+ s++;
+ }
+ v->next = s;
} else if (isalpha(*s)) {
- v->type = 1;
- while (isalpha(*s)) s++;
- v->keep = *s;
- *s = 0;
- v->s = v->next;
+ v->type = TEXT;
+ while (*s && (isalpha(*s) || *s == '-') ) {
+ v->len++;
+ s++;
+ }
+ v->next = s;
+ /* last character in a alphabetic can't be a - */
+ while (*s == '-') {
+ v->len--;
+ s--;
+ }
+ } else {
+ /* impossible */
+ return 0;
}
- v->next = s;
- return ++v->cn;
+ v->cn++;
+ return v->cn;
}
/*
* alphabetic less than numeric
+ * return -1 if vsa < vsb, 0 if equal, 1 if vsa > vsb
*/
int zpm_vercmp(const char *vsa, const char *vsb) {
struct ver a, b;
+ struct zpm_version_info ainfo, binfo;
int an, bn;
int cmp;
+ char astr[256], bstr[256];
if (vsa && !vsb) {
return 1;
return 0;
}
- init_ver(&a, vsa);
- init_ver(&b, vsb);
+ zpm_parse_version(vsa, &ainfo);
+ zpm_parse_version(vsb, &binfo);
+ cmp = cmp_strlen(ainfo.name, ainfo.namelen, binfo.name, binfo.namelen);
+
+ if (cmp != 0) {
+ return cmp;
+ }
+
+ if (ainfo.verlen > 255) {
+ ainfo.verlen = 255;
+ }
+ if (binfo.verlen > 255) {
+ binfo.verlen = 255;
+ }
+
+ strncpy(astr, ainfo.verstr, ainfo.verlen);
+ strncpy(bstr, binfo.verstr, binfo.verlen);
+ astr[ainfo.verlen] = 0;
+ bstr[binfo.verlen] = 0;
+
+ init_ver(&a, astr);
+ init_ver(&b, bstr);
do {
an = next_comp(&a);
bn = next_comp(&b);
if (an != bn) {
if (an == 0 && a.type == 2 && b.sep == 0 && b.type == 1) {
- return 1;
+ cmp = 1; break;
} else if (bn == 0 && b.type == 2 && a.sep == 0 && a.type == 1) {
- return -1;
+ cmp = -1; break;
}
- return an < bn ? -1 : 1;
+ cmp = an < bn ? -1 : 1; break;
}
if (an == 0 && bn == 0) {
- return 0;
+ break;
}
cmp = ver_cmp(&a, &b);
if (cmp != 0) {
- return cmp;
+ break;
}
} while (an && bn);
- return 0;
+ if (cmp != 0) {
+ return cmp;
+ }
+
+ /* compare release */
+ if (ainfo.release != binfo.release) {
+ cmp = ainfo.release < binfo.release ? -1 : 1;
+ }
+
+ return cmp;
}
int cmp;
char *a = 0, *b = 0;
int print = 1;
- int argn, opt, pass, failed = 0;
-
- /* 0x1 = less than, 0x2 = equal, 0x4 = greater */
- unsigned testmask = 0;
- unsigned testcases = 0;
+ int argn, opt = 0;
+ int inorder = 1, equal = 1;
char *greatest = 0;
char *least = 0;
- if (ac < 2) return 1;
-
- while ((opt = getopt(ac, av, "qgleLG")) != -1) {
+ while ((opt = getopt(ac, av, "qLG")) != -1) {
switch (opt) {
case 'q': print = 0; break;
- case 'l': testmask |= 0x1; break;
- case 'e': testmask |= 0x2; break;
- case 'g': testmask |= 0x4; break;
- case 'L': print = 2; break;
- case 'G': print = 3; break;
+ case 'G': print = 2; break;
+ case 'L': print = 3; break;
default:
exit(EXIT_FAILURE);
break;
}
}
+
argn = optind;
if (ac > argn) {
while (ac > argn) {
b = av[argn++];
- cmp = zpm_vercmp(a, b) + 1;
- testcases |= (1<<cmp);
-
- if (testmask) {
- pass = (testmask & (1 << cmp));
- if (!pass) {
- failed++;
- }
+ cmp = zpm_vercmp(a, b);
+ switch (cmp) {
+ case 0:
+ break;
+ case -1:
+ equal = 0;
+ break;
+ case 1:
+ equal = 0;
+ inorder = 0;
+ break;
+ default:
+ break;
}
-
- cmp = zpm_vercmp(greatest, b);
+
+ cmp = zpm_vercmp(b, greatest);
if (cmp > 0) {
greatest = b;
}
- cmp = zpm_vercmp(least, b);
+ cmp = zpm_vercmp(b, least);
if (cmp < 0) {
least = b;
}
+ a = b;
}
- switch (testcases) {
- case 0: /* no tests */
- cmp = 0; break;
- case 1: /* only less than */
- cmp = 2; break;
- case 2: /* only equal */
- cmp = 0; break;
- case 4: /* only greater than */
- cmp = 1; break;
- default: /* mixed */
- cmp = 3; break;
+ if (equal) {
+ cmp = 0;
+ } else if (inorder) {
+ cmp = -1;
+ } else {
+ cmp = 1;
}
switch (print) {
case 1:
- printf("%d\n", cmp == 2 ? -1 : cmp);
+ printf("%d\n", cmp);
break;
case 2:
if (greatest) printf("%s\n", greatest);
default:
break;
}
- fflush(stdout);
- if (testmask == 0) {
- return cmp;
- } else {
- return failed ? 1 : 0;
- }
+ return cmp == -1 ? 2 : cmp;
}
. tap.sh
+# 17 vtests, 3 least, 3 greatest, 2 other, 3 temp handling
+# 17 * 6 + 3 + 3
+plan 111
+
+require rm -rf tmp
+require mkdir tmp
+
vtest() {
res=$(zpm-vercmp "$1" "$2")
okstreq "$res" -1 "$1 < $2"
+
res=$(zpm-vercmp "$2" "$1")
okstreq "$res" 1 "$2 > $1"
+
res=$(zpm-vercmp "$1" "$1")
okstreq "$res" 0 "$1 == $1"
+
+ res=$(zpm shell vercmp.db "select '$1' < '$2' collate vercmp")
+ okstreq "$res" 1 "zpm shell $1 < $2"
+ res=$(zpm shell vercmp.db "select '$1' > '$2' collate vercmp")
+ okstreq "$res" 0 "zpm shell not $1 > $2"
+ res=$(zpm shell vercmp.db "select '$1' = '$1' collate vercmp")
+ okstreq "$res" 1 "zpm shell $1 = $1"
}
least() {
okstreq "$g" "$want" "greatest $*"
}
-plan 53
-
least 1.0a 1.0a 1.0b
least 1.0a 1.0b 1.0a
greatest z a b z
least abc abc
greatest abc abc
-zpm vercmp -gq 'gnupg-1.0-1' "gnupg-2.0-1"
-exitwith 1 vercmp -g not gt
+#zpm vercmp -gq 'gnupg-1.0-1' "gnupg-2.0-1"
+#exitwith 1 vercmp -g not gt
-zpm vercmp -gq 'gnupg-2.0-1' "gnupg-1.0-1"
-exitwith 0 vercmp -g is gt
+#zpm vercmp -gq 'gnupg-2.0-1' "gnupg-1.0-1"
+#exitwith 0 vercmp -g is gt
# alpha
vtest 1.0a 1.0b
vtest 1.0beta 1.0p
vtest 1.0p 1.0pre
vtest 1.0pre 1.0rc
-vtest 1.0rc 1.0
+
+vtest 1.0 1.0rc
vtest 1.0 1.0.a
vtest 1.0.a 1.0.1
vtest 1 1.0
vtest 1.0 1.1
+
vtest 1.1 1.1.1
vtest 1.2 2.0
vtest 2.0 3.0.0
vtest 1.0-1 1.0-2
vtest 1.0-2 2.0-1
+# full
+vtest ffmpeg-4.1-3 ffmpeg-4.1.1-1
+vtest 4.1-3 4.1.1-1
+
+require rm -rf tmp
finish
int zpm_hash(char *path, char *hash, uint32_t flags);
int zpm_readopts(struct zpm *pkg, int ac, char **av);
+struct zpm_version_info {
+ const char *verstr;
+ const char *name; int namelen;
+ const char *version; int verlen;
+ const char *relstr; int rellen;
+ int release;
+};
+
+int zpm_parse_version(const char *pstr, struct zpm_version_info *info);
int zpm_vercmp(const char *a, const char *b);
/* add vercmp collation to db */