From patchwork Mon Jun 15 08:26:56 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stijn Devriendt X-Patchwork-Id: 1309321 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=googlegroups.com (client-ip=2a00:1450:4864:20::238; helo=mail-lj1-x238.google.com; envelope-from=swupdate+bncbclnzfwotyirbxhbtt3qkgqeuaxlwaq@googlegroups.com; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=googlegroups.com header.i=@googlegroups.com header.a=rsa-sha256 header.s=20161025 header.b=NSQH5Tcg; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20161025 header.b=VwfAyD1F; dkim-atps=neutral Received: from mail-lj1-x238.google.com (mail-lj1-x238.google.com [IPv6:2a00:1450:4864:20::238]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 49lkw14bWzz9sSg for ; Mon, 15 Jun 2020 18:27:12 +1000 (AEST) Received: by mail-lj1-x238.google.com with SMTP id u10sf1366095ljk.3 for ; Mon, 15 Jun 2020 01:27:12 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1592209628; cv=pass; d=google.com; s=arc-20160816; b=VBkzkM/MK2cM8z+yXucZp6uD1R0PfwXaXAKFkhmYIxOUXYdM/mTvIcdCNoz9vm0WjS j6V2ITf5lLjID+v683aalJfLbLEB7Nrpm4j45NIsxcg414elwSr/mE2hON8J1T2Bnd/5 JCqKq2Dm/I28ZuNBKgroXS5zLFSIcyfx+Br/iccxMOIt4QFSTaKYKU6iJqwX2MkdBIoU p7znAR7dOL0RJ5Yn5zLaQS558uDs9ZMROnZutN8nuyrXDaE8AI6YGheCmUXkGnE6CSdC Yy4rpOruk/bOfOUrokJvL+E0QCqfrOXn6CpOtQ+qwZlP8Vi843aZhMYYp0JwBqYqTwAe 4luw== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:references:in-reply-to:message-id :date:subject:cc:to:from:mime-version:sender:dkim-signature :dkim-signature; bh=F1CfENYuSP18Nx+2JguHjaZWXyxr34ispjJN0cLDtvs=; b=R9ehvBUXbcO5/H0eFpYtzPEG+RsKTIxXT0q4/kpuzYBMblcmaQUDsFrs+yzH9fsr7L vWYRwEDE3fgEMBVgXG/Vqzud+kWaeWwmlQkbCW1lWy6l9oPCYOC2TJKW89coIAktT3qM DUuMM4fNP1fav+/UqHVlWCzaALe2UjIBBu8YeTpJpNyJcr/2IR+fQy8AHvPUeprgF/Qv QGkjT2xO7yAvICU6v3sQceeKF1KqosrSUAmk9xC9/7C2lzNGvxFYyRWQXdyiNbB1cQ2e js6JOLLpQvnIENL+QCjB7UsdwV/1def8c2ovUpB8S9A2C5W3IT4oFPH+zvWcR+jl5H5P TwMA== ARC-Authentication-Results: i=2; gmr-mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=l162PuiG; spf=pass (google.com: domain of highguy@gmail.com designates 2a00:1450:4864:20::543 as permitted sender) smtp.mailfrom=highguy@gmail.com; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20161025; h=sender:mime-version:from:to:cc:subject:date:message-id:in-reply-to :references:x-original-sender:x-original-authentication-results :precedence:mailing-list:list-id:list-post:list-help:list-archive :list-subscribe:list-unsubscribe; bh=F1CfENYuSP18Nx+2JguHjaZWXyxr34ispjJN0cLDtvs=; b=NSQH5Tcgf7xlKHynXmQacueZLWjYQhzmMDTqtDD05zS7rF+MCADHop0OPcVnpDUqYS sQ2BniF0bWADESSJVJRepH5/v5k8Q+YeEHjKUvD99bi9RwY+TtrSb55quyMJTrDCR68b epnICP7SJgkdHlITVDp+NixWs02MJZGensTN78Rkht7Em2c3SO5wrOmn4lh3Cc++FSni Ougr5lNE5h0JbMFzfRoGRU9BqI9JCGbcnnlthBixS9iVTYEPpXAegemC9sXmHgMGO0nY xBrt3Yv3OB1u6XLsM38klNzYpRLhORv01kOGEDmZmnDOFQo8brCT9MUuJ/jYfh8GADhV SoGg== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:from:to:cc:subject:date:message-id:in-reply-to :references:x-original-sender:x-original-authentication-results :precedence:mailing-list:list-id:list-post:list-help:list-archive :list-subscribe:list-unsubscribe; bh=F1CfENYuSP18Nx+2JguHjaZWXyxr34ispjJN0cLDtvs=; b=VwfAyD1Fnvm894ASB8Z3zhKjobLcY/DArCzn8EF+HJC1eklQquiQsj+PkRLaRTvaQT uI4KTz6J2QFsP1qSbnZ7Te2O9W8pJtQbXw+OmSjOhukk9UIgZ8bl0AH918SVQBxiWa49 G6eDibxOb6m9nbaGY9i12bOHgd29XXpiKbqB80GCwmKzJRQq9Dm5bPaOL5MFOCGJjzFa Pxb/CwUgAblHEIRazWq5UL1L+WNJTZl2v0heGNhC++hV8w5G/NQPqIMX/P4SwdGIB6dj A6SnMcpoEf0aA91cRP281Zwm7SkZKPCq2Z+w7jr3b8Bxc/0JlbOySshpC5mGagVW6/ra jBYA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=sender:x-gm-message-state:mime-version:from:to:cc:subject:date :message-id:in-reply-to:references:x-original-sender :x-original-authentication-results:precedence:mailing-list:list-id :x-spam-checked-in-group:list-post:list-help:list-archive :list-subscribe:list-unsubscribe; bh=F1CfENYuSP18Nx+2JguHjaZWXyxr34ispjJN0cLDtvs=; b=LOz2tV6n/ep8Y4faUDICgZTXVpJDcCnLVgfsgBNLfI9Xjbkhr69OluSxBXkQynneGT UVN0b/86iyVegXvj2rTsUJtmHIqRlWw5EtCVRAcxFZHOSFgGRfZ7QvrM7Z4ksc7w43Ta rrjigXbGPjjBii7zc4TVfjm4fYM13wtR1/mfxigm2fjKoMqvy85TaPm96+qc0lwpjipO 5+DuDg9iRkOt5rz10s3tKkpNZw90LoGocTgQrZhDxoNYleBAZphGAYmxulSJrzqyZ0ya a/EcXIYfqumCFsQWyLxpC/7U5VlTS20RCh41h673ZI5okJvh4nk0eGozxFzOfW36M40b HJyw== Sender: swupdate@googlegroups.com X-Gm-Message-State: AOAM533Et5lh1rZojYpEFeq0+aggBdeRr9SyYO91m+mAFUvWc2nBUpBs yWLl41xp6gAXinptYPFcIMc= X-Google-Smtp-Source: ABdhPJz+2K3n4/xy8GOMWhQFkjihVi11kq8FD8t6U0mTbJ3MgT8Mkc2okN2aXOoWV4/T53JgoxW8xw== X-Received: by 2002:a2e:810d:: with SMTP id d13mr11847469ljg.174.1592209628732; Mon, 15 Jun 2020 01:27:08 -0700 (PDT) MIME-Version: 1.0 X-BeenThere: swupdate@googlegroups.com Received: by 2002:a2e:8843:: with SMTP id z3ls2422772ljj.11.gmail; Mon, 15 Jun 2020 01:27:07 -0700 (PDT) X-Received: by 2002:a2e:7311:: with SMTP id o17mr12216748ljc.35.1592209627870; Mon, 15 Jun 2020 01:27:07 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1592209627; cv=none; d=google.com; s=arc-20160816; b=l1Qthc+/zFufyGg9gyaimJK3wDWJqB/uo34yktPid+G7Y7s82DPF9hG08W3/9SpQKu ng0lGbCjmEXlmdOYHtw48omxJe5Z3sWoj0cGSp9a8zv9C2D/Msp8ZU7/AluXZFmUOnmk cY8ME3FL1jXSDkIDqtUdxBaE3HTOtznnERaNsbhotO00Xsp4mEWNNnnKMQqIKP8zzx4s rL/hh/N3KkFzLSxFq48WgXxaghl6H7A/hzGV2D/EaMw49qsOXTy7+ox4UjbLl/zPbhhT UHPATP281HeYam1+zSAqELjCzV9HaDsDX+FF8Qpp5WQ8+5vMCgHwIgzv3000rhzhKX0a WC0w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=kNEGeoIivLOih1IS9PiqfmezEzGOJu5rPWwSBFD1+7k=; b=b7goZ9xwtBKv6NBGiQ/OkX0NjcqY2xVIm+ErBJotGaGXp+cTBnn0kozj64rv/bwFQa d3Yz3F8psjuPsqxHevfNHLaZTjcPwj4P1YLQTe5K4CvBxn61eWopqv5vehYHd9P8wSGs ubq2KIMFDR0ANphUunrGlafsbcukl/0HHoqT6vocAC9VH9b4O6zdJchQXJnnSX6hl9sy ZoMR894LFKMATDPlEVyE4k06V/9LorVJA+KrKVWuPJsUOHP9CSb/+HbyemoepK/EOGB0 S1I/k/CeoTGmmHrmP+9t6A6vNuxToh8xJYCUWDtwXY4FXGvvAohiGAGDbDk4P7ikYYFO W+rg== ARC-Authentication-Results: i=1; gmr-mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=l162PuiG; spf=pass (google.com: domain of highguy@gmail.com designates 2a00:1450:4864:20::543 as permitted sender) smtp.mailfrom=highguy@gmail.com; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from mail-ed1-x543.google.com (mail-ed1-x543.google.com. [2a00:1450:4864:20::543]) by gmr-mx.google.com with ESMTPS id z3si117430lfe.5.2020.06.15.01.27.07 for (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Mon, 15 Jun 2020 01:27:07 -0700 (PDT) Received-SPF: pass (google.com: domain of highguy@gmail.com designates 2a00:1450:4864:20::543 as permitted sender) client-ip=2a00:1450:4864:20::543; Received: by mail-ed1-x543.google.com with SMTP id x93so10837708ede.9 for ; Mon, 15 Jun 2020 01:27:07 -0700 (PDT) X-Received: by 2002:a05:6402:1d96:: with SMTP id dk22mr23419929edb.258.1592209627196; Mon, 15 Jun 2020 01:27:07 -0700 (PDT) Received: from sde1.telecom-it.be (ptr-dv4l9auv4w1m1i5kotr.18120a2.ip6.access.telenet.be. [2a02:1811:ce13:ba00:717d:f0a2:e207:f7af]) by smtp.googlemail.com with ESMTPSA id fi13sm8656314ejb.34.2020.06.15.01.27.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 15 Jun 2020 01:27:06 -0700 (PDT) From: Stijn Devriendt To: HIGHGuY@gmail.com, sbabic@denx.de Cc: tomas@aparicio.me, swupdate@googlegroups.com, Stijn Devriendt Subject: [swupdate] [PATCH v3 1/7] Add semver library Date: Mon, 15 Jun 2020 10:26:56 +0200 Message-Id: <20200615082702.32693-2-sde@unmatched.eu> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200612100354.3591-1-sde@unmatched.eu> References: <20200612100354.3591-1-sde@unmatched.eu> X-Original-Sender: HIGHGuY@gmail.com X-Original-Authentication-Results: gmr-mx.google.com; dkim=pass header.i=@gmail.com header.s=20161025 header.b=l162PuiG; spf=pass (google.com: domain of highguy@gmail.com designates 2a00:1450:4864:20::543 as permitted sender) smtp.mailfrom=highguy@gmail.com; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Precedence: list Mailing-list: list swupdate@googlegroups.com; contact swupdate+owners@googlegroups.com List-ID: X-Spam-Checked-In-Group: swupdate@googlegroups.com X-Google-Group-Id: 605343134186 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , Originally from: https://github.com/h2non/semver.c.git Original commit: bd1db234a68f305ed10268bd023df1ad672061d7 License: MIT Signed-off-by: Stijn Devriendt --- 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 + */ + +#include +#include +#include +#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