aboutsummaryrefslogtreecommitdiff
path: root/libbutl
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 /libbutl
parent7c665d965c0ebb259849d5032faa0854c6ae94f2 (diff)
Add support for parsing semantic and semantic-like versions
Diffstat (limited to 'libbutl')
-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
5 files changed, 443 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: