From 8561af5a2551ec453c9888125de273f2cc2940c0 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 14 Aug 2018 14:09:32 +0200 Subject: Add support for parsing semantic and semantic-like versions --- libbutl/semantic-version.cxx | 150 ++++++++++++++++++++++++++++++++++ libbutl/semantic-version.ixx | 78 ++++++++++++++++++ libbutl/semantic-version.mxx | 187 +++++++++++++++++++++++++++++++++++++++++++ libbutl/standard-version.cxx | 13 ++- libbutl/standard-version.mxx | 19 +++++ 5 files changed, 443 insertions(+), 4 deletions(-) create mode 100644 libbutl/semantic-version.cxx create mode 100644 libbutl/semantic-version.ixx create mode 100644 libbutl/semantic-version.mxx (limited to 'libbutl') 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 +#endif + +#include + +#ifndef __cpp_lib_modules +#include +#include +#include +#include + +#include // strchr() +#include // strtoull() +#include // move() +#include // 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 version; + std::string failure_reason; + }; + + LIBBUTL_SYMEXPORT semantic_version_result + parse_semantic_version_impl (const std::string&, std::size_t, const char*); + + inline optional + parse_semantic_version (const std::string& s, bool ab) + { + return parse_semantic_version (s, ab ? "-+" : nullptr); + } + + inline optional + parse_semantic_version (const std::string& s, const char* bs) + { + return parse_semantic_version_impl (s, 0, bs).version; + } + + inline optional + parse_semantic_version (const std::string& s, std::size_t p, bool ab) + { + return parse_semantic_version (s, p, ab ? "-+" : nullptr); + } + + inline optional + 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 +#include // size_t +#include // uint*_t +#include // move() +#include +#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 +#endif + +#include + +// FreeBSD defines these macros in its . +// +#ifdef major +# undef major +#endif + +#ifdef minor +# undef minor +#endif + +LIBBUTL_MODEXPORT namespace butl +{ + // Semantic or semantic-like version. + // + // .[.][] + // + // 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 component is considered + // valid only if separated from 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 + parse_semantic_version (const std::string&, bool allow_build = true); + + optional + parse_semantic_version (const std::string&, const char* build_separators); + + optional + parse_semantic_version (const std::string&, std::size_t pos, bool = true); + + optional + 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 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 (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 // [+-]..- // 0[+] // + // 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: -- cgit v1.1