Message ID | 20200609162639.14788-1-sde@unmatched.eu |
---|---|
State | Changes Requested |
Headers | show |
Series | [1/4] Add semver library | expand |
Hi Stji, what have you addressed between V1 and V2 ? Can you add a version number to new versions of series so that it is easier to follow, with a short history list ("Changes in Vx:") under the "---" line ? Thanks ! On 09.06.20 18:26, Stijn Devriendt wrote: > Originally from: https://github.com/h2non/semver.c.git > Original commit: bd1db234a68f305ed10268bd023df1ad672061d7 > License: MIT > > Signed-off-by: Stijn Devriendt <sde@unmatched.eu> > --- > core/Makefile | 3 +- > core/semver.c | 638 +++++++++++++++++++++++++++++++++++++++++++++++ > include/semver.h | 105 ++++++++ > 3 files changed, 745 insertions(+), 1 deletion(-) > create mode 100644 core/semver.c > create mode 100644 include/semver.h > > diff --git a/core/Makefile b/core/Makefile > index a1d5f09..ad94120 100644 > --- a/core/Makefile > +++ b/core/Makefile > @@ -23,4 +23,5 @@ obj-y += swupdate.o \ > progress_thread.o \ > parsing_library.o \ > artifacts_versions.o \ > - swupdate_dict.o > + swupdate_dict.o \ > + semver.o > diff --git a/core/semver.c b/core/semver.c > new file mode 100644 > index 0000000..77a11ba > --- /dev/null > +++ b/core/semver.c > @@ -0,0 +1,638 @@ > +/* > + * semver.c > + * > + * Copyright (c) 2015-2017 Tomas Aparicio > + * MIT licensed This is still unchanged, it must be a SPDX identifier. > + */ > + > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include "semver.h" > + > +#define SLICE_SIZE 50 > +#define DELIMITER "." > +#define PR_DELIMITER "-" > +#define MT_DELIMITER "+" > +#define NUMBERS "0123456789" > +#define ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" > +#define DELIMITERS DELIMITER PR_DELIMITER MT_DELIMITER > +#define VALID_CHARS NUMBERS ALPHA DELIMITERS > + > +static const size_t MAX_SIZE = sizeof(char) * 255; > +static const int MAX_SAFE_INT = (unsigned int) -1 >> 1; > + > +/** > + * Define comparison operators, storing the > + * ASCII code per each symbol in hexadecimal notation. > + */ > + > +enum operators { > + SYMBOL_GT = 0x3e, > + SYMBOL_LT = 0x3c, > + SYMBOL_EQ = 0x3d, > + SYMBOL_TF = 0x7e, > + SYMBOL_CF = 0x5e > +}; > + > +/** > + * Private helpers > + */ > + > +/* > + * Remove [begin:len-begin] from str by moving len data from begin+len to begin. > + * If len is negative cut out to the end of the string. > + */ > +static int > +strcut (char *str, int begin, int len) { > + size_t l; > + l = strlen(str); > + > + if((int)l < 0 || (int)l > MAX_SAFE_INT) return -1; > + > + if (len < 0) len = l - begin + 1; > + if (begin + len > (int)l) len = l - begin; > + memmove(str + begin, str + begin + len, l - len + 1 - begin); > + > + return len; > +} > + > +static int > +contains (const char c, const char *matrix, size_t len) { > + size_t x; > + for (x = 0; x < len; x++) > + if ((char) matrix[x] == c) return 1; > + return 0; > +} > + > +static int > +has_valid_chars (const char *str, const char *matrix) { > + size_t i, len, mlen; > + len = strlen(str); > + mlen = strlen(matrix); > + > + for (i = 0; i < len; i++) > + if (contains(str[i], matrix, mlen) == 0) > + return 0; > + > + return 1; > +} > + > +static int > +binary_comparison (int x, int y) { > + if (x == y) return 0; > + if (x > y) return 1; > + return -1; > +} > + > +static int > +parse_int (const char *s) { > + int valid, num; > + valid = has_valid_chars(s, NUMBERS); > + if (valid == 0) return -1; > + > + num = strtol(s, NULL, 10); > + if (num > MAX_SAFE_INT) return -1; > + > + return num; > +} > + > +/* > + * Return a string allocated on the heap with the content from sep to end and > + * terminate buf at sep. > + */ > +static char * > +parse_slice (char *buf, char sep) { > + char *pr, *part; > + int plen; > + > + /* Find separator in buf */ > + pr = strchr(buf, sep); > + if (pr == NULL) return NULL; > + /* Length from separator to end of buf */ > + plen = strlen(pr); > + > + /* Copy from buf into new string */ > + part = (char*)calloc(plen + 1, sizeof(*part)); > + if (part == NULL) return NULL; > + memcpy(part, pr + 1, plen); > + /* Null terminate new string */ > + part[plen] = '\0'; > + > + /* Terminate buf where separator was */ > + *pr = '\0'; > + > + return part; > +} > + > +/** > + * Parses a string as semver expression. > + * > + * Returns: > + * > + * `0` - Parsed successfully > + * `-1` - In case of error > + */ > + > +int > +semver_parse (const char *str, semver_t *ver) { > + int valid, res; > + size_t len; > + char *buf; > + valid = semver_is_valid(str); > + if (!valid) return -1; > + > + len = strlen(str); > + buf = (char*)calloc(len + 1, sizeof(*buf)); > + if (buf == NULL) return -1; > + strcpy(buf, str); > + > + ver->metadata = parse_slice(buf, MT_DELIMITER[0]); > + ver->prerelease = parse_slice(buf, PR_DELIMITER[0]); > + > + res = semver_parse_version(buf, ver); > + free(buf); > +#if DEBUG > 0 > + printf("[debug] semver.c %s = %d.%d.%d, %s %s\n", str, ver->major, ver->minor, ver->patch, ver->prerelease, ver->metadata); > +#endif > + return res; > +} > + > +/** > + * Parses a given string as semver expression. > + * > + * Returns: > + * > + * `0` - Parsed successfully > + * `-1` - Parse error or invalid > + */ > + > +int > +semver_parse_version (const char *str, semver_t *ver) { > + size_t len; > + int index, value; > + char *slice, *next, *endptr; > + slice = (char *) str; > + index = 0; > + > + while (slice != NULL && index++ < 4) { > + next = strchr(slice, DELIMITER[0]); > + if (next == NULL) > + len = strlen(slice); > + else > + len = next - slice; > + if (len > SLICE_SIZE) return -1; > + > + /* Cast to integer and store */ > + value = strtol(slice, &endptr, 10); > + if (endptr != next && *endptr != '\0') return -1; > + > + switch (index) { > + case 1: ver->major = value; break; > + case 2: ver->minor = value; break; > + case 3: ver->patch = value; break; > + } > + > + /* Continue with the next slice */ > + if (next == NULL) > + slice = NULL; > + else > + slice = next + 1; > + } > + > + return 0; > +} > + > +static int > +compare_prerelease (char *x, char *y) { > + char *lastx, *lasty, *xptr, *yptr, *endptr; > + int xlen, ylen, xisnum, yisnum, xnum, ynum; > + int xn, yn, min, res; > + if (x == NULL && y == NULL) return 0; > + if (y == NULL && x) return -1; > + if (x == NULL && y) return 1; > + > + lastx = x; > + lasty = y; > + xlen = strlen(x); > + ylen = strlen(y); > + > + while (1) { > + if ((xptr = strchr(lastx, DELIMITER[0])) == NULL) > + xptr = x + xlen; > + if ((yptr = strchr(lasty, DELIMITER[0])) == NULL) > + yptr = y + ylen; > + > + xnum = strtol(lastx, &endptr, 10); > + xisnum = endptr == xptr ? 1 : 0; > + ynum = strtol(lasty, &endptr, 10); > + yisnum = endptr == yptr ? 1 : 0; > + > + if (xisnum && !yisnum) return -1; > + if (!xisnum && yisnum) return 1; > + > + if (xisnum && yisnum) { > + /* Numerical comparison */ > + if (xnum != ynum) return xnum < ynum ? -1 : 1; > + } else { > + /* String comparison */ > + xn = xptr - lastx; > + yn = yptr - lasty; > + min = xn < yn ? xn : yn; > + if ((res = strncmp(lastx, lasty, min))) return res < 0 ? -1 : 1; > + if (xn != yn) return xn < yn ? -1 : 1; > + } > + > + lastx = xptr + 1; > + lasty = yptr + 1; > + if (lastx == x + xlen + 1 && lasty == y + ylen + 1) break; > + if (lastx == x + xlen + 1) return -1; > + if (lasty == y + ylen + 1) return 1; > + } > + > + return 0; > +} > + > +int > +semver_compare_prerelease (semver_t x, semver_t y) { > + return compare_prerelease(x.prerelease, y.prerelease); > +} > + > +/** > + * Performs a major, minor and patch binary comparison (x, y). > + * This function is mostly used internally > + * > + * Returns: > + * > + * `0` - If versiona are equal > + * `1` - If x is higher than y > + * `-1` - If x is lower than y > + */ > + > +int > +semver_compare_version (semver_t x, semver_t y) { > + int res; > + > + if ((res = binary_comparison(x.major, y.major)) == 0) { > + if ((res = binary_comparison(x.minor, y.minor)) == 0) { > + return binary_comparison(x.patch, y.patch); > + } > + } > + > + return res; > +} > + > +/** > + * Compare two semantic versions (x, y). > + * > + * Returns: > + * - `1` if x is higher than y > + * - `0` if x is equal to y > + * - `-1` if x is lower than y > + */ > + > +int > +semver_compare (semver_t x, semver_t y) { > + int res; > + > + if ((res = semver_compare_version(x, y)) == 0) { > + return semver_compare_prerelease(x, y); > + } > + > + return res; > +} > + > +/** > + * Performs a `greater than` comparison > + */ > + > +int > +semver_gt (semver_t x, semver_t y) { > + return semver_compare(x, y) == 1; > +} > + > +/** > + * Performs a `lower than` comparison > + */ > + > +int > +semver_lt (semver_t x, semver_t y) { > + return semver_compare(x, y) == -1; > +} > + > +/** > + * Performs a `equality` comparison > + */ > + > +int > +semver_eq (semver_t x, semver_t y) { > + return semver_compare(x, y) == 0; > +} > + > +/** > + * Performs a `non equal to` comparison > + */ > + > +int > +semver_neq (semver_t x, semver_t y) { > + return semver_compare(x, y) != 0; > +} > + > +/** > + * Performs a `greater than or equal` comparison > + */ > + > +int > +semver_gte (semver_t x, semver_t y) { > + return semver_compare(x, y) >= 0; > +} > + > +/** > + * Performs a `lower than or equal` comparison > + */ > + > +int > +semver_lte (semver_t x, semver_t y) { > + return semver_compare(x, y) <= 0; > +} > + > +/** > + * Checks if version `x` can be satisfied by `y` > + * performing a comparison with caret operator. > + * > + * See: https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4 > + * > + * Returns: > + * > + * `1` - Can be satisfied > + * `0` - Cannot be satisfied > + */ > + > +int > +semver_satisfies_caret (semver_t x, semver_t y) { > + /* Major versions must always match. */ > + if (x.major == y.major) { > + /* If major version is 0, minor versions must match */ > + if (x.major == 0) { > + /* If minor version is 0, patch must match */ > + if (x.minor == 0){ > + return (x.minor == y.minor) && (x.patch == y.patch); > + } > + /* If minor version is not 0, patch must be >= */ > + else if (x.minor == y.minor){ > + return x.patch >= y.patch; > + } > + else{ > + return 0; > + } > + } > + else if (x.minor > y.minor){ > + return 1; > + } > + else if (x.minor == y.minor) > + { > + return x.patch >= y.patch; > + } > + else { > + return 0; > + } > + } > + return 0; > +} > + > +/** > + * Checks if version `x` can be satisfied by `y` > + * performing a comparison with tilde operator. > + * > + * See: https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1 > + * > + * Returns: > + * > + * `1` - Can be satisfied > + * `0` - Cannot be satisfied > + */ > + > +int > +semver_satisfies_patch (semver_t x, semver_t y) { > + return x.major == y.major > + && x.minor == y.minor; > +} > + > +/** > + * Checks if both versions can be satisfied > + * based on the given comparison operator. > + * > + * Allowed operators: > + * > + * - `=` - Equality > + * - `>=` - Higher or equal to > + * - `<=` - Lower or equal to > + * - `<` - Lower than > + * - `>` - Higher than > + * - `^` - Caret comparison (see https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4) > + * - `~` - Tilde comparison (see https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1) > + * > + * Returns: > + * > + * `1` - Can be satisfied > + * `0` - Cannot be satisfied > + */ > + > +int > +semver_satisfies (semver_t x, semver_t y, const char *op) { > + int first, second; > + /* Extract the comparison operator */ > + first = op[0]; > + second = op[1]; > + > + /* Caret operator */ > + if (first == SYMBOL_CF) > + return semver_satisfies_caret(x, y); > + > + /* Tilde operator */ > + if (first == SYMBOL_TF) > + return semver_satisfies_patch(x, y); > + > + /* Strict equality */ > + if (first == SYMBOL_EQ) > + return semver_eq(x, y); > + > + /* Greater than or equal comparison */ > + if (first == SYMBOL_GT) { > + if (second == SYMBOL_EQ) { > + return semver_gte(x, y); > + } > + return semver_gt(x, y); > + } > + > + /* Lower than or equal comparison */ > + if (first == SYMBOL_LT) { > + if (second == SYMBOL_EQ) { > + return semver_lte(x, y); > + } > + return semver_lt(x, y); > + } > + > + return 0; > +} > + > +/** > + * Free heep allocated memory of a given semver. > + * This is just a convenient function that you > + * should call when you're done. > + */ > + > +void > +semver_free (semver_t *x) { > + if (x->metadata) { > + free(x->metadata); > + x->metadata = NULL; > + } > + if (x->prerelease) { > + free(x->prerelease); > + x->prerelease = NULL; > + } > +} > + > +/** > + * Renders > + */ > + > +static void > +concat_num (char * str, int x, char * sep) { > + char buf[SLICE_SIZE] = {0}; > + if (sep == NULL) sprintf(buf, "%d", x); > + else sprintf(buf, "%s%d", sep, x); > + strcat(str, buf); > +} > + > +static void > +concat_char (char * str, char * x, char * sep) { > + char buf[SLICE_SIZE] = {0}; > + sprintf(buf, "%s%s", sep, x); > + strcat(str, buf); > +} > + > +/** > + * Render a given semver as string > + */ > + > +void > +semver_render (semver_t *x, char *dest) { > + concat_num(dest, x->major, NULL); > + concat_num(dest, x->minor, DELIMITER); > + concat_num(dest, x->patch, DELIMITER); > + if (x->prerelease) concat_char(dest, x->prerelease, PR_DELIMITER); > + if (x->metadata) concat_char(dest, x->metadata, MT_DELIMITER); > +} > + > +/** > + * Version bump helpers > + */ > + > +void > +semver_bump (semver_t *x) { > + x->major++; > +} > + > +void > +semver_bump_minor (semver_t *x) { > + x->minor++; > +} > + > +void > +semver_bump_patch (semver_t *x) { > + x->patch++; > +} > + > +/** > + * Helpers > + */ > + > +static int > +has_valid_length (const char *s) { > + return strlen(s) <= MAX_SIZE; > +} > + > +/** > + * Checks if a given semver string is valid > + * > + * Returns: > + * > + * `1` - Valid expression > + * `0` - Invalid > + */ > + > +int > +semver_is_valid (const char *s) { > + return has_valid_length(s) > + && has_valid_chars(s, VALID_CHARS); > +} > + > +/** > + * Removes non-valid characters in the given string. > + * > + * Returns: > + * > + * `0` - Valid > + * `-1` - Invalid input > + */ > + > +int > +semver_clean (char *s) { > + size_t i, len, mlen; > + int res; > + if (has_valid_length(s) == 0) return -1; > + > + len = strlen(s); > + mlen = strlen(VALID_CHARS); > + > + for (i = 0; i < len; i++) { > + if (contains(s[i], VALID_CHARS, mlen) == 0) { > + res = strcut(s, i, 1); > + if(res == -1) return -1; > + --len; --i; > + } > + } > + > + return 0; > +} > + > +static int > +char_to_int (const char * str) { > + int buf; > + size_t i,len, mlen; > + buf = 0; > + len = strlen(str); > + mlen = strlen(VALID_CHARS); > + > + for (i = 0; i < len; i++) > + if (contains(str[i], VALID_CHARS, mlen)) > + buf += (int) str[i]; > + > + return buf; > +} > + > +/** > + * Render a given semver as numeric value. > + * Useful for ordering and filtering. > + */ > + > +int > +semver_numeric (semver_t *x) { > + int num; > + char buf[SLICE_SIZE * 3]; > + memset(&buf, 0, SLICE_SIZE * 3); > + > + if (x->major) concat_num(buf, x->major, NULL); > + if (x->major || x->minor) concat_num(buf, x->minor, NULL); > + if (x->major || x->minor || x->patch) concat_num(buf, x->patch, NULL); > + > + num = parse_int(buf); > + if(num == -1) return -1; > + > + if (x->prerelease) num += char_to_int(x->prerelease); > + if (x->metadata) num += char_to_int(x->metadata); > + > + return num; > +} > diff --git a/include/semver.h b/include/semver.h > new file mode 100644 > index 0000000..1b48670 > --- /dev/null > +++ b/include/semver.h > @@ -0,0 +1,105 @@ > +/* > + * semver.h > + * > + * Copyright (c) 2015-2017 Tomas Aparicio > + * MIT licensed > + */ > + > +#ifndef __SEMVER_H > +#define __SEMVER_H > + > +#ifdef __cplusplus > +extern "C" { > +#endif > + > +#ifndef SEMVER_VERSION > +#define SEMVER_VERSION "0.2.0" > +#endif > + > +/** > + * semver_t struct > + */ > + > +typedef struct semver_version_s { > + int major; > + int minor; > + int patch; > + char * metadata; > + char * prerelease; > +} semver_t; > + > +/** > + * Set prototypes > + */ > + > +int > +semver_satisfies (semver_t x, semver_t y, const char *op); > + > +int > +semver_satisfies_caret (semver_t x, semver_t y); > + > +int > +semver_satisfies_patch (semver_t x, semver_t y); > + > +int > +semver_compare (semver_t x, semver_t y); > + > +int > +semver_compare_version (semver_t x, semver_t y); > + > +int > +semver_compare_prerelease (semver_t x, semver_t y); > + > +int > +semver_gt (semver_t x, semver_t y); > + > +int > +semver_gte (semver_t x, semver_t y); > + > +int > +semver_lt (semver_t x, semver_t y); > + > +int > +semver_lte (semver_t x, semver_t y); > + > +int > +semver_eq (semver_t x, semver_t y); > + > +int > +semver_neq (semver_t x, semver_t y); > + > +int > +semver_parse (const char *str, semver_t *ver); > + > +int > +semver_parse_version (const char *str, semver_t *ver); > + > +void > +semver_render (semver_t *x, char *dest); > + > +int > +semver_numeric (semver_t *x); > + > +void > +semver_bump (semver_t *x); > + > +void > +semver_bump_minor (semver_t *x); > + > +void > +semver_bump_patch (semver_t *x); > + > +void > +semver_free (semver_t *x); > + > +int > +semver_is_valid (const char *s); > + > +int > +semver_clean (char *s); > + > +#ifdef __cplusplus > +} > +#endif > + > +#endif > Best regards, Stefano Babic
On Wednesday, June 10, 2020 at 12:41:57 PM UTC+2, Stefano Babic wrote: > > Hi Stji, > > what have you addressed between V1 and V2 ? Can you add a version number > to new versions of series so that it is easier to follow, with a short > history list ("Changes in Vx:") under the "---" line ? Thanks ! > > Hi Stefano, Nothing actually. I just made sure that Tomas is included in the series. Regards, Stijn > On 09.06.20 18:26, Stijn Devriendt wrote: > > Originally from: https://github.com/h2non/semver.c.git > > Original commit: bd1db234a68f305ed10268bd023df1ad672061d7 > > License: MIT > > > > Signed-off-by: Stijn Devriendt <s...@unmatched.eu <javascript:>> > > --- > > core/Makefile | 3 +- > > core/semver.c | 638 +++++++++++++++++++++++++++++++++++++++++++++++ > > include/semver.h | 105 ++++++++ > > 3 files changed, 745 insertions(+), 1 deletion(-) > > create mode 100644 core/semver.c > > create mode 100644 include/semver.h > > > > diff --git a/core/Makefile b/core/Makefile > > index a1d5f09..ad94120 100644 > > --- a/core/Makefile > > +++ b/core/Makefile > > @@ -23,4 +23,5 @@ obj-y += swupdate.o \ > > progress_thread.o \ > > parsing_library.o \ > > artifacts_versions.o \ > > - swupdate_dict.o > > + swupdate_dict.o \ > > + semver.o > > diff --git a/core/semver.c b/core/semver.c > > new file mode 100644 > > index 0000000..77a11ba > > --- /dev/null > > +++ b/core/semver.c > > @@ -0,0 +1,638 @@ > > +/* > > + * semver.c > > + * > > + * Copyright (c) 2015-2017 Tomas Aparicio > > + * MIT licensed > > This is still unchanged, it must be a SPDX identifier. > > > + */ > > + > > +#include <stdio.h> > > +#include <stdlib.h> > > +#include <string.h> > > +#include "semver.h" > > + > > +#define SLICE_SIZE 50 > > +#define DELIMITER "." > > +#define PR_DELIMITER "-" > > +#define MT_DELIMITER "+" > > +#define NUMBERS "0123456789" > > +#define ALPHA > "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" > > +#define DELIMITERS DELIMITER PR_DELIMITER MT_DELIMITER > > +#define VALID_CHARS NUMBERS ALPHA DELIMITERS > > + > > +static const size_t MAX_SIZE = sizeof(char) * 255; > > +static const int MAX_SAFE_INT = (unsigned int) -1 >> 1; > > + > > +/** > > + * Define comparison operators, storing the > > + * ASCII code per each symbol in hexadecimal notation. > > + */ > > + > > +enum operators { > > + SYMBOL_GT = 0x3e, > > + SYMBOL_LT = 0x3c, > > + SYMBOL_EQ = 0x3d, > > + SYMBOL_TF = 0x7e, > > + SYMBOL_CF = 0x5e > > +}; > > + > > +/** > > + * Private helpers > > + */ > > + > > +/* > > + * Remove [begin:len-begin] from str by moving len data from begin+len > to begin. > > + * If len is negative cut out to the end of the string. > > + */ > > +static int > > +strcut (char *str, int begin, int len) { > > + size_t l; > > + l = strlen(str); > > + > > + if((int)l < 0 || (int)l > MAX_SAFE_INT) return -1; > > + > > + if (len < 0) len = l - begin + 1; > > + if (begin + len > (int)l) len = l - begin; > > + memmove(str + begin, str + begin + len, l - len + 1 - begin); > > + > > + return len; > > +} > > + > > +static int > > +contains (const char c, const char *matrix, size_t len) { > > + size_t x; > > + for (x = 0; x < len; x++) > > + if ((char) matrix[x] == c) return 1; > > + return 0; > > +} > > + > > +static int > > +has_valid_chars (const char *str, const char *matrix) { > > + size_t i, len, mlen; > > + len = strlen(str); > > + mlen = strlen(matrix); > > + > > + for (i = 0; i < len; i++) > > + if (contains(str[i], matrix, mlen) == 0) > > + return 0; > > + > > + return 1; > > +} > > + > > +static int > > +binary_comparison (int x, int y) { > > + if (x == y) return 0; > > + if (x > y) return 1; > > + return -1; > > +} > > + > > +static int > > +parse_int (const char *s) { > > + int valid, num; > > + valid = has_valid_chars(s, NUMBERS); > > + if (valid == 0) return -1; > > + > > + num = strtol(s, NULL, 10); > > + if (num > MAX_SAFE_INT) return -1; > > + > > + return num; > > +} > > + > > +/* > > + * Return a string allocated on the heap with the content from sep to > end and > > + * terminate buf at sep. > > + */ > > +static char * > > +parse_slice (char *buf, char sep) { > > + char *pr, *part; > > + int plen; > > + > > + /* Find separator in buf */ > > + pr = strchr(buf, sep); > > + if (pr == NULL) return NULL; > > + /* Length from separator to end of buf */ > > + plen = strlen(pr); > > + > > + /* Copy from buf into new string */ > > + part = (char*)calloc(plen + 1, sizeof(*part)); > > + if (part == NULL) return NULL; > > + memcpy(part, pr + 1, plen); > > + /* Null terminate new string */ > > + part[plen] = '\0'; > > + > > + /* Terminate buf where separator was */ > > + *pr = '\0'; > > + > > + return part; > > +} > > + > > +/** > > + * Parses a string as semver expression. > > + * > > + * Returns: > > + * > > + * `0` - Parsed successfully > > + * `-1` - In case of error > > + */ > > + > > +int > > +semver_parse (const char *str, semver_t *ver) { > > + int valid, res; > > + size_t len; > > + char *buf; > > + valid = semver_is_valid(str); > > + if (!valid) return -1; > > + > > + len = strlen(str); > > + buf = (char*)calloc(len + 1, sizeof(*buf)); > > + if (buf == NULL) return -1; > > + strcpy(buf, str); > > + > > + ver->metadata = parse_slice(buf, MT_DELIMITER[0]); > > + ver->prerelease = parse_slice(buf, PR_DELIMITER[0]); > > + > > + res = semver_parse_version(buf, ver); > > + free(buf); > > +#if DEBUG > 0 > > + printf("[debug] semver.c %s = %d.%d.%d, %s %s\n", str, ver->major, > ver->minor, ver->patch, ver->prerelease, ver->metadata); > > +#endif > > + return res; > > +} > > + > > +/** > > + * Parses a given string as semver expression. > > + * > > + * Returns: > > + * > > + * `0` - Parsed successfully > > + * `-1` - Parse error or invalid > > + */ > > + > > +int > > +semver_parse_version (const char *str, semver_t *ver) { > > + size_t len; > > + int index, value; > > + char *slice, *next, *endptr; > > + slice = (char *) str; > > + index = 0; > > + > > + while (slice != NULL && index++ < 4) { > > + next = strchr(slice, DELIMITER[0]); > > + if (next == NULL) > > + len = strlen(slice); > > + else > > + len = next - slice; > > + if (len > SLICE_SIZE) return -1; > > + > > + /* Cast to integer and store */ > > + value = strtol(slice, &endptr, 10); > > + if (endptr != next && *endptr != '\0') return -1; > > + > > + switch (index) { > > + case 1: ver->major = value; break; > > + case 2: ver->minor = value; break; > > + case 3: ver->patch = value; break; > > + } > > + > > + /* Continue with the next slice */ > > + if (next == NULL) > > + slice = NULL; > > + else > > + slice = next + 1; > > + } > > + > > + return 0; > > +} > > + > > +static int > > +compare_prerelease (char *x, char *y) { > > + char *lastx, *lasty, *xptr, *yptr, *endptr; > > + int xlen, ylen, xisnum, yisnum, xnum, ynum; > > + int xn, yn, min, res; > > + if (x == NULL && y == NULL) return 0; > > + if (y == NULL && x) return -1; > > + if (x == NULL && y) return 1; > > + > > + lastx = x; > > + lasty = y; > > + xlen = strlen(x); > > + ylen = strlen(y); > > + > > + while (1) { > > + if ((xptr = strchr(lastx, DELIMITER[0])) == NULL) > > + xptr = x + xlen; > > + if ((yptr = strchr(lasty, DELIMITER[0])) == NULL) > > + yptr = y + ylen; > > + > > + xnum = strtol(lastx, &endptr, 10); > > + xisnum = endptr == xptr ? 1 : 0; > > + ynum = strtol(lasty, &endptr, 10); > > + yisnum = endptr == yptr ? 1 : 0; > > + > > + if (xisnum && !yisnum) return -1; > > + if (!xisnum && yisnum) return 1; > > + > > + if (xisnum && yisnum) { > > + /* Numerical comparison */ > > + if (xnum != ynum) return xnum < ynum ? -1 : 1; > > + } else { > > + /* String comparison */ > > + xn = xptr - lastx; > > + yn = yptr - lasty; > > + min = xn < yn ? xn : yn; > > + if ((res = strncmp(lastx, lasty, min))) return res < 0 ? -1 : 1; > > + if (xn != yn) return xn < yn ? -1 : 1; > > + } > > + > > + lastx = xptr + 1; > > + lasty = yptr + 1; > > + if (lastx == x + xlen + 1 && lasty == y + ylen + 1) break; > > + if (lastx == x + xlen + 1) return -1; > > + if (lasty == y + ylen + 1) return 1; > > + } > > + > > + return 0; > > +} > > + > > +int > > +semver_compare_prerelease (semver_t x, semver_t y) { > > + return compare_prerelease(x.prerelease, y.prerelease); > > +} > > + > > +/** > > + * Performs a major, minor and patch binary comparison (x, y). > > + * This function is mostly used internally > > + * > > + * Returns: > > + * > > + * `0` - If versiona are equal > > + * `1` - If x is higher than y > > + * `-1` - If x is lower than y > > + */ > > + > > +int > > +semver_compare_version (semver_t x, semver_t y) { > > + int res; > > + > > + if ((res = binary_comparison(x.major, y.major)) == 0) { > > + if ((res = binary_comparison(x.minor, y.minor)) == 0) { > > + return binary_comparison(x.patch, y.patch); > > + } > > + } > > + > > + return res; > > +} > > + > > +/** > > + * Compare two semantic versions (x, y). > > + * > > + * Returns: > > + * - `1` if x is higher than y > > + * - `0` if x is equal to y > > + * - `-1` if x is lower than y > > + */ > > + > > +int > > +semver_compare (semver_t x, semver_t y) { > > + int res; > > + > > + if ((res = semver_compare_version(x, y)) == 0) { > > + return semver_compare_prerelease(x, y); > > + } > > + > > + return res; > > +} > > + > > +/** > > + * Performs a `greater than` comparison > > + */ > > + > > +int > > +semver_gt (semver_t x, semver_t y) { > > + return semver_compare(x, y) == 1; > > +} > > + > > +/** > > + * Performs a `lower than` comparison > > + */ > > + > > +int > > +semver_lt (semver_t x, semver_t y) { > > + return semver_compare(x, y) == -1; > > +} > > + > > +/** > > + * Performs a `equality` comparison > > + */ > > + > > +int > > +semver_eq (semver_t x, semver_t y) { > > + return semver_compare(x, y) == 0; > > +} > > + > > +/** > > + * Performs a `non equal to` comparison > > + */ > > + > > +int > > +semver_neq (semver_t x, semver_t y) { > > + return semver_compare(x, y) != 0; > > +} > > + > > +/** > > + * Performs a `greater than or equal` comparison > > + */ > > + > > +int > > +semver_gte (semver_t x, semver_t y) { > > + return semver_compare(x, y) >= 0; > > +} > > + > > +/** > > + * Performs a `lower than or equal` comparison > > + */ > > + > > +int > > +semver_lte (semver_t x, semver_t y) { > > + return semver_compare(x, y) <= 0; > > +} > > + > > +/** > > + * Checks if version `x` can be satisfied by `y` > > + * performing a comparison with caret operator. > > + * > > + * See: > https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4 > > + * > > + * Returns: > > + * > > + * `1` - Can be satisfied > > + * `0` - Cannot be satisfied > > + */ > > + > > +int > > +semver_satisfies_caret (semver_t x, semver_t y) { > > + /* Major versions must always match. */ > > + if (x.major == y.major) { > > + /* If major version is 0, minor versions must match */ > > + if (x.major == 0) { > > + /* If minor version is 0, patch must match */ > > + if (x.minor == 0){ > > + return (x.minor == y.minor) && (x.patch == y.patch); > > + } > > + /* If minor version is not 0, patch must be >= */ > > + else if (x.minor == y.minor){ > > + return x.patch >= y.patch; > > + } > > + else{ > > + return 0; > > + } > > + } > > + else if (x.minor > y.minor){ > > + return 1; > > + } > > + else if (x.minor == y.minor) > > + { > > + return x.patch >= y.patch; > > + } > > + else { > > + return 0; > > + } > > + } > > + return 0; > > +} > > + > > +/** > > + * Checks if version `x` can be satisfied by `y` > > + * performing a comparison with tilde operator. > > + * > > + * See: https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1 > > + * > > + * Returns: > > + * > > + * `1` - Can be satisfied > > + * `0` - Cannot be satisfied > > + */ > > + > > +int > > +semver_satisfies_patch (semver_t x, semver_t y) { > > + return x.major == y.major > > + && x.minor == y.minor; > > +} > > + > > +/** > > + * Checks if both versions can be satisfied > > + * based on the given comparison operator. > > + * > > + * Allowed operators: > > + * > > + * - `=` - Equality > > + * - `>=` - Higher or equal to > > + * - `<=` - Lower or equal to > > + * - `<` - Lower than > > + * - `>` - Higher than > > + * - `^` - Caret comparison (see > https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4) > > + * - `~` - Tilde comparison (see > https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1) > > + * > > + * Returns: > > + * > > + * `1` - Can be satisfied > > + * `0` - Cannot be satisfied > > + */ > > + > > +int > > +semver_satisfies (semver_t x, semver_t y, const char *op) { > > + int first, second; > > + /* Extract the comparison operator */ > > + first = op[0]; > > + second = op[1]; > > + > > + /* Caret operator */ > > + if (first == SYMBOL_CF) > > + return semver_satisfies_caret(x, y); > > + > > + /* Tilde operator */ > > + if (first == SYMBOL_TF) > > + return semver_satisfies_patch(x, y); > > + > > + /* Strict equality */ > > + if (first == SYMBOL_EQ) > > + return semver_eq(x, y); > > + > > + /* Greater than or equal comparison */ > > + if (first == SYMBOL_GT) { > > + if (second == SYMBOL_EQ) { > > + return semver_gte(x, y); > > + } > > + return semver_gt(x, y); > > + } > > + > > + /* Lower than or equal comparison */ > > + if (first == SYMBOL_LT) { > > + if (second == SYMBOL_EQ) { > > + return semver_lte(x, y); > > + } > > + return semver_lt(x, y); > > + } > > + > > + return 0; > > +} > > + > > +/** > > + * Free heep allocated memory of a given semver. > > + * This is just a convenient function that you > > + * should call when you're done. > > + */ > > + > > +void > > +semver_free (semver_t *x) { > > + if (x->metadata) { > > + free(x->metadata); > > + x->metadata = NULL; > > + } > > + if (x->prerelease) { > > + free(x->prerelease); > > + x->prerelease = NULL; > > + } > > +} > > + > > +/** > > + * Renders > > + */ > > + > > +static void > > +concat_num (char * str, int x, char * sep) { > > + char buf[SLICE_SIZE] = {0}; > > + if (sep == NULL) sprintf(buf, "%d", x); > > + else sprintf(buf, "%s%d", sep, x); > > + strcat(str, buf); > > +} > > + > > +static void > > +concat_char (char * str, char * x, char * sep) { > > + char buf[SLICE_SIZE] = {0}; > > + sprintf(buf, "%s%s", sep, x); > > + strcat(str, buf); > > +} > > + > > +/** > > + * Render a given semver as string > > + */ > > + > > +void > > +semver_render (semver_t *x, char *dest) { > > + concat_num(dest, x->major, NULL); > > + concat_num(dest, x->minor, DELIMITER); > > + concat_num(dest, x->patch, DELIMITER); > > + if (x->prerelease) concat_char(dest, x->prerelease, PR_DELIMITER); > > + if (x->metadata) concat_char(dest, x->metadata, MT_DELIMITER); > > +} > > + > > +/** > > + * Version bump helpers > > + */ > > + > > +void > > +semver_bump (semver_t *x) { > > + x->major++; > > +} > > + > > +void > > +semver_bump_minor (semver_t *x) { > > + x->minor++; > > +} > > + > > +void > > +semver_bump_patch (semver_t *x) { > > + x->patch++; > > +} > > + > > +/** > > + * Helpers > > + */ > > + > > +static int > > +has_valid_length (const char *s) { > > + return strlen(s) <= MAX_SIZE; > > +} > > + > > +/** > > + * Checks if a given semver string is valid > > + * > > + * Returns: > > + * > > + * `1` - Valid expression > > + * `0` - Invalid > > + */ > > + > > +int > > +semver_is_valid (const char *s) { > > + return has_valid_length(s) > > + && has_valid_chars(s, VALID_CHARS); > > +} > > + > > +/** > > + * Removes non-valid characters in the given string. > > + * > > + * Returns: > > + * > > + * `0` - Valid > > + * `-1` - Invalid input > > + */ > > + > > +int > > +semver_clean (char *s) { > > + size_t i, len, mlen; > > + int res; > > + if (has_valid_length(s) == 0) return -1; > > + > > + len = strlen(s); > > + mlen = strlen(VALID_CHARS); > > + > > + for (i = 0; i < len; i++) { > > + if (contains(s[i], VALID_CHARS, mlen) == 0) { > > + res = strcut(s, i, 1); > > + if(res == -1) return -1; > > + --len; --i; > > + } > > + } > > + > > + return 0; > > +} > > + > > +static int > > +char_to_int (const char * str) { > > + int buf; > > + size_t i,len, mlen; > > + buf = 0; > > + len = strlen(str); > > + mlen = strlen(VALID_CHARS); > > + > > + for (i = 0; i < len; i++) > > + if (contains(str[i], VALID_CHARS, mlen)) > > + buf += (int) str[i]; > > + > > + return buf; > > +} > > + > > +/** > > + * Render a given semver as numeric value. > > + * Useful for ordering and filtering. > > + */ > > + > > +int > > +semver_numeric (semver_t *x) { > > + int num; > > + char buf[SLICE_SIZE * 3]; > > + memset(&buf, 0, SLICE_SIZE * 3); > > + > > + if (x->major) concat_num(buf, x->major, NULL); > > + if (x->major || x->minor) concat_num(buf, x->minor, NULL); > > + if (x->major || x->minor || x->patch) concat_num(buf, x->patch, > NULL); > > + > > + num = parse_int(buf); > > + if(num == -1) return -1; > > + > > + if (x->prerelease) num += char_to_int(x->prerelease); > > + if (x->metadata) num += char_to_int(x->metadata); > > + > > + return num; > > +} > > diff --git a/include/semver.h b/include/semver.h > > new file mode 100644 > > index 0000000..1b48670 > > --- /dev/null > > +++ b/include/semver.h > > @@ -0,0 +1,105 @@ > > +/* > > + * semver.h > > + * > > + * Copyright (c) 2015-2017 Tomas Aparicio > > + * MIT licensed > > + */ > > + > > +#ifndef __SEMVER_H > > +#define __SEMVER_H > > + > > +#ifdef __cplusplus > > +extern "C" { > > +#endif > > + > > +#ifndef SEMVER_VERSION > > +#define SEMVER_VERSION "0.2.0" > > +#endif > > + > > +/** > > + * semver_t struct > > + */ > > + > > +typedef struct semver_version_s { > > + int major; > > + int minor; > > + int patch; > > + char * metadata; > > + char * prerelease; > > +} semver_t; > > + > > +/** > > + * Set prototypes > > + */ > > + > > +int > > +semver_satisfies (semver_t x, semver_t y, const char *op); > > + > > +int > > +semver_satisfies_caret (semver_t x, semver_t y); > > + > > +int > > +semver_satisfies_patch (semver_t x, semver_t y); > > + > > +int > > +semver_compare (semver_t x, semver_t y); > > + > > +int > > +semver_compare_version (semver_t x, semver_t y); > > + > > +int > > +semver_compare_prerelease (semver_t x, semver_t y); > > + > > +int > > +semver_gt (semver_t x, semver_t y); > > + > > +int > > +semver_gte (semver_t x, semver_t y); > > + > > +int > > +semver_lt (semver_t x, semver_t y); > > + > > +int > > +semver_lte (semver_t x, semver_t y); > > + > > +int > > +semver_eq (semver_t x, semver_t y); > > + > > +int > > +semver_neq (semver_t x, semver_t y); > > + > > +int > > +semver_parse (const char *str, semver_t *ver); > > + > > +int > > +semver_parse_version (const char *str, semver_t *ver); > > + > > +void > > +semver_render (semver_t *x, char *dest); > > + > > +int > > +semver_numeric (semver_t *x); > > + > > +void > > +semver_bump (semver_t *x); > > + > > +void > > +semver_bump_minor (semver_t *x); > > + > > +void > > +semver_bump_patch (semver_t *x); > > + > > +void > > +semver_free (semver_t *x); > > + > > +int > > +semver_is_valid (const char *s); > > + > > +int > > +semver_clean (char *s); > > + > > +#ifdef __cplusplus > > +} > > +#endif > > + > > +#endif > > > > Best regards, > Stefano Babic > > -- > ===================================================================== > DENX Software Engineering GmbH, Managing Director: Wolfgang Denk > HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany > Phone: +49-8142-66989-53 Fax: +49-8142-66989-80 Email: sba...@denx.de > <javascript:> > ===================================================================== >
diff --git a/core/Makefile b/core/Makefile index a1d5f09..ad94120 100644 --- a/core/Makefile +++ b/core/Makefile @@ -23,4 +23,5 @@ obj-y += swupdate.o \ progress_thread.o \ parsing_library.o \ artifacts_versions.o \ - swupdate_dict.o + swupdate_dict.o \ + semver.o diff --git a/core/semver.c b/core/semver.c new file mode 100644 index 0000000..77a11ba --- /dev/null +++ b/core/semver.c @@ -0,0 +1,638 @@ +/* + * semver.c + * + * Copyright (c) 2015-2017 Tomas Aparicio + * MIT licensed + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "semver.h" + +#define SLICE_SIZE 50 +#define DELIMITER "." +#define PR_DELIMITER "-" +#define MT_DELIMITER "+" +#define NUMBERS "0123456789" +#define ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define DELIMITERS DELIMITER PR_DELIMITER MT_DELIMITER +#define VALID_CHARS NUMBERS ALPHA DELIMITERS + +static const size_t MAX_SIZE = sizeof(char) * 255; +static const int MAX_SAFE_INT = (unsigned int) -1 >> 1; + +/** + * Define comparison operators, storing the + * ASCII code per each symbol in hexadecimal notation. + */ + +enum operators { + SYMBOL_GT = 0x3e, + SYMBOL_LT = 0x3c, + SYMBOL_EQ = 0x3d, + SYMBOL_TF = 0x7e, + SYMBOL_CF = 0x5e +}; + +/** + * Private helpers + */ + +/* + * Remove [begin:len-begin] from str by moving len data from begin+len to begin. + * If len is negative cut out to the end of the string. + */ +static int +strcut (char *str, int begin, int len) { + size_t l; + l = strlen(str); + + if((int)l < 0 || (int)l > MAX_SAFE_INT) return -1; + + if (len < 0) len = l - begin + 1; + if (begin + len > (int)l) len = l - begin; + memmove(str + begin, str + begin + len, l - len + 1 - begin); + + return len; +} + +static int +contains (const char c, const char *matrix, size_t len) { + size_t x; + for (x = 0; x < len; x++) + if ((char) matrix[x] == c) return 1; + return 0; +} + +static int +has_valid_chars (const char *str, const char *matrix) { + size_t i, len, mlen; + len = strlen(str); + mlen = strlen(matrix); + + for (i = 0; i < len; i++) + if (contains(str[i], matrix, mlen) == 0) + return 0; + + return 1; +} + +static int +binary_comparison (int x, int y) { + if (x == y) return 0; + if (x > y) return 1; + return -1; +} + +static int +parse_int (const char *s) { + int valid, num; + valid = has_valid_chars(s, NUMBERS); + if (valid == 0) return -1; + + num = strtol(s, NULL, 10); + if (num > MAX_SAFE_INT) return -1; + + return num; +} + +/* + * Return a string allocated on the heap with the content from sep to end and + * terminate buf at sep. + */ +static char * +parse_slice (char *buf, char sep) { + char *pr, *part; + int plen; + + /* Find separator in buf */ + pr = strchr(buf, sep); + if (pr == NULL) return NULL; + /* Length from separator to end of buf */ + plen = strlen(pr); + + /* Copy from buf into new string */ + part = (char*)calloc(plen + 1, sizeof(*part)); + if (part == NULL) return NULL; + memcpy(part, pr + 1, plen); + /* Null terminate new string */ + part[plen] = '\0'; + + /* Terminate buf where separator was */ + *pr = '\0'; + + return part; +} + +/** + * Parses a string as semver expression. + * + * Returns: + * + * `0` - Parsed successfully + * `-1` - In case of error + */ + +int +semver_parse (const char *str, semver_t *ver) { + int valid, res; + size_t len; + char *buf; + valid = semver_is_valid(str); + if (!valid) return -1; + + len = strlen(str); + buf = (char*)calloc(len + 1, sizeof(*buf)); + if (buf == NULL) return -1; + strcpy(buf, str); + + ver->metadata = parse_slice(buf, MT_DELIMITER[0]); + ver->prerelease = parse_slice(buf, PR_DELIMITER[0]); + + res = semver_parse_version(buf, ver); + free(buf); +#if DEBUG > 0 + printf("[debug] semver.c %s = %d.%d.%d, %s %s\n", str, ver->major, ver->minor, ver->patch, ver->prerelease, ver->metadata); +#endif + return res; +} + +/** + * Parses a given string as semver expression. + * + * Returns: + * + * `0` - Parsed successfully + * `-1` - Parse error or invalid + */ + +int +semver_parse_version (const char *str, semver_t *ver) { + size_t len; + int index, value; + char *slice, *next, *endptr; + slice = (char *) str; + index = 0; + + while (slice != NULL && index++ < 4) { + next = strchr(slice, DELIMITER[0]); + if (next == NULL) + len = strlen(slice); + else + len = next - slice; + if (len > SLICE_SIZE) return -1; + + /* Cast to integer and store */ + value = strtol(slice, &endptr, 10); + if (endptr != next && *endptr != '\0') return -1; + + switch (index) { + case 1: ver->major = value; break; + case 2: ver->minor = value; break; + case 3: ver->patch = value; break; + } + + /* Continue with the next slice */ + if (next == NULL) + slice = NULL; + else + slice = next + 1; + } + + return 0; +} + +static int +compare_prerelease (char *x, char *y) { + char *lastx, *lasty, *xptr, *yptr, *endptr; + int xlen, ylen, xisnum, yisnum, xnum, ynum; + int xn, yn, min, res; + if (x == NULL && y == NULL) return 0; + if (y == NULL && x) return -1; + if (x == NULL && y) return 1; + + lastx = x; + lasty = y; + xlen = strlen(x); + ylen = strlen(y); + + while (1) { + if ((xptr = strchr(lastx, DELIMITER[0])) == NULL) + xptr = x + xlen; + if ((yptr = strchr(lasty, DELIMITER[0])) == NULL) + yptr = y + ylen; + + xnum = strtol(lastx, &endptr, 10); + xisnum = endptr == xptr ? 1 : 0; + ynum = strtol(lasty, &endptr, 10); + yisnum = endptr == yptr ? 1 : 0; + + if (xisnum && !yisnum) return -1; + if (!xisnum && yisnum) return 1; + + if (xisnum && yisnum) { + /* Numerical comparison */ + if (xnum != ynum) return xnum < ynum ? -1 : 1; + } else { + /* String comparison */ + xn = xptr - lastx; + yn = yptr - lasty; + min = xn < yn ? xn : yn; + if ((res = strncmp(lastx, lasty, min))) return res < 0 ? -1 : 1; + if (xn != yn) return xn < yn ? -1 : 1; + } + + lastx = xptr + 1; + lasty = yptr + 1; + if (lastx == x + xlen + 1 && lasty == y + ylen + 1) break; + if (lastx == x + xlen + 1) return -1; + if (lasty == y + ylen + 1) return 1; + } + + return 0; +} + +int +semver_compare_prerelease (semver_t x, semver_t y) { + return compare_prerelease(x.prerelease, y.prerelease); +} + +/** + * Performs a major, minor and patch binary comparison (x, y). + * This function is mostly used internally + * + * Returns: + * + * `0` - If versiona are equal + * `1` - If x is higher than y + * `-1` - If x is lower than y + */ + +int +semver_compare_version (semver_t x, semver_t y) { + int res; + + if ((res = binary_comparison(x.major, y.major)) == 0) { + if ((res = binary_comparison(x.minor, y.minor)) == 0) { + return binary_comparison(x.patch, y.patch); + } + } + + return res; +} + +/** + * Compare two semantic versions (x, y). + * + * Returns: + * - `1` if x is higher than y + * - `0` if x is equal to y + * - `-1` if x is lower than y + */ + +int +semver_compare (semver_t x, semver_t y) { + int res; + + if ((res = semver_compare_version(x, y)) == 0) { + return semver_compare_prerelease(x, y); + } + + return res; +} + +/** + * Performs a `greater than` comparison + */ + +int +semver_gt (semver_t x, semver_t y) { + return semver_compare(x, y) == 1; +} + +/** + * Performs a `lower than` comparison + */ + +int +semver_lt (semver_t x, semver_t y) { + return semver_compare(x, y) == -1; +} + +/** + * Performs a `equality` comparison + */ + +int +semver_eq (semver_t x, semver_t y) { + return semver_compare(x, y) == 0; +} + +/** + * Performs a `non equal to` comparison + */ + +int +semver_neq (semver_t x, semver_t y) { + return semver_compare(x, y) != 0; +} + +/** + * Performs a `greater than or equal` comparison + */ + +int +semver_gte (semver_t x, semver_t y) { + return semver_compare(x, y) >= 0; +} + +/** + * Performs a `lower than or equal` comparison + */ + +int +semver_lte (semver_t x, semver_t y) { + return semver_compare(x, y) <= 0; +} + +/** + * Checks if version `x` can be satisfied by `y` + * performing a comparison with caret operator. + * + * See: https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4 + * + * Returns: + * + * `1` - Can be satisfied + * `0` - Cannot be satisfied + */ + +int +semver_satisfies_caret (semver_t x, semver_t y) { + /* Major versions must always match. */ + if (x.major == y.major) { + /* If major version is 0, minor versions must match */ + if (x.major == 0) { + /* If minor version is 0, patch must match */ + if (x.minor == 0){ + return (x.minor == y.minor) && (x.patch == y.patch); + } + /* If minor version is not 0, patch must be >= */ + else if (x.minor == y.minor){ + return x.patch >= y.patch; + } + else{ + return 0; + } + } + else if (x.minor > y.minor){ + return 1; + } + else if (x.minor == y.minor) + { + return x.patch >= y.patch; + } + else { + return 0; + } + } + return 0; +} + +/** + * Checks if version `x` can be satisfied by `y` + * performing a comparison with tilde operator. + * + * See: https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1 + * + * Returns: + * + * `1` - Can be satisfied + * `0` - Cannot be satisfied + */ + +int +semver_satisfies_patch (semver_t x, semver_t y) { + return x.major == y.major + && x.minor == y.minor; +} + +/** + * Checks if both versions can be satisfied + * based on the given comparison operator. + * + * Allowed operators: + * + * - `=` - Equality + * - `>=` - Higher or equal to + * - `<=` - Lower or equal to + * - `<` - Lower than + * - `>` - Higher than + * - `^` - Caret comparison (see https://docs.npmjs.com/misc/semver#caret-ranges-1-2-3-0-2-5-0-0-4) + * - `~` - Tilde comparison (see https://docs.npmjs.com/misc/semver#tilde-ranges-1-2-3-1-2-1) + * + * Returns: + * + * `1` - Can be satisfied + * `0` - Cannot be satisfied + */ + +int +semver_satisfies (semver_t x, semver_t y, const char *op) { + int first, second; + /* Extract the comparison operator */ + first = op[0]; + second = op[1]; + + /* Caret operator */ + if (first == SYMBOL_CF) + return semver_satisfies_caret(x, y); + + /* Tilde operator */ + if (first == SYMBOL_TF) + return semver_satisfies_patch(x, y); + + /* Strict equality */ + if (first == SYMBOL_EQ) + return semver_eq(x, y); + + /* Greater than or equal comparison */ + if (first == SYMBOL_GT) { + if (second == SYMBOL_EQ) { + return semver_gte(x, y); + } + return semver_gt(x, y); + } + + /* Lower than or equal comparison */ + if (first == SYMBOL_LT) { + if (second == SYMBOL_EQ) { + return semver_lte(x, y); + } + return semver_lt(x, y); + } + + return 0; +} + +/** + * Free heep allocated memory of a given semver. + * This is just a convenient function that you + * should call when you're done. + */ + +void +semver_free (semver_t *x) { + if (x->metadata) { + free(x->metadata); + x->metadata = NULL; + } + if (x->prerelease) { + free(x->prerelease); + x->prerelease = NULL; + } +} + +/** + * Renders + */ + +static void +concat_num (char * str, int x, char * sep) { + char buf[SLICE_SIZE] = {0}; + if (sep == NULL) sprintf(buf, "%d", x); + else sprintf(buf, "%s%d", sep, x); + strcat(str, buf); +} + +static void +concat_char (char * str, char * x, char * sep) { + char buf[SLICE_SIZE] = {0}; + sprintf(buf, "%s%s", sep, x); + strcat(str, buf); +} + +/** + * Render a given semver as string + */ + +void +semver_render (semver_t *x, char *dest) { + concat_num(dest, x->major, NULL); + concat_num(dest, x->minor, DELIMITER); + concat_num(dest, x->patch, DELIMITER); + if (x->prerelease) concat_char(dest, x->prerelease, PR_DELIMITER); + if (x->metadata) concat_char(dest, x->metadata, MT_DELIMITER); +} + +/** + * Version bump helpers + */ + +void +semver_bump (semver_t *x) { + x->major++; +} + +void +semver_bump_minor (semver_t *x) { + x->minor++; +} + +void +semver_bump_patch (semver_t *x) { + x->patch++; +} + +/** + * Helpers + */ + +static int +has_valid_length (const char *s) { + return strlen(s) <= MAX_SIZE; +} + +/** + * Checks if a given semver string is valid + * + * Returns: + * + * `1` - Valid expression + * `0` - Invalid + */ + +int +semver_is_valid (const char *s) { + return has_valid_length(s) + && has_valid_chars(s, VALID_CHARS); +} + +/** + * Removes non-valid characters in the given string. + * + * Returns: + * + * `0` - Valid + * `-1` - Invalid input + */ + +int +semver_clean (char *s) { + size_t i, len, mlen; + int res; + if (has_valid_length(s) == 0) return -1; + + len = strlen(s); + mlen = strlen(VALID_CHARS); + + for (i = 0; i < len; i++) { + if (contains(s[i], VALID_CHARS, mlen) == 0) { + res = strcut(s, i, 1); + if(res == -1) return -1; + --len; --i; + } + } + + return 0; +} + +static int +char_to_int (const char * str) { + int buf; + size_t i,len, mlen; + buf = 0; + len = strlen(str); + mlen = strlen(VALID_CHARS); + + for (i = 0; i < len; i++) + if (contains(str[i], VALID_CHARS, mlen)) + buf += (int) str[i]; + + return buf; +} + +/** + * Render a given semver as numeric value. + * Useful for ordering and filtering. + */ + +int +semver_numeric (semver_t *x) { + int num; + char buf[SLICE_SIZE * 3]; + memset(&buf, 0, SLICE_SIZE * 3); + + if (x->major) concat_num(buf, x->major, NULL); + if (x->major || x->minor) concat_num(buf, x->minor, NULL); + if (x->major || x->minor || x->patch) concat_num(buf, x->patch, NULL); + + num = parse_int(buf); + if(num == -1) return -1; + + if (x->prerelease) num += char_to_int(x->prerelease); + if (x->metadata) num += char_to_int(x->metadata); + + return num; +} diff --git a/include/semver.h b/include/semver.h new file mode 100644 index 0000000..1b48670 --- /dev/null +++ b/include/semver.h @@ -0,0 +1,105 @@ +/* + * semver.h + * + * Copyright (c) 2015-2017 Tomas Aparicio + * MIT licensed + */ + +#ifndef __SEMVER_H +#define __SEMVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef SEMVER_VERSION +#define SEMVER_VERSION "0.2.0" +#endif + +/** + * semver_t struct + */ + +typedef struct semver_version_s { + int major; + int minor; + int patch; + char * metadata; + char * prerelease; +} semver_t; + +/** + * Set prototypes + */ + +int +semver_satisfies (semver_t x, semver_t y, const char *op); + +int +semver_satisfies_caret (semver_t x, semver_t y); + +int +semver_satisfies_patch (semver_t x, semver_t y); + +int +semver_compare (semver_t x, semver_t y); + +int +semver_compare_version (semver_t x, semver_t y); + +int +semver_compare_prerelease (semver_t x, semver_t y); + +int +semver_gt (semver_t x, semver_t y); + +int +semver_gte (semver_t x, semver_t y); + +int +semver_lt (semver_t x, semver_t y); + +int +semver_lte (semver_t x, semver_t y); + +int +semver_eq (semver_t x, semver_t y); + +int +semver_neq (semver_t x, semver_t y); + +int +semver_parse (const char *str, semver_t *ver); + +int +semver_parse_version (const char *str, semver_t *ver); + +void +semver_render (semver_t *x, char *dest); + +int +semver_numeric (semver_t *x); + +void +semver_bump (semver_t *x); + +void +semver_bump_minor (semver_t *x); + +void +semver_bump_patch (semver_t *x); + +void +semver_free (semver_t *x); + +int +semver_is_valid (const char *s); + +int +semver_clean (char *s); + +#ifdef __cplusplus +} +#endif + +#endif
Originally from: https://github.com/h2non/semver.c.git Original commit: bd1db234a68f305ed10268bd023df1ad672061d7 License: MIT Signed-off-by: Stijn Devriendt <sde@unmatched.eu> --- core/Makefile | 3 +- core/semver.c | 638 +++++++++++++++++++++++++++++++++++++++++++++++ include/semver.h | 105 ++++++++ 3 files changed, 745 insertions(+), 1 deletion(-) create mode 100644 core/semver.c create mode 100644 include/semver.h