diff options
-rw-r--r-- | .github/workflows/cmake.yml | 45 | ||||
-rw-r--r-- | CMakeLists.txt | 14 | ||||
-rw-r--r-- | main.c | 320 | ||||
-rw-r--r-- | tests.c | 353 | ||||
-rw-r--r-- | version_compare.c | 369 | ||||
-rw-r--r-- | version_compare.h | 18 |
6 files changed, 800 insertions, 319 deletions
diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 0000000..8e3eb97 --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,45 @@ +name: CMake + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + tests: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + include: + - name: Debug (Linux) + os: ubuntu-latest + build_type: Debug + + - name: Debug (MacOS) + os: macos-latest + build_type: Debug + + - name: Release (Linux) + os: ubuntu-latest + build_type: Release + + - name: Release (MacOS) + os: macos-latest + build_type: Release + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + run: cmake -B ${{ github.workspace }}/build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + + - name: Build + run: cmake --build ${{ github.workspace }}/build --config ${{ matrix.build_type }} + + - name: Test + working-directory: ${{ github.workspace }}/build + run: ctest -V -C ${{ matrix.build_type }} + diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ccb593..f780fe9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,4 +11,16 @@ else() add_compile_options(-Wall -Wextra -pedantic) endif() -add_executable(version_compare main.c) +include(CTest) + +add_library(vcmp STATIC version_compare.c version_compare.h) +target_compile_definitions(vcmp PUBLIC ENABLE_TESTING=1) + +add_executable(test_version_compare tests.c version_compare.h) +target_link_libraries(test_version_compare vcmp) + +add_test(test test_version_compare) +add_executable(version_compare main.c version_compare.h) +target_link_libraries(version_compare vcmp) + + @@ -1,321 +1,5 @@ -#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(""); -} +#include "version_compare.h" 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; + return entry(argc, argv); } @@ -0,0 +1,353 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include "version_compare.h" + +struct TestCase_strings { + char *s; + const char *result; +}; + +static struct TestCase_strings test_cases_collapse_whitespace[] = { + {"", ""}, + {" ", ""}, + {" ", ""}, + {" leading", "leading"}, + {"trailing ", "trailing"}, + {" leading", "leading"}, + {" leading and trailing ", "leading and trailing"}, + {"This line will be collapsed", "This line will be collapsed"}, +}; + +static struct TestCase_strings test_cases_lstrip[] = { + {"", ""}, + {" ", ""}, + {" ", ""}, + {" leading", "leading"}, + {"trailing ", "trailing "}, + {" leading", "leading"}, + {" leading and trailing ", "leading and trailing "}, + {"This line will be collapsed", "This line will be collapsed"}, +}; + +static struct TestCase_strings test_cases_rstrip[] = { + {"", ""}, + {" ", ""}, + {" ", ""}, + {" leading", " leading"}, + {"trailing ", "trailing"}, + {" leading", " leading"}, + {" leading and trailing ", " leading and trailing"}, + {"This line will be collapsed", "This line will be collapsed"}, +}; + +struct TestCase_version_compare { + char *a, *op, *b; + int result; +}; + +static struct TestCase_version_compare test_cases_version_compare[] = { + {"0", "=", "0", 1}, + {"0", "<", "1",1}, + {"0", "<=", "1",1}, + {"0", ">", "1",0}, + {"0", ">=", "1",0}, + {"0", "!=", "1",1}, + + {"1a", "=", "1b", 0}, + {"1a", "<", "1b", 1}, + {"1a", "<=", "1b", 1}, + {"1a", ">", "1b", 0}, + {"1a", ">=", "1b", 0}, + {"1a", "!=", "1b", 1}, + + {"1.0", "=", "1.0.0", 1}, + {"1.0", "<", "1.0.0", 0}, + {"1.0", "<=", "1.0.0", 1}, + {"1.0", ">", "1.0.0", 0}, + {"1.0", ">=", "1.0.0", 1}, + {"1.0", "!=", "1.0.0", 0}, + + {"1.0a", "=", "1.0.0", 0}, + {"1.0a", "<", "1.0.0", 0}, + {"1.0a", "<=", "1.0.0", 0}, + {"1.0a", ">", "1.0.0", 1}, + {"1.0a", ">=", "1.0.0", 1}, + {"1.0a", "!=", "1.0.0", 1}, + + {"2022.1", "=", "2022.4", 0}, + {"2022.1", "<", "2022.4", 1}, + {"2022.1", "<=", "2022.4", 1}, + {"2022.1", ">", "2022.4", 0}, + {"2022.1", ">=", "2022.4", 0}, + {"2022.1", "!=", "2022.4", 1}, + + {"2022.4", "=", "2022.1", 0}, + {"2022.4", "<", "2022.1", 0}, + {"2022.4", "<=", "2022.1", 0}, + {"2022.4", ">", "2022.1", 1}, + {"2022.4", ">=", "2022.1", 1}, + {"2022.4", "!=", "2022.1", 1}, +}; + +static struct TestCase_version_compare error_cases_version_compare[] = { + // nonsense++ + {"", "=", "", -1}, + {" ", "=", " ", -1}, + {"a", "", "a", -1}, + {"a", "", "b", -1}, + {"a", "@", "b", -1}, +}; + +static int run_cases_version_compare(struct TestCase_version_compare tests[], size_t size) { + int failed = 0; + for (size_t i = 0; i < size; i++) { + int result = 0; + struct TestCase_version_compare *test = &tests[i]; + int op = version_parse_operator(test->op); + result = version_compare(op, test->a, test->b); + + printf("%s %s %s is %s", test->a, test->op, test->b, result ? "TRUE" : "FALSE" ); + if (test->result != result) { + printf(" [FAILED: got %d, expected %d]\n", result, test->result); + failed++; + } else { + puts(""); + } + } + return failed; +} + +typedef char *(*strfn) (char **s); + +static int run_cases_string(struct TestCase_strings tests[], size_t size, strfn fn) { + int failed = 0; + for (size_t i = 0; i < size; i++) { + int result = 0; + struct TestCase_strings *test = &tests[i]; + char *s = strdup(test->s); + + fn(&s); + result = strcmp(test->result, s); + + printf("'%s' is %s", s, !result ? "CORRECT" : "INCORRECT" ); + if (result) { + printf(" [FAILED: got '%s', expected '%s']\n", s, test->result); + failed++; + } else { + puts(""); + } + free(s); + } + return failed; +} + + +char **make_argv(int *argc, char *argv[]) { + char **args; + *argc = 0; + + for (size_t i = 0; argv[i] != NULL; i++) { + *argc = *argc + 1; + } + + //char **args = calloc(*argc + 1, sizeof(*argv)); + args = calloc(*argc + 1, sizeof(*argv)); + if (!args) { + perror("unable to allocate args array"); + return NULL; + } + + args[0] = strdup("version_compare"); + if (!args[0]) { + perror("unable to allocate mock program name"); + return NULL; + } + + for (int i = 0; i < *argc; i++) { + args[i + 1] = strdup(argv[i]); + if (!args[i + 1]) { + perror("unable to allocate argument in array"); + return NULL; + } + } + *argc = *argc + 1; + return args; +} + +void free_argv(int argc, char *argv[]) { + for (int i = 0; i < argc; i++) { + free(argv[i]); + } + free(argv); +} + +int run_program(char *argv[]) { + int result = 0; + int argc = 0; + char **args = NULL; + + args = make_argv(&argc, argv); + if (!args) { + perror("make_argv failed"); + return -1; + } + const char *filename = "stdout.log"; + char data[255] = {0}; + char *end = NULL; + int o_stdout; + int save_stdout; + + + o_stdout = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0600); + if (o_stdout == -1) { + perror("unable to open stdout log"); + goto run_program_failed; + } + + save_stdout = dup(fileno(stdout)); + if (save_stdout == -1) { + perror("unable to duplicate stdout"); + goto run_program_failed; + } + + fflush(stdout); + if (dup2(o_stdout, fileno(stdout)) == -1) { + perror("unable to redirect stdout"); + goto run_program_failed; + } + + result = entry(argc, args); + + fflush(stdout); + close(o_stdout); + + if (dup2(save_stdout, fileno(stdout)) == -1) { + free_argv(argc, args); + perror("unable to restore stdout"); + goto run_program_failed; + } + close(save_stdout); + + if (result < 0) { + free_argv(argc, args); + return result; + } + + if (access(filename, F_OK) < 0) { + perror(filename); + goto run_program_failed; + } + + FILE *fp = fopen(filename, "r"); + if (!fp) { + perror("unable to open output log file"); + goto run_program_failed; + } + + if ((fgets(data, sizeof(data) - 1, fp)) == NULL) { + fprintf(stderr, "no output recorded in main program execution\n"); + remove(filename); + fclose(fp); + goto run_program_failed; + } + + result = (int)strtol(data, &end, 10); + if (!end) { + fprintf(stderr, "unexpected error in main program execution\n"); + remove(filename); + fclose(fp); + goto run_program_failed; + } + + fclose(fp); + remove(filename); + free_argv(argc, args); + return result; + +run_program_failed: + free_argv(argc, args); + return -1; +} + +static int run_cases_program_split(struct TestCase_version_compare tests[], size_t size) { + int failed = 0; + for (size_t i = 0; i < size; i++) { + int result = 0; + struct TestCase_version_compare *test = &tests[i]; + result = run_program((char *[]){test->a, test->op, test->b, NULL}); + + printf("%s %s %s is %s", test->a, test->op, test->b, result ? "TRUE" : "FALSE" ); + if (test->result != result) { + printf(" [FAILED: got %d, expected %d]\n", result, test->result); + failed++; + } else { + puts(""); + } + } + return failed; +} + +static int run_cases_program_standalone(struct TestCase_version_compare tests[], size_t size) { + int failed = 0; + for (size_t i = 0; i < size; i++) { + int result = 0; + struct TestCase_version_compare *test = &tests[i]; + char data[255] = {0}; + + snprintf(data, sizeof(data) - 1, "%s %s %s", test->a, test->op, test->b); + result = run_program((char *[]){data, NULL}); + + printf("%s %s %s is %s", test->a, test->op, test->b, result ? "TRUE" : "FALSE" ); + if (test->result != result) { + printf(" [FAILED: got %d, expected %d]\n", result, test->result); + failed++; + } else { + puts(""); + } + } + return failed; +} + +int main() { + int failed = 0; + + printf("\nTEST version_compare()\n"); + failed += run_cases_version_compare(test_cases_version_compare, + sizeof(test_cases_version_compare) / sizeof(test_cases_version_compare[0])); + printf("\nTEST version_compare errors()\n"); + failed += run_cases_version_compare(error_cases_version_compare, + sizeof(error_cases_version_compare) / sizeof(error_cases_version_compare[0])); + printf("\nTEST collapse_whitespace()\n"); + failed += run_cases_string(test_cases_collapse_whitespace, + sizeof(test_cases_collapse_whitespace) / sizeof(test_cases_collapse_whitespace[0]), + &collapse_whitespace); + printf("\nTEST lstrip()\n"); + failed += run_cases_string(test_cases_lstrip, + sizeof(test_cases_lstrip) / sizeof(test_cases_lstrip[0]), + &lstrip); + printf("\nTEST rstrip()\n"); + failed += run_cases_string(test_cases_rstrip, + sizeof(test_cases_rstrip) / sizeof(test_cases_rstrip[0]), + &rstrip); + + printf("\nTEST main program entry point (split string)\n"); + failed += run_cases_program_split(test_cases_version_compare, + sizeof(test_cases_version_compare) / sizeof(test_cases_version_compare[0])); + + printf("\nTEST main program entry point errors (split string)\n"); + failed += run_cases_program_split(error_cases_version_compare, + sizeof(error_cases_version_compare) / sizeof(error_cases_version_compare[0])); + + printf("\nTEST main program entry point (standalone string)\n"); + failed += run_cases_program_standalone(test_cases_version_compare, + sizeof(test_cases_version_compare) / sizeof(test_cases_version_compare[0])); + + printf("\nTEST main program entry point errors (standalone string)\n"); + failed += run_cases_program_standalone(error_cases_version_compare, + sizeof(error_cases_version_compare) / sizeof(error_cases_version_compare[0])); + + return failed != 0; +} 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; +} diff --git a/version_compare.h b/version_compare.h new file mode 100644 index 0000000..a951e53 --- /dev/null +++ b/version_compare.h @@ -0,0 +1,18 @@ +#ifndef VERSION_COMPARE_VERSION_COMPARE_H +#define VERSION_COMPARE_VERSION_COMPARE_H + +#define GT 1 << 1 +#define LT 1 << 2 +#define EQ 1 << 3 +#define NOT 1 << 4 + +int isempty(char *str); +char *lstrip(char **s); +char *rstrip(char **s); +char *collapse_whitespace(char **s); +int version_sum(const char *str); +int version_parse_operator(char *str); +int version_compare(int flags, const char *aa, const char *bb); +int entry(int argc, char *argv[]); + +#endif //VERSION_COMPARE_VERSION_COMPARE_H
\ No newline at end of file |