diff mbox series

[1/4] Add semver library

Message ID 20200609162639.14788-1-sde@unmatched.eu
State Changes Requested
Headers show
Series [1/4] Add semver library | expand

Commit Message

Stijn Devriendt June 9, 2020, 4:26 p.m. UTC
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

Comments

Stefano Babic June 10, 2020, 10:41 a.m. UTC | #1
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
Stijn Devriendt June 10, 2020, 1 p.m. UTC | #2
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 mbox series

Patch

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