aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2022-07-12 15:46:10 -0400
committerJoseph Hunkeler <jhunkeler@gmail.com>2022-07-12 15:46:10 -0400
commit4a196857e13bd65ef982fe90dae1a1865985ee94 (patch)
treeaf6b51959fa0f7319177b930f9cbef9902c5d126
parent113f8d0e3d797d942ea6d40d8f6032595878b549 (diff)
downloadversion_compare-4a196857e13bd65ef982fe90dae1a1865985ee94.tar.gz
Initial commit
-rw-r--r--.gitignore4
-rw-r--r--CMakeLists.txt14
-rw-r--r--README.md55
-rw-r--r--main.c321
4 files changed, 393 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index c6127b3..5de0ffa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,3 +50,7 @@ modules.order
Module.symvers
Mkfile.old
dkms.conf
+
+# IDE
+.idea
+cmake-build-*
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..8ccb593
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,14 @@
+cmake_minimum_required(VERSION 3.0)
+project(version_compare C)
+
+set(CMAKE_C_STANDARD 99)
+
+if (MSVC)
+ # warning level 4 and all warnings as errors
+ add_compile_options(/W4 /WX)
+else()
+ # lots of warnings and all warnings
+ add_compile_options(-Wall -Wextra -pedantic)
+endif()
+
+add_executable(version_compare main.c)
diff --git a/README.md b/README.md
index e309882..3dadf27 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,54 @@
-# version_compare \ No newline at end of file
+# version_compare
+
+```
+usage: version_compare {{v} | {v1} {operator} {v2}}
+{v} execution example:
+ version_compare "1.2.3 > 1.2.3"
+ 0
+ version_compare "1.2.3 >= 1.2.3"
+ 1
+ version_compare "1.2.3 < 1.2.3"
+ 0
+ version_compare "1.2.3 <= 1.2.3"
+ 1
+ version_compare "1.2.3 != 1.2.3"
+ 0
+ version_compare "1.2.3 = 1.2.3"
+ 1
+
+{v1} {operator} {v2} execution example:
+ version_compare "1.2.3" ">" "1.2.3"
+ 0
+ version_compare "1.2.3" ">=" "1.2.3"
+ 1
+ version_compare "1.2.3" "<" "1.2.3"
+ 0
+ version_compare "1.2.3" "<=" "1.2.3"
+ 1
+ version_compare "1.2.3" "!=" "1.2.3"
+ 0
+ version_compare "1.2.3" "=" "1.2.3"
+ 1
+```
+
+## Example
+
+```shell
+#!/usr/bin/env bash
+
+v1=1.0.0
+v2=1.0.1
+op='>='
+
+result=$(version_compare "$v1 $op $v2")
+if [ -z "$result" ]; then
+ # operation failed
+ exit 1
+fi
+
+if (( result )); then
+ # operation true
+else
+ # operation false
+fi
+``` \ No newline at end of file
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..0f08e88
--- /dev/null
+++ b/main.c
@@ -0,0 +1,321 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#define GT 1 << 1
+#define LT 1 << 2
+#define EQ 1 << 3
+#define NOT 1 << 4
+
+/**
+ * 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;
+
+ orig = (*s);
+ if (strlen((*s)) > 1) {
+ (*s) = (*s) + strlen((*s)) - 1;
+ } else {
+ return (*s);
+ }
+
+ if (!isblank(*(*s)) || *(*s) == '\0') {
+ (*s) = orig;
+ return (*s);
+ }
+
+ while (isblank(*(*s)) && *(*s) != '\0') {
+ end = (*s);
+ (*s)--;
+ }
+ *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, numspace);
+ memmove((*s), space, strlen(space) + numspace);
+ (*s)[strlen((*s))] = '\0';
+ 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
+ */
+int version_sum(const char *str) {
+ int result;
+ char *s, *ptr, *end;
+
+ result = 0;
+ s = strdup(str);
+ 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;
+ 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++;
+ }
+
+ 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;
+
+ result_a = version_sum(aa);
+ result_b = version_sum(bb);
+ 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 main(int argc, char *argv[]) {
+ int result, op, must_free;
+ 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]);
+ exit(1);
+ }
+
+ must_free = 0;
+ if (argc < 3) {
+ int i;
+ i = 0;
+ arg = strdup(argv[1]);
+ arg_orig = arg;
+ collapse_whitespace(&arg);
+
+ for (; (token = strsep(&arg, " ")) != NULL; i++) {
+ 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]);
+ exit(-2);
+ }
+
+ v1 = tokens[0];
+ operator = tokens[1];
+ v2 = tokens[2];
+ must_free = 1;
+ } 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);
+ exit(1);
+ }
+
+ result = version_compare(op, v1, v2);
+ printf("%d\n", result);
+
+ if (must_free) {
+ for (int i = 0; tokens[i] != NULL; i++) {
+ free(tokens[i]);
+ }
+ free(arg);
+ }
+ return 0;
+}