summaryrefslogtreecommitdiffstats
path: root/src/lib/version_compare.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/version_compare.c')
-rw-r--r--src/lib/version_compare.c276
1 files changed, 276 insertions, 0 deletions
diff --git a/src/lib/version_compare.c b/src/lib/version_compare.c
new file mode 100644
index 0000000..ee265ff
--- /dev/null
+++ b/src/lib/version_compare.c
@@ -0,0 +1,276 @@
+/* $Id: version_compare.c 3551 2022-01-29 02:57:33Z bird $ */
+/** @file
+ * version_compare - version compare.
+ */
+
+/*
+ * Copyright (c) 2020 knut st. osmundsen <bird-kBuild-spamxx@anduin.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Alternatively, the content of this file may be used under the terms of the
+ * GPL version 2 or later, or LGPL version 2.1 or later.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "version_compare.h"
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+
+/**
+ * Simple quantification of the pre-release designations (alpha2, beta1, rc1).
+ *
+ * @returns ~0U if not a pre-release designation, lesser values if it is.
+ * @note Case is ignored.
+ */
+static const char *check_release_type(char ch, const char *psz, unsigned *puValue)
+{
+ const char * const pszStart = psz;
+ *puValue = ~0U;
+ switch (ch)
+ {
+ default:
+ return psz;
+
+ case 'r':
+ case 'R':
+ ch = *psz++;
+ if (ch != 'c' && ch != 'C')
+ return pszStart;
+ *puValue = ~0U/4 * 2;
+ break;
+ case 'b':
+ case 'B':
+ ch = *psz++;
+ if (ch != 'e' && ch != 'E')
+ return pszStart;
+ ch = *psz++;
+ if (ch != 't' && ch != 'T')
+ return pszStart;
+ ch = *psz++;
+ if (ch != 'a' && ch != 'A')
+ return pszStart;
+ *puValue = ~0U/4;
+ break;
+ case 'a':
+ case 'A':
+ ch = *psz++;
+ if (ch != 'l' && ch != 'L')
+ return pszStart;
+ ch = *psz++;
+ if (ch != 'p' && ch != 'P')
+ return pszStart;
+ ch = *psz++;
+ if (ch != 'h' && ch != 'H')
+ return pszStart;
+ ch = *psz++;
+ if (ch != 'a' && ch != 'A')
+ return pszStart;
+ *puValue = 0;
+ break;
+ }
+
+ /* The next must be an non-alpha character, if a digit we add it to the value. */
+ ch = *psz;
+ if (isdigit(ch))
+ {
+ long int lSub = strtol(psz, (char **)&psz, 10);
+ if (lSub >= ~0U / 4)
+ lSub = ~0U / 4 - 1;
+ *puValue += (unsigned)lSub;
+ }
+ else if (isalpha(ch))
+ return pszStart;
+ return psz;
+}
+
+
+/**
+ * Deals with returns, mainly from the string compare part.
+ */
+static int compare_failed(char ch1, char ch2)
+{
+ if (ch1 == '~')
+ return -1;
+ if (ch2 == '~')
+ return 1;
+ if (ch1 == '\0' || ch1 == '/') /* treat '/' similar to '\0' to deal with the v14.2/ vs v14.2.11.9/ case. */
+ return -1;
+ if (ch2 == '\0' || ch2 == '/')
+ return 1;
+ if (isdigit(ch1))
+ return -1;
+ if (isdigit(ch2))
+ return 1;
+ if (isalpha(ch1))
+ return isalpha(ch2) ? (int)ch1 - (int)ch2 : -1;
+ if (isalpha(ch2))
+ return 1;
+ return (int)ch1 - (int)ch2;
+}
+
+
+int version_compare(const char *psz1, const char *psz2)
+{
+ for (;;)
+ {
+ int diff;
+
+ /* Work non-digits: */
+ char ch1 = *psz1++;
+ char ch2 = *psz2++;
+ for (;;)
+ {
+ if (ch1 == ch2)
+ {
+ if (ch1 != '\0')
+ {
+ if (isdigit(ch1))
+ break;
+ ch1 = *psz1++;
+ ch2 = *psz2++;
+ }
+ else
+ return 0;
+ }
+ else if (isdigit(ch1) && isdigit(ch2))
+ break;
+ else
+ return compare_failed(ch1, ch2);
+ }
+
+ /* Skip leading zeros */
+ while (ch1 == '0')
+ ch1 = *psz1++;
+
+ while (ch2 == '0')
+ ch2 = *psz2++;
+
+ /* Compare digits. */
+ for (diff = 0;;)
+ {
+ if (isdigit(ch1))
+ {
+ if (isdigit(ch2))
+ {
+ if (diff == 0)
+ diff = (int)ch1 - (int)ch2;
+ ch1 = *psz1++;
+ ch2 = *psz2++;
+ }
+ else
+ return 1; /* The number in psz1 is longer and therefore larger. */
+ }
+ else if (isdigit(ch2))
+ return -1; /* The number in psz1 is shorter and therefore smaller. */
+ else if (diff != 0)
+ return diff;
+ else
+ break;
+ }
+
+ /* Neither ch1 nor ch2 is a digit at this point, but complete the
+ comparisons of the two before looping. We check for alpha, beta, rc
+ suffixes here (mainly to correctly order 1.2.3r4567890 after 1.2.3rc1) */
+ {
+ unsigned uType1 = ~0;
+ unsigned uType2 = ~0;
+ psz1 = check_release_type(ch1, psz1, &uType1);
+ psz2 = check_release_type(ch2, psz2, &uType2);
+ if (uType1 != uType2)
+ return uType1 < uType2 ? -1 : 1;
+ if (ch1 != ch2 && uType1 == ~0U)
+ return compare_failed(ch1, ch2);
+ if (ch1 == '\0')
+ return 0;
+ }
+ }
+}
+
+
+#ifdef TEST
+# include <stdio.h>
+
+int main()
+{
+ static const struct
+ {
+ int rcExpect;
+ const char *psz1, *psz2;
+ } s_aTests[] =
+ {
+ { 0, "", "" },
+ { 0, "a", "a" },
+ { 0, "ab", "ab" },
+ { 0, "abc", "abc" },
+ { 0, "001", "1" },
+ { 0, "000", "0" },
+ { -1, "0", "a" },
+ { -1, "0", "1" },
+ { -1, "0", "9" },
+ { -1, "0", "99" },
+ { -1, "9", "99" },
+ { -1, "98", "99" },
+ { 0, "asdfasdf", "asdfasdf" },
+ { -1, "asdfasdf", "asdfasdfz" },
+ { +1, "asdfasdfz", "asdfasdf" },
+ { 0, "a1s2d3f4", "a1s2d3f4" },
+ { 0, "a01s002d003f004", "a1s2d3f4" },
+ { 0, "a1s2d3f4", "a01s002d003f004" },
+ { 0, "kBuild-0.099.7", "kBuild-0.99.00007" },
+ { +1, "kBuild-0.099.7", "kBuild-0.99.00007rc1" },
+ { +1, "kBuild-0.099.7rc2", "kBuild-0.99.7beta3" },
+ { -1, "kBuild-0.099.7alpha", "kBuild-0.99.7beta3" },
+ { -1, "kBuild-0.099.7alpha", "kBuild-0.99.7beta3" },
+ { -1, "kBuild-0.099.7alpha", "kBuild-0.99.7alpha1" },
+ { 0, "kBuild-0.099.7ALPHA1", "kBuild-0.99.7alpha1" },
+ { -1, "kBuild-0.099.7BETA1", "kBuild-0.99.7rC1" },
+ { -1, "kBuild-0.099", "kBuild-0.99.0" },
+ { +1, "kBuild-0.099", "kBuild-0.99~" },
+ { +1, "1.2.3r4567890", "1.2.3rc1" },
+ { +1, "1.2.3r4567890", "1.2.3RC1" },
+ { -1, "/tools/win.amd64/vcc/v14.2/Tools", "/tools/win.amd64/vcc/v14.2.11.9/Tools" },
+ { -1, "/tools/win.amd64/vcc/v14.2/Tools", "/tools/win.amd64/vcc/v14.211.9/Tools" },
+ { -1, "/tools/win.amd64/vcc/v14.2/Tools", "/tools/win.amd64/vcc/v14.2r2/Tools" },
+ { -1, "/tools/win.amd64/vcc/v14.2/Tools", "/tools/win.amd64/vcc/v14.2-r2/Tools" },
+ };
+ unsigned cErrors = 0;
+ unsigned i;
+
+ for (i = 0; i < sizeof(s_aTests) / sizeof(s_aTests[0]); i++)
+ {
+ int rc = version_compare(s_aTests[i].psz1, s_aTests[i].psz2);
+ int rcExpect = s_aTests[i].rcExpect;
+ if (rc != (rcExpect < 0 ? -1 : rcExpect > 0 ? 1 : 0))
+ {
+ fprintf(stderr, "error: Test #%u: %d, expected %d: '%s' vs '%s'\n",
+ i, rc, rcExpect, s_aTests[i].psz1, s_aTests[i].psz2);
+ cErrors++;
+ }
+ }
+
+ return cErrors == 0 ? 0 : 1;
+}
+#endif
+