aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2018-08-14 14:09:32 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2018-08-14 14:09:32 +0200
commit8561af5a2551ec453c9888125de273f2cc2940c0 (patch)
tree4458e955d33f9aaf7e904c8de717803a33ecc81f
parent7c665d965c0ebb259849d5032faa0854c6ae94f2 (diff)
Add support for parsing semantic and semantic-like versions
-rw-r--r--libbutl/semantic-version.cxx150
-rw-r--r--libbutl/semantic-version.ixx78
-rw-r--r--libbutl/semantic-version.mxx187
-rw-r--r--libbutl/standard-version.cxx13
-rw-r--r--libbutl/standard-version.mxx19
-rw-r--r--tests/semantic-version/buildfile8
-rw-r--r--tests/semantic-version/driver.cxx84
7 files changed, 535 insertions, 4 deletions
diff --git a/libbutl/semantic-version.cxx b/libbutl/semantic-version.cxx
new file mode 100644
index 0000000..2427202
--- /dev/null
+++ b/libbutl/semantic-version.cxx
@@ -0,0 +1,150 @@
+// file : libbutl/semantic-version.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef __cpp_modules
+#include <libbutl/semantic-version.mxx>
+#endif
+
+#include <cassert>
+
+#ifndef __cpp_lib_modules
+#include <string>
+#include <cstddef>
+#include <cstdint>
+#include <ostream>
+
+#include <cstring> // strchr()
+#include <cstdlib> // strtoull()
+#include <utility> // move()
+#include <stdexcept> // invalid_argument
+#endif
+
+// Other includes.
+
+#ifdef __cpp_modules
+module butl.semantic_version;
+
+// Only imports additional to interface.
+#ifdef __clang__
+#ifdef __cpp_lib_modules
+import std.core;
+import std.io;
+#endif
+import butl.optional;
+#endif
+#else
+#endif
+
+using namespace std;
+
+namespace butl
+{
+ string semantic_version::
+ string (bool ib) const
+ {
+ std::string r;
+ r = to_string (major);
+ r += '.';
+ r += to_string (minor);
+ r += '.';
+ r += to_string (patch);
+ if (!ib) r += build;
+ return r;
+ }
+
+ uint64_t semantic_version::
+ numeric () const
+ {
+ if (const char* w = (major > 999 ? "major version greater than 999" :
+ minor > 999 ? "minor version greater than 999" :
+ patch > 999 ? "patch version greater than 999" :
+ nullptr))
+ throw invalid_argument (w);
+
+ // AAABBBCCC0000
+ // 10000000000
+ // 10000000
+ // 10000
+ //
+ return (major * 10000000000) + (minor * 10000000) + (patch * 10000);
+ }
+
+ semantic_version::
+ semantic_version (uint64_t n, std::string b)
+ : build (move (b))
+ {
+ // AAABBBCCC0000
+ if (n > 9999999990000ULL || (n % 1000) != 0)
+ throw invalid_argument ("invalid numeric representation");
+
+ // AAABBBCCC0000
+ major = n / 10000000000 % 1000;
+ minor = n / 10000000 % 1000;
+ patch = n / 10000 % 1000;
+ }
+
+ semantic_version::
+ semantic_version (const std::string& s, size_t p, const char* bs)
+ {
+ semantic_version_result r (parse_semantic_version_impl (s, p, bs));
+
+ if (r.version)
+ *this = move (*r.version);
+ else
+ throw invalid_argument (r.failure_reason);
+ }
+
+ // From standard-version.cxx
+ //
+ bool
+ parse_uint64 (const string& s, size_t& p,
+ uint64_t& r,
+ uint64_t min = 0, uint64_t max = uint64_t (~0));
+
+ semantic_version_result
+ parse_semantic_version_impl (const string& s, size_t p, const char* bs)
+ {
+ auto bail = [] (string m)
+ {
+ return semantic_version_result {nullopt, move (m)};
+ };
+
+ semantic_version r;
+
+ if (!parse_uint64 (s, p, r.major))
+ return bail ("invalid major version");
+
+ if (s[p] != '.')
+ return bail ("'.' expected after major version");
+
+ if (!parse_uint64 (s, ++p, r.minor))
+ return bail ("invalid minor version");
+
+ if (s[p] == '.')
+ {
+ // Treat it as build if failed to parse as patch (e.g., 1.2.alpha).
+ //
+ if (!parse_uint64 (s, ++p, r.patch))
+ {
+ //if (require_patch)
+ // return bail ("invalid patch version");
+
+ --p;
+ // Fall through.
+ }
+ }
+ //else if (require_patch)
+ // return bail ("'.' expected after minor version");
+
+ if (char c = s[p])
+ {
+ if (bs == nullptr || (*bs != '\0' && strchr (bs, c) == nullptr))
+ return bail ("junk after version");
+
+ r.build.assign (s, p, string::npos);
+ }
+
+ return semantic_version_result {move (r), string ()};
+ }
+}
diff --git a/libbutl/semantic-version.ixx b/libbutl/semantic-version.ixx
new file mode 100644
index 0000000..cd88663
--- /dev/null
+++ b/libbutl/semantic-version.ixx
@@ -0,0 +1,78 @@
+// file : libbutl/semantic-version.ixx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+namespace butl
+{
+ inline semantic_version::
+ semantic_version (std::uint64_t mj,
+ std::uint64_t mi,
+ std::uint64_t p,
+ std::string b)
+ : major (mj),
+ minor (mi),
+ patch (p),
+ build (std::move (b))
+ {
+ }
+
+ inline semantic_version::
+ semantic_version (const std::string& s, bool ab)
+ : semantic_version (s, ab ? "-+" : nullptr)
+ {
+ }
+
+ inline semantic_version::
+ semantic_version (const std::string& s, const char* bs)
+ : semantic_version (s, 0, bs)
+ {
+ }
+
+ inline semantic_version::
+ semantic_version (const std::string& s, std::size_t p, bool ab)
+ : semantic_version (s, p, ab ? "-+" : nullptr)
+ {
+ }
+
+ inline int semantic_version::
+ compare (const semantic_version& v, bool ib) const
+ {
+ return (major != v.major ? (major < v.major ? -1 : 1) :
+ minor != v.minor ? (minor < v.minor ? -1 : 1) :
+ patch != v.patch ? (patch < v.patch ? -1 : 1) :
+ ib ? 0 : build.compare (v.build));
+ }
+
+ struct semantic_version_result
+ {
+ optional<semantic_version> version;
+ std::string failure_reason;
+ };
+
+ LIBBUTL_SYMEXPORT semantic_version_result
+ parse_semantic_version_impl (const std::string&, std::size_t, const char*);
+
+ inline optional<semantic_version>
+ parse_semantic_version (const std::string& s, bool ab)
+ {
+ return parse_semantic_version (s, ab ? "-+" : nullptr);
+ }
+
+ inline optional<semantic_version>
+ parse_semantic_version (const std::string& s, const char* bs)
+ {
+ return parse_semantic_version_impl (s, 0, bs).version;
+ }
+
+ inline optional<semantic_version>
+ parse_semantic_version (const std::string& s, std::size_t p, bool ab)
+ {
+ return parse_semantic_version (s, p, ab ? "-+" : nullptr);
+ }
+
+ inline optional<semantic_version>
+ parse_semantic_version (const std::string& s, std::size_t p, const char* bs)
+ {
+ return parse_semantic_version_impl (s, p, bs).version;
+ }
+}
diff --git a/libbutl/semantic-version.mxx b/libbutl/semantic-version.mxx
new file mode 100644
index 0000000..d64eb82
--- /dev/null
+++ b/libbutl/semantic-version.mxx
@@ -0,0 +1,187 @@
+// file : libbutl/semantic-version.mxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef __cpp_modules
+#pragma once
+#endif
+
+// C includes.
+
+#ifndef __cpp_lib_modules
+#include <string>
+#include <cstddef> // size_t
+#include <cstdint> // uint*_t
+#include <utility> // move()
+#include <ostream>
+#endif
+
+// Other includes.
+
+#ifdef __cpp_modules
+export module butl.semantic_version;
+#ifdef __cpp_lib_modules
+import std.core;
+import std.io;
+#endif
+import butl.optional;
+#else
+#include <libbutl/optional.mxx>
+#endif
+
+#include <libbutl/export.hxx>
+
+// FreeBSD defines these macros in its <sys/types.h>.
+//
+#ifdef major
+# undef major
+#endif
+
+#ifdef minor
+# undef minor
+#endif
+
+LIBBUTL_MODEXPORT namespace butl
+{
+ // Semantic or semantic-like version.
+ //
+ // <major>.<minor>[.<patch>][<build>]
+ //
+ // If the patch component is absent, then it defaults to 0.
+ //
+ // @@ Currently there is no way to enforce the three-component version.
+ // Supporting this will require changing allow_build to a bit-wise
+ // flag. See parse_semantic_version_impl() for some sketched code.
+ // We may also want to pass these flags to string() to not print
+ // 0 patch.
+ //
+ // By default, a version containing the <build> component is considered
+ // valid only if separated from <patch> with '-' (semver pre-release) or '+'
+ // (semver build metadata). However, as discussed below, the list of valid
+ // separators can be customized to recognize other semver-like formats.
+ //
+ // Note also that the format of semver pre-release and build metadata are
+ // not validated.
+ //
+ struct LIBBUTL_SYMEXPORT semantic_version
+ {
+ std::uint64_t major = 0;
+ std::uint64_t minor = 0;
+ std::uint64_t patch = 0;
+ std::string build;
+
+ // Construct the semantic version from various representations. Throw
+ // std::invalid_argument if the format is not recognizable or components
+ // are invalid.
+ //
+ semantic_version () = default;
+
+ semantic_version (std::uint64_t major,
+ std::uint64_t minor,
+ std::uint64_t patch,
+ std::string build = "");
+
+ // The build_separators argument can be NULL (no build component allowed),
+ // empty (any build component allowed), or a string of characters to allow
+ // as separators. When allow_build is true build_separators defaults to
+ // "-+".
+ //
+ explicit
+ semantic_version (const std::string&, bool allow_build = true);
+
+ semantic_version (const std::string&, const char* build_separators);
+
+ // As above but parse from the specified position until the end of the
+ // string.
+ //
+ semantic_version (const std::string&, std::size_t pos, bool = true);
+
+ semantic_version (const std::string&, std::size_t pos, const char*);
+
+ std::string
+ string (bool ignore_build = false) const;
+
+ // Numeric representation in the AAABBBCCC0000 form, where:
+ //
+ // AAA - major version number
+ // BBB - minor version number
+ // CCC - patch version number
+ //
+ // See standard version for details.
+ //
+ explicit
+ semantic_version (std::uint64_t numeric, std::string build = "");
+
+ // If any of the major/minor/patch components is greater than 999, then
+ // throw std::invalid_argument. The build component is ignored.
+ //
+ std::uint64_t
+ numeric () const;
+
+ // Unless instructed to ignore, the build components are compared
+ // lexicographically.
+ //
+ int
+ compare (const semantic_version&, bool ignore_build = false) const;
+ };
+
+ // Try to parse a string as a semantic version returning nullopt if invalid.
+ //
+ optional<semantic_version>
+ parse_semantic_version (const std::string&, bool allow_build = true);
+
+ optional<semantic_version>
+ parse_semantic_version (const std::string&, const char* build_separators);
+
+ optional<semantic_version>
+ parse_semantic_version (const std::string&, std::size_t pos, bool = true);
+
+ optional<semantic_version>
+ parse_semantic_version (const std::string&, std::size_t pos, const char*);
+
+ // NOTE: comparison operators take the build component into account.
+ //
+ inline bool
+ operator< (const semantic_version& x, const semantic_version& y) noexcept
+ {
+ return x.compare (y) < 0;
+ }
+
+ inline bool
+ operator> (const semantic_version& x, const semantic_version& y) noexcept
+ {
+ return x.compare (y) > 0;
+ }
+
+ inline bool
+ operator== (const semantic_version& x, const semantic_version& y) noexcept
+ {
+ return x.compare (y) == 0;
+ }
+
+ inline bool
+ operator<= (const semantic_version& x, const semantic_version& y) noexcept
+ {
+ return x.compare (y) <= 0;
+ }
+
+ inline bool
+ operator>= (const semantic_version& x, const semantic_version& y) noexcept
+ {
+ return x.compare (y) >= 0;
+ }
+
+ inline bool
+ operator!= (const semantic_version& x, const semantic_version& y) noexcept
+ {
+ return !(x == y);
+ }
+
+ inline std::ostream&
+ operator<< (std::ostream& o, const semantic_version& x)
+ {
+ return o << x.string ();
+ }
+}
+
+#include <libbutl/semantic-version.ixx>
diff --git a/libbutl/standard-version.cxx b/libbutl/standard-version.cxx
index e4180d1..fc2d528 100644
--- a/libbutl/standard-version.cxx
+++ b/libbutl/standard-version.cxx
@@ -42,12 +42,17 @@ using namespace std;
namespace butl
{
- // Utility functions
+ // Parse uint64_t from the specified string starting at the specified
+ // position and check the min/max constraints. If successful, save the
+ // result, update the position to point to the next character, and return
+ // true. Otherwise return false (result and position are unchanged).
//
- static bool
+ // Note: also used for semantic_version parsing.
+ //
+ bool
parse_uint64 (const string& s, size_t& p,
uint64_t& r,
- uint64_t min, uint64_t max)
+ uint64_t min = 0, uint64_t max = uint64_t (~0))
{
if (s[p] == '-' || s[p] == '+') // strtoull() allows these.
return false;
@@ -59,8 +64,8 @@ namespace butl
if (errno == ERANGE || b == e || v < min || v > max)
return false;
+ r = v;
p = e - s.c_str ();
- r = static_cast<uint64_t> (v);
return true;
}
diff --git a/libbutl/standard-version.mxx b/libbutl/standard-version.mxx
index 5f019c4..7b1f232 100644
--- a/libbutl/standard-version.mxx
+++ b/libbutl/standard-version.mxx
@@ -48,6 +48,25 @@ LIBBUTL_MODEXPORT namespace butl
// [+<epoch>-]<maj>.<min>.<patch>-
// 0[+<revision>]
//
+ // The numeric version format is AAABBBCCCDDDE where:
+ //
+ // AAA - major version number
+ // BBB - minor version number
+ // CCC - patch version number
+ // DDD - alpha / beta (DDD + 500) version number
+ // E - final (0) / snapshot (1)
+ //
+ // When DDDE is not 0, 1 is subtracted from AAABBBCCC. For example:
+ //
+ // Version AAABBBCCCDDDE
+ //
+ // 0.1.0 0000010000000
+ // 0.1.2 0000010010000
+ // 1.2.3 0010020030000
+ // 2.2.0-a.1 0020019990010
+ // 3.0.0-b.2 0029999995020
+ // 2.2.0-a.1.z 0020019990011
+ //
struct LIBBUTL_SYMEXPORT standard_version
{
// Invariants:
diff --git a/tests/semantic-version/buildfile b/tests/semantic-version/buildfile
new file mode 100644
index 0000000..1055228
--- /dev/null
+++ b/tests/semantic-version/buildfile
@@ -0,0 +1,8 @@
+# file : tests/semantic-version/buildfile
+# copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+import libs = libbutl%lib{butl}
+libs += $stdmod_lib
+
+exe{driver}: {hxx cxx}{*} $libs
diff --git a/tests/semantic-version/driver.cxx b/tests/semantic-version/driver.cxx
new file mode 100644
index 0000000..024e7a0
--- /dev/null
+++ b/tests/semantic-version/driver.cxx
@@ -0,0 +1,84 @@
+// file : tests/semantic-version/driver.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2018 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <cassert>
+
+#ifndef __cpp_lib_modules
+#include <iostream>
+#endif
+
+// Other includes.
+
+#ifdef __cpp_modules
+#ifdef __cpp_lib_modules
+import std.core;
+import std.io;
+#endif
+import butl.semantic_version;
+#else
+#include <libbutl/semantic-version.mxx>
+#endif
+
+using namespace std;
+using namespace butl;
+
+int
+main ()
+{
+ using semver = semantic_version;
+ using failed = invalid_argument;
+
+ // Construction.
+ //
+ {
+ semver v;
+ assert (v.major == 0 && v.minor == 0 && v.patch == 0 && v.build.empty ());
+ }
+
+ {
+ semver v (1, 2, 3);
+ assert (v.major == 1 && v.minor == 2 && v.patch == 3 && v.build.empty ());
+ assert (v.string () == "1.2.3");
+ }
+ {
+ semver v (1, 2, 3, ".4");
+ assert (v.major == 1 && v.minor == 2 && v.patch == 3 && v.build == ".4");
+ assert (v.string () == "1.2.3.4");
+ assert (v.string (true) == "1.2.3");
+ }
+
+ // Comparison.
+ //
+ assert (semver (2, 0 ,0) > semver (1, 2, 3));
+ assert (semver (1, 2, 0) > semver (1, 1, 2));
+ assert (semver (1, 1, 2) > semver (1, 1, 1, ".2"));
+ assert (semver (1, 1, 1, ".2") > semver (1, 1, 1, ".1"));
+ assert (semver (1, 1, 1, ".1").compare (semver (1, 1, 1, ".2"), true) == 0);
+
+ // String representation.
+ //
+ assert (semver ("1.2") == semver (1, 2, 0));
+ assert (semver ("1.2-3") == semver (1, 2, 0, "-3"));
+ assert (semver ("1.2.a1", "+-.") == semver (1, 2, 0, ".a1"));
+ assert (semver ("1.2.3") == semver (1, 2, 3));
+ assert (semver ("1.2.3-4") == semver (1, 2, 3, "-4"));
+ assert (semver ("1.2.3+4") == semver (1, 2, 3, "+4"));
+ assert (semver ("1.2.3.4", "+-.") == semver (1, 2, 3, ".4"));
+ assert (semver ("1.2.3a", "") == semver (1, 2, 3, "a"));
+ try {semver v ("1.2.3-4", false); assert (false);} catch (failed) {}
+ try {semver v ("1.2.3.4"); assert (false);} catch (failed) {}
+ try {semver v ("1.2.3a"); assert (false);} catch (failed) {}
+ assert (!parse_semantic_version ("1.2.3.4"));
+
+ // Numeric representation.
+ //
+ // AAABBBCCC0000
+ assert (semver ( 10020030000ULL) == semver (1, 2, 3));
+ assert (semver ( 9999999990000ULL, ".4") == semver (999, 999, 999, ".4"));
+ try { semver v ( 10020030001ULL); assert (false);} catch (failed) {}
+ try { semver v (10000020030000ULL); assert (false);} catch (failed) {}
+ assert (semver (1, 2, 3).numeric () == 10020030000ULL);
+ assert (semver (999, 999, 999, ".4").numeric () == 9999999990000ULL);
+ try { semver (9999, 0, 0).numeric (); assert (false);} catch (failed) {}
+}