Giter Site home page Giter Site logo

semver.c's Introduction

semver.c Build Status GitHub release

Semantic version v2.0 parser and render written in ANSI C with zero dependencies.

Features

  • Standard compliant (otherwise, open an issue)
  • Version metadata parsing
  • Version prerelease parsing
  • Version comparison helpers
  • Supports comparison operators
  • Version render
  • Version bump
  • Version sanitizer
  • 100% test coverage
  • No regexp (ANSI C doesn't support it)
  • Numeric conversion for sorting/filtering

Versions

  • v0 - Legacy version. Beta. Not maintained anymore.
  • v1 - Current stable version.

Usage

Basic comparison:

#include <stdio.h>
#include <semver.h>

char current[] = "1.5.10";
char compare[] = "2.3.0";

int
main(int argc, char *argv[]) {
    semver_t current_version = {};
    semver_t compare_version = {};

    if (semver_parse(current, &current_version)
      || semver_parse(compare, &compare_version)) {
      fprintf(stderr,"Invalid semver string\n");
      return -1;
    }

    int resolution = semver_compare(compare_version, current_version);

    if (resolution == 0) {
      printf("Versions %s is equal to: %s\n", compare, current);
    }
    else if (resolution == -1) {
      printf("Version %s is lower than: %s\n", compare, current);
    }
    else {
      printf("Version %s is higher than: %s\n", compare, current);
    }

    // Free allocated memory when we're done
    semver_free(&current_version);
    semver_free(&compare_version);
    return 0;
}

Satisfies version:

#include <stdio.h>
#include <semver.h>

semver_t current = {};
semver_t compare = {};

int
main(int argc, char *argv[]) {
    semver_parse("1.3.10", &current);
    semver_parse("1.5.2", &compare);

    // Use caret operator for the comparison
    char operator[] = "^";

    if (semver_satisfies(current, compare, operator)) {
      printf("Version %s can be satisfied by %s", "1.3.10", "1.5.2");
    }

    // Free allocated memory when we're done
    semver_free(&current);
    semver_free(&compare);
    return 0;
}

Installation

Clone this repository:

$ git clone https://github.com/h2non/semver.c

Or install with clib:

$ clib install h2non/semver.c

API

struct semver_t { int major, int minor, int patch, char * prerelease, char * metadata }

semver base struct.

semver_parse(const char *str, semver_t *ver) => int

Parses a string as semver expression.

Returns:

  • -1 - In case of invalid semver or parsing error.
  • 0 - All was fine!

semver_compare(semver_t a, semver_t b) => int

Compare versions a with b.

Returns:

  • -1 in case of lower version.
  • 0 in case of equal versions.
  • 1 in case of higher version.

semver_satisfies(semver_t a, semver_t b, char *operator) => int

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 operator comparison (more info)
  • ~ - Tilde operator comparison (more info)

Returns:

  • 1 - Can be satisfied
  • 0 - Cannot be satisfied

semver_satisfies_caret(semver_t a, semver_t b) => int

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

semver_satisfies_patch(semver_t a, semver_t b) => int

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

semver_eq(semver_t a, semver_t b) => int

Equality comparison.

semver_ne(semver_t a, semver_t b) => int

Non equal comparison.

semver_gt(semver_t a, semver_t b) => int

Greater than comparison.

semver_lt(semver_t a, semver_t b) => int

Lower than comparison.

semver_gte(semver_t a, semver_t b) => int

Greater than or equal comparison.

semver_lte(semver_t a, semver_t b) => int

Lower than or equal comparison.

semver_render(semver_t *v, char *dest) => void

Render as string.

semver_numeric(semver_t *v) => int

Render as numeric value. Useful for ordering and filtering.

semver_bump(semver_t *a) => void

Bump major version.

semver_bump_minor(semver_t *a) => void

Bump minor version.

semver_bump_patch(semver_t *a) => void

Bump patch version.

semver_free(semver_t *a) => void

Helper to free allocated memory from heap.

semver_is_valid(char *str) => int

Checks if the given string is a valid semver expression.

semver_clean(char *str) => int

Removes invalid semver characters in a given string.

License

MIT - Tomas Aparicio

semver.c's People

Contributors

gderecho avatar gordon avatar h2non avatar mbodmer avatar nickez avatar ojousima avatar robloach avatar stephenmathieson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

semver.c's Issues

Clean helper

Given a string, the function should remove all non-valid characters. This is the unique sanitizaton helper that makes sense to be natively provided by the library.

tests leaking memory

looks like there are a few memory leaks/invalid reads that should be addressed:

$ valgrind ./test --leak-check=full
[ ... ]
==5700== 
==5700== HEAP SUMMARY:
==5700==     in use at exit: 566 bytes in 55 blocks
==5700==   total heap usage: 116 allocs, 61 frees, 1,067 bytes allocated
==5700== 
[ ... ]
==5700== LEAK SUMMARY:
==5700==    definitely lost: 566 bytes in 55 blocks
==5700==    indirectly lost: 0 bytes in 0 blocks
==5700==      possibly lost: 0 bytes in 0 blocks
==5700==    still reachable: 0 bytes in 0 blocks
==5700==         suppressed: 0 bytes in 0 blocks
==5700== 
==5700== For counts of detected and suppressed errors, rerun with: -v
==5700== ERROR SUMMARY: 95 errors from 22 contexts (suppressed: 2 from 2)

here's the full valgrind report that shows what's happening in more detail.

Major/Minor/Patch should not be an int

Hi.

Major, minor and patch are “non-negative integers”. So no need to use a signed integer.
Also, int in C is at least 16-bits, so the maximum version that we can safely contain is 32'767. This approach falls short on projects using dates as version (e.g. https://github.com/janmojzis/tinyssh/releases). In fact, 20190101 does not fit in a signed int as major version.
One solution would be to use directly uint64 like here: https://docs.rs/crate/semver/0.9.0/source/src/version.rs. But guessing that this project may also be used in embedded devices. 64-bit is a little too much.
Patch should be at least 16-bits (semver/semver#516 (comment)).
How about uint32 for major, uint16 for minor and patch? This way, we can form a 64-bit long version number by concatenating uint32, uint16 and uint16. This also simplify your semver_numeric function[1].

[1]

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

uint64_t semver_numeric(void) {
	const uint32_t major = 20190101UL;
	const uint16_t minor = 42U, patch = 256U;
	return (uint64_t)major << 32U | (uint32_t)minor << 16U | patch;
}

int main(void) {
	printf("%" PRIu64 "\n", semver_numeric());
	return EXIT_SUCCESS;
}

Cheers,
Issam.

Accepts empty string as valid semver (i.e. treats major component as optional)

I don't think it's generally a problem to be loose with semver parsing, but I wonder if semver_parse("", ...) should fail instead of the current behavior of yielding 0.0.0?

A related side effect of this is "-1.2.3" is parsed as 0.0.0 with prerelease "1.2.3", which probably wasn't the intent of that string. In general, anything that forms a valid prerelease and/or metadata suffix is accepted as a valid semver in itself.

And other strangeness:

void testparse (const char *str) {

    semver_t v = {};

    printf("%-10s: ", str);
    if (semver_parse(str, &v))
        printf("error");
    else
        printf("major=%d  minor=%d  patch=%d  pre=%-8s  meta=%s", 
               v.major, v.minor, v.patch, v.prerelease, v.metadata);
    printf("\n");

    semver_free(&v);

}

int main () {

    testparse("");
    testparse("-1.2.3");

    testparse("-");
    testparse("+");
    testparse("-+");
    testparse("-3.14159");
    testparse("+10");

    testparse("fail");

}

Outputs:

          : major=0  minor=0  patch=0  pre=(null)    meta=(null)
-1.2.3    : major=0  minor=0  patch=0  pre=1.2.3     meta=(null)
-         : major=0  minor=0  patch=0  pre=          meta=(null)
+         : major=0  minor=0  patch=0  pre=(null)    meta=
-+        : major=0  minor=0  patch=0  pre=          meta=
-3.14159  : major=0  minor=0  patch=0  pre=3.14159   meta=(null)
+10       : major=0  minor=0  patch=0  pre=(null)    meta=10
fail      : error

All of those seem like they should, at least philosophically, result in parse failures. 🤷‍♂️

Having the parser verify that a major component is present would cover all those cases as well as the empty string case.

I mean, there's loose, and then there's loose... 😂

Incompatible type assignment

semver.c:116:8: error: assigning to 'char *' from incompatible type 'void *'
  part = calloc(plen + 1, sizeof(*part));
       ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
semver.c:146:7: error: assigning to 'char *' from incompatible type 'void *'
  buf = calloc(len + 1, sizeof(*buf));
      ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

May need to set the value, and then cast it afterwards.

Parse expressions

Built-in support to easily parse semver expressions, such as:

^ 1.2.3
~ 1.2.3
= 1.2.3
>= 1.2.3
1.2.3 > 2
1.2.3 ^ 2
1.2.3 ~ 2

API:

semver_expression("^1.2.3", "0.1.3")

source semver.c should include a version

I find it amusing that the source file does not include a version.
and the copyright date is not extended to reflect the latest version.

Could the comments be modified so the return codes do not indicate a char when in fact they return int

For example:

/**
 * 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) {

should be

/**
 * Compare two semantic versions (x, y).
 *
 * Returns:
 *   1 -  x is higher
 *   0 -  they are equal
 *  -1 -  y is higher
 */

int
semver_compare (semver_t x, semver_t y) {

semver_satisfies 'caret' corner cases

From https://docs.npmjs.com/misc/semver#caret-ranges-123-025-004

^1.2.3 := >=1.2.3 <2.0.0

Note >= 1.2.3, i.e., !(< 1.2.3). 1.2.3-alpha < 1.2.3 per semver, but semver_satisfies(1.2.3, 1.2.3-alpha, ^) == 1.

Note that < 2.0.0 and 2.0.0-alpha < 2.0.0, but semver_satisfies(1.2.3, 2.0.0-alpha, ^) == 0

#include <assert.h>
#include <stdio.h>
#include "semver.h"

int satisfies(const char *sv1, const char *sv2, const char *op) {
	semver_t v1, v2;
	int rc;

	rc = semver_parse(sv1, &v1);
	assert(!rc);
	rc = semver_parse(sv2, &v2);
	assert(!rc);
	rc = semver_satisfies(v1, v2, op);
	semver_free(&v1);
	semver_free(&v2);
	return rc;
}

int main(int argc, char *argv[]) {
	struct bundle {
		const char *sv1;
		const char *sv2;
	} cases [] = {
		{"1.2.3", "1.2.3"},
		{"1.2.3", "1.2.3-alpha"}, /* 1.2.3 > 1.2.3-alpha ! */
		{"1.2.3", "2.0.0"},
		{"1.2.3", "2.0.0-alpha"}, /* 2.0.0-alpha < 2.0.0 ! */
	};
	size_t num_cases = sizeof(cases) / sizeof(struct bundle), i;

	for (i=0; i < num_cases; ++i)
		printf("%s ^ %s = %d\n", cases[i].sv1, cases[i].sv2, 
			satisfies(cases[i].sv1, cases[i].sv2, "^"));
	return 0;
}

outputs

1.2.3 ^ 1.2.3 = 1
1.2.3 ^ 1.2.3-alpha = 1 # should be 0
1.2.3 ^ 2.0.0 = 0
1.2.3 ^ 2.0.0-alpha = 0 # should be 1

semver_satisfies 'tilde' corner cases

Per https://docs.npmjs.com/misc/semver#tilde-ranges-123-12-1
~1.2.3 := >=1.2.3 <1.(2+1).0 := >=1.2.3 <1.3.0

Note that 1.2.3-alpha < 1.2.3, i.e. "!(>= 1.2.3), but semver_satisfies(1.2.3, 1.2.3-alpha, ~) == 1
Note that 1.3.0-alpha < 1.3.0, but semver_satisfies(1.2.3, 1.3.0-alpha, ~) == 0.

#include <assert.h>
#include <stdio.h>
#include "semver.h"

int satisfies(const char *sv1, const char *sv2, const char *op) {
	semver_t v1, v2;
	int rc;

	rc = semver_parse(sv1, &v1);
	assert(!rc);
	rc = semver_parse(sv2, &v2);
	assert(!rc);
	rc = semver_satisfies(v1, v2, op);
	semver_free(&v1);
	semver_free(&v2);
	return rc;
}

int main(int argc, char *argv[]) {
	struct bundle {
		const char *sv1;
		const char *sv2;
	} cases [] = {
		{"1.2.3", "1.2.3"},
		{"1.2.3", "1.2.3-alpha"}, /* 1.2.3 > 1.2.3-alpha ! */
		{"1.2.3", "2.0.0"},
		{"1.2.3", "2.0.0-alpha"}, /* 2.0.0-alpha < 2.0.0 ! */
	};
	size_t num_cases = sizeof(cases) / sizeof(struct bundle), i;

	for (i=0; i < num_cases; ++i)
		printf("%s ^ %s = %d\n", cases[i].sv1, cases[i].sv2, 
			satisfies(cases[i].sv1, cases[i].sv2, "~"));
	return 0;
}

outputs

1.2.3 ^ 1.2.3 = 1
1.2.3 ^ 1.2.3-alpha = 1 # should be 0
1.2.3 ^ 2.0.0 = 0
1.2.3 ^ 2.0.0-alpha = 0 # should be 1

Is semver_compare correctly ordering prerelease

Hi Tomás thanks for your work on semver.c

Looking at http://semver.org/spec/v2.0.0.html#spec-item-11:
The suggested ordering when taking prereleases into account is
Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0

#include <stdio.h>
#include "semver.h"


int main(int argc, char *argv[]) {
  semver_t v1 = {},v2 = {},v3 = {},v4 = {},v5 = {},v6 = {},v7 = {},v8 = {};
  semver_parse("1.0.0-alpha", &v1);
  semver_parse("1.0.0-alpha.1", &v2);
  semver_parse("1.0.0-alpha.beta", &v3);
  semver_parse("1.0.0-beta", &v4);
  semver_parse("1.0.0-beta.2", &v5);
  semver_parse("1.0.0-beta.11", &v6);
  semver_parse("1.0.0-rc.1", &v7);
  semver_parse("1.0.0", &v8);
  int comp1 = semver_compare(v1, v2);
  int comp2 = semver_compare(v2, v3);
  int comp3 = semver_compare(v3, v4);
  int comp4 = semver_compare(v4, v5);
  int comp5 = semver_compare(v5, v6);
  int comp6 = semver_compare(v6, v7);
  int comp7 = semver_compare(v7, v8);
  printf("Comp1 = %d\n",comp);
  printf("Comp2 = %d\n",comp2);
  printf("Comp3 = %d\n",comp3);
  printf("Comp4 = %d\n",comp4);
  printf("Comp5 = %d\n",comp5);
  printf("Comp6 = %d\n",comp6);
  printf("Comp7 = %d\n",comp7);
  semver_free(&v1);
  semver_free(&v2);
  semver_free(&v3);
  semver_free(&v4);
  semver_free(&v5);
  semver_free(&v6);
  semver_free(&v7);
  semver_free(&v8);
  return 0;
}

john@ubuntu:~/semantictest$ ./semver 
Comp1 = 1
Comp2 = 1
Comp3 = -1
Comp4 = 1
Comp5 = 1
Comp6 = -1
Comp7 = -1

Expected:

john@ubuntu:~/semantictest$ ./semver 
Comp1 = -1
Comp2 = -1
Comp3 = -1
Comp4 = -1
Comp5 = -1
Comp6 = -1
Comp7 = -1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.