diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | CMakeLists.txt | 14 | ||||
-rw-r--r-- | README.md | 55 | ||||
-rw-r--r-- | main.c | 321 |
4 files changed, 393 insertions, 1 deletions
@@ -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) @@ -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 @@ -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; +} |