aboutsummaryrefslogtreecommitdiff
path: root/version_compare.c
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2022-07-22 20:17:29 -0400
committerJoseph Hunkeler <jhunkeler@gmail.com>2022-07-22 20:17:29 -0400
commita70733d9a64320eac8038d733df67467cd97976c (patch)
tree767c0edc76bafc42e27a23ddad7ff9054d7e5728 /version_compare.c
parent4a196857e13bd65ef982fe90dae1a1865985ee94 (diff)
downloadversion_compare-a70733d9a64320eac8038d733df67467cd97976c.tar.gz
Implement test harnass
* Fix various memory leaks * Fix off by one error in rsplit * main program is a wrapper for the vcmp library entry() function * Implement basic CI
Diffstat (limited to 'version_compare.c')
-rw-r--r--version_compare.c369
1 files changed, 369 insertions, 0 deletions
diff --git a/version_compare.c b/version_compare.c
new file mode 100644
index 0000000..c6aaca7
--- /dev/null
+++ b/version_compare.c
@@ -0,0 +1,369 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "version_compare.h"
+
+/**
+ * Determine whether a string consists of only whitespace, or not
+ * @parm str string to check for whitespace
+ * @return 0 if string is not empty
+ * @return 1 if string is empty
+ */
+int isempty(char *str) {
+ char *ptr;
+ size_t len;
+
+ ptr = str;
+ len = strlen(ptr);
+ while (isblank(*ptr)) {
+ len--;
+ ptr++;
+ }
+ return (int)(len == 0);
+}
+
+/**
+ * Remove leading whitespace from string
+ * @param s input string
+ * @return pointer to string
+ */
+char *lstrip(char **s) {
+ char *orig;
+ char *end;
+
+ orig = (*s);
+ if (!isblank(*(*s)) || *(*s) == '\0') {
+ return (*s);
+ }
+
+ while (isblank(*(*s)) && *(*s) != '\0') {
+ end = (*s);
+ (*s)++;
+ }
+ memmove(orig, (*s), strlen(end));
+
+ (*s) = orig;
+ return (*s);
+}
+
+/**
+ * Remove tailing whitespace from string
+ * @param s input string
+ * @return pointer to string
+ */
+char *rstrip(char **s) {
+ char *orig;
+ char *end;
+ size_t len;
+
+ orig = (*s);
+ len = strlen((*s));
+ if (len > 1) {
+ // Jump to end of the string
+ (*s) = (*s) + len - 1;
+ }
+
+ if (!isblank(*(*s)) || *(*s) == '\0') {
+ (*s) = orig;
+ return (*s);
+ }
+
+ while (len != 0 && isblank(*(*s)) && *(*s) != '\0') {
+ end = (*s);
+ (*s)--;
+ --len;
+ }
+ *end = '\0';
+
+ (*s) = orig;
+ return (*s);
+}
+
+/**
+ * Reduces multiple whitespace between characters
+ * @param s input string
+ * @return pointer to string
+ */
+char *collapse_whitespace(char **s) {
+ char *orig;
+ char *space;
+
+ lstrip(s);
+ rstrip(s);
+
+ space = NULL;
+ orig = (*s);
+ while (*(*s) != '\0') {
+ char *space_next;
+
+ if (space) {
+ int numspace = 0;
+ while (isblank(*space) && *space != '\0') {
+ numspace++;
+ space++;
+ }
+
+ memmove((*s), space, strlen(space) - 1 + numspace);
+ space = NULL;
+ }
+
+ space_next = (*s) + 1;
+ if (isblank(*(*s)) && (space_next != NULL && isblank(*space_next))) {
+ space = (*s);
+ (*s)++;
+ continue;
+ }
+
+ (*s)++;
+ }
+ (*s) = orig;
+ return (*s);
+}
+
+/**
+ * Sum each part of a '.'-delimited version string
+ * @param str version string
+ * @return sum of each part
+ * @return -1 on error
+ */
+int version_sum(const char *str) {
+ int result;
+ char *s, *ptr, *end;
+
+ if (!str || isempty(str)) {
+ return -1;
+ }
+
+ result = 0;
+ s = strdup(str);
+ if (!s) {
+ return -1;
+ }
+ ptr = s;
+ end = ptr;
+
+ // Parsing stops at the first non-alpha, non-'.' character
+ // Digits are processed until the first invalid character
+ // I'm torn whether this should be considered an error
+ while (end != NULL) {
+ result += (int) strtoul(ptr, &end, 10);
+ ptr = end;
+ if (*ptr == '.')
+ ptr++;
+ else if (isalpha(*ptr)) {
+ result += *ptr - ('a' - 1);
+ ptr++;
+ }
+ else
+ end = NULL;
+ }
+
+ free(s);
+ return result;
+}
+
+/**
+ * Convert version operator(s) to flags
+ * @param str input string
+ * @return operator flags
+ */
+int version_parse_operator(char *str) {
+ const char *valid = "><=!";
+ char *pos;
+ char *strp;
+ int result;
+
+ strp = str;
+ pos = strp;
+ result = 0;
+
+ if (isempty(str)) {
+ return -1;
+ }
+ while ((pos = strpbrk(pos, valid)) != NULL) {
+ switch (*pos) {
+ case '>':
+ result |= GT;
+ break;
+ case '<':
+ result |= LT;
+ break;
+ case '=':
+ result |= EQ;
+ break;
+ case '!':
+ result |= NOT;
+ break;
+ }
+ strp = pos++;
+ }
+
+ if (!result)
+ return -1;
+ return result;
+}
+
+/**
+ * Compare version strings based on flag(s)
+ * @param flags verison operators
+ * @param aa version1
+ * @param bb version2
+ * @return 1 flag operation is true
+ * @return 0 flag operation is false
+ */
+int version_compare(int flags, const char *aa, const char *bb) {
+ int result, result_a, result_b;
+
+ if (!flags || flags < 0) {
+ return -1;
+ }
+
+ result_a = version_sum(aa);
+ if (result_a < 0)
+ return -1;
+
+ result_b = version_sum(bb);
+ if (result_b < 0)
+ return -1;
+
+ result = 0;
+ if (flags & GT && flags & EQ)
+ result |= result_a >= result_b;
+ else if (flags & LT && flags & EQ)
+ result |= result_a <= result_b;
+ else if (flags & NOT && flags & EQ)
+ result |= result_a != result_b;
+ else if (flags & GT)
+ result |= result_a > result_b;
+ else if (flags & LT)
+ result |= result_a < result_b;
+ else if (flags & EQ)
+ result |= result_a == result_b;
+
+ return result;
+}
+
+void usage(char *prog) {
+ char *name;
+ name = strrchr(prog, '/');
+ if (!name)
+ name = prog;
+ else
+ name++;
+
+ char *examples[] = {
+ "{v} execution example:\n",
+ " %s \"1.2.3 > 1.2.3\"\n",
+ " 0\n",
+ " %s \"1.2.3 >= 1.2.3\"\n",
+ " 1\n",
+ " %s \"1.2.3 < 1.2.3\"\n",
+ " 0\n",
+ " %s \"1.2.3 <= 1.2.3\"\n",
+ " 1\n",
+ " %s \"1.2.3 != 1.2.3\"\n",
+ " 0\n",
+ " %s \"1.2.3 = 1.2.3\"\n",
+ " 1\n",
+ "\n",
+ "{v1} {operator} {v2} execution example:\n",
+ " %s \"1.2.3\" \">\" \"1.2.3\"\n",
+ " 0\n",
+ " %s \"1.2.3\" \">=\" \"1.2.3\"\n",
+ " 1\n",
+ " %s \"1.2.3\" \"<\" \"1.2.3\"\n",
+ " 0\n",
+ " %s \"1.2.3\" \"<=\" \"1.2.3\"\n",
+ " 1\n",
+ " %s \"1.2.3\" \"!=\" \"1.2.3\"\n",
+ " 0\n",
+ " %s \"1.2.3\" \"=\" \"1.2.3\"\n",
+ " 1\n",
+ NULL,
+ };
+
+ printf("usage: %s {{v} | {v1} {operator} {v2}}\n", name);
+ for (int i = 0; examples[i] != NULL; i++) {
+ char *output;
+
+ output = examples[i];
+ if (strchr(examples[i], '%')) {
+ printf(output, name);
+ } else {
+ printf("%s", output);
+ }
+ }
+ puts("");
+}
+
+
+int entry(int argc, char *argv[]) {
+ int result, op, must_free, ntokens, die;
+ char *v1, *v2, *operator, *arg, *arg_orig, *token;
+ char *tokens[4] = {NULL, NULL, NULL, NULL};
+
+ if (argc < 2) {
+ fprintf(stderr, "Not enough arguments.\n");
+ usage(argv[0]);
+ return 1;
+ }
+
+ die = 0;
+ must_free = 0;
+ if (argc < 3) {
+ int i;
+ i = 0;
+ ntokens = 0;
+ must_free = 1;
+ arg = strdup(argv[1]);
+ arg_orig = arg;
+ collapse_whitespace(&arg);
+
+ for (; (token = strsep(&arg, " ")) != NULL; i++, ntokens++) {
+ tokens[i] = strdup(token);
+ }
+ arg = arg_orig;
+
+ if (i < 3) {
+ fprintf(stderr, "Invalid version spec (missing whitespace or token?): '%s'\n", argv[1]);
+ usage(argv[0]);
+ die = 1;
+ goto free_tokens_and_die;
+ }
+
+ v1 = tokens[0];
+ operator = tokens[1];
+ v2 = tokens[2];
+ } else {
+ collapse_whitespace(&argv[1]);
+ collapse_whitespace(&argv[2]);
+ collapse_whitespace(&argv[3]);
+ v1 = argv[1];
+ operator = argv[2];
+ v2 = argv[3];
+ }
+
+ op = version_parse_operator(operator);
+ if (op < 0) {
+ fprintf(stderr, "Invalid operator sequence: '%s'\n", operator);
+ die = 1;
+ goto free_tokens_and_die;
+ }
+
+ result = version_compare(op, v1, v2);
+ printf("%d\n", result);
+
+free_tokens_and_die:
+ if (must_free) {
+ for (int i = 0; i < ntokens; i++) {
+ free(tokens[i]);
+ }
+ free(arg);
+ }
+
+ if (die)
+ return -1;
+
+ return 0;
+}