diff options
-rw-r--r-- | libbutl/git.cxx | 4 | ||||
-rw-r--r-- | libbutl/openssl.txx | 1 | ||||
-rw-r--r-- | libbutl/semantic-version.cxx | 70 | ||||
-rw-r--r-- | libbutl/semantic-version.hxx | 70 | ||||
-rw-r--r-- | libbutl/semantic-version.ixx | 64 | ||||
-rw-r--r-- | tests/semantic-version/driver.cxx | 33 |
6 files changed, 159 insertions, 83 deletions
diff --git a/libbutl/git.cxx b/libbutl/git.cxx index cc10c91..f37e16a 100644 --- a/libbutl/git.cxx +++ b/libbutl/git.cxx @@ -36,7 +36,9 @@ namespace butl // MinGit: git version 2.16.1.windows.1 // if (s.compare (0, 12, "git version ") == 0) - return parse_semantic_version (s, 12, "" /* build_separators */); + return parse_semantic_version (s, 12, + semantic_version::allow_build, + "" /* build_separators */); return nullopt; } diff --git a/libbutl/openssl.txx b/libbutl/openssl.txx index 01e854c..f55432d 100644 --- a/libbutl/openssl.txx +++ b/libbutl/openssl.txx @@ -105,6 +105,7 @@ namespace butl optional<semantic_version> ver ( parse_semantic_version (string (s, b, e != string::npos ? e - b : e), + semantic_version::allow_build, "" /* build_separators */)); if (!ver) diff --git a/libbutl/semantic-version.cxx b/libbutl/semantic-version.cxx index 3be382f..9e0a1ef 100644 --- a/libbutl/semantic-version.cxx +++ b/libbutl/semantic-version.cxx @@ -3,6 +3,7 @@ #include <libbutl/semantic-version.hxx> +#include <cassert> #include <cstring> // strchr() #include <utility> // move() #include <stdexcept> // invalid_argument @@ -52,9 +53,9 @@ namespace butl } semantic_version:: - semantic_version (const std::string& s, size_t p, const char* bs) + semantic_version (const std::string& s, size_t p, flags fs, const char* bs) { - semantic_version_result r (parse_semantic_version_impl (s, p, bs)); + semantic_version_result r (parse_semantic_version_impl (s, p, fs, bs)); if (r.version) *this = move (*r.version); @@ -70,8 +71,27 @@ namespace butl 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) + parse_semantic_version_impl (const string& s, size_t p, + semantic_version::flags fs, + const char* bs) { + bool allow_build ((fs & semantic_version::allow_build) != 0); + + // If build separators are specified, then the allow_build flag must be + // specified explicitly. + // + assert (bs == nullptr || allow_build); + + if (allow_build && bs == nullptr) + bs = "-+"; + + bool require_minor ((fs & semantic_version::allow_omit_minor) == 0); + + if (!require_minor) + fs |= semantic_version::allow_omit_patch; + + bool require_patch ((fs & semantic_version::allow_omit_patch) == 0); + auto bail = [] (string m) { return semantic_version_result {nullopt, move (m)}; @@ -82,31 +102,47 @@ namespace butl 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] == '.') + if (s[p] == '.') // Is there a minor version? { - // Treat it as build if failed to parse as patch (e.g., 1.2.alpha). + // Try to parse the minor version and treat it as build on failure + // (e.g., 1.alpha). // - if (!parse_uint64 (s, ++p, r.patch)) + if (parse_uint64 (s, ++p, r.minor)) { - //if (require_patch) - // return bail ("invalid patch version"); + if (s[p] == '.') // Is there a patch version? + { + // Try to parse the patch version and treat it as build on failure + // (e.g., 1.2.alpha). + // + if (parse_uint64 (s, ++p, r.patch)) + ; + else + { + if (require_patch) + return bail ("invalid patch version"); + + --p; + // Fall through. + } + } + else if (require_patch) + return bail ("'.' expected after minor version"); + } + else + { + if (require_minor) + return bail ("invalid minor version"); --p; // Fall through. } } - //else if (require_patch) - // return bail ("'.' expected after minor version"); + else if (require_minor) + return bail ("'.' expected after major version"); if (char c = s[p]) { - if (bs == nullptr || (*bs != '\0' && strchr (bs, c) == nullptr)) + if (!allow_build || (*bs != '\0' && strchr (bs, c) == nullptr)) return bail ("junk after version"); r.build.assign (s, p, string::npos); diff --git a/libbutl/semantic-version.hxx b/libbutl/semantic-version.hxx index 16f3d56..1dc7d1d 100644 --- a/libbutl/semantic-version.hxx +++ b/libbutl/semantic-version.hxx @@ -27,15 +27,9 @@ namespace butl { // Semantic or semantic-like version. // - // <major>.<minor>[.<patch>][<build>] + // <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. + // If the minor and patch components are absent, then they default to 0. // // By default, a version containing the <build> component is considered // valid only if separated from <patch> with '-' (semver pre-release) or '+' @@ -63,23 +57,36 @@ namespace butl 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 - // "-+". + // If the allow_build flag is specified, then build_separators argument + // can be a string of characters to allow as separators, empty (any build + // component allowed), or NULL (defaults to "-+"). // - explicit - semantic_version (const std::string&, bool allow_build = true); + // Note: allow_omit_minor implies allow_omit_patch. + // + enum flags + { + none = 0, // Exact <major>.<minor>.<patch> form. + allow_omit_minor = 0x01, // Allow <major> form. + allow_omit_patch = 0x02, // Allow <major>.<minor> form. + allow_build = 0x04, // Allow <major>.<minor>.<patch>-<build> form. + }; - semantic_version (const std::string&, const char* build_separators); + explicit + semantic_version (const std::string&, + flags = none, + const char* build_separators = nullptr); // 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*); + semantic_version (const std::string&, + std::size_t pos, + flags = none, + const char* = nullptr); + // @@ We may also want to pass allow_* flags not to print 0 minor/patch or + // maybe invent ignore_* flags. + // std::string string (bool ignore_build = false) const; @@ -116,16 +123,15 @@ namespace butl // 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); + parse_semantic_version (const std::string&, + semantic_version::flags = semantic_version::none, + const char* build_separators = nullptr); 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*); + parse_semantic_version (const std::string&, + std::size_t pos, + semantic_version::flags = semantic_version::none, + const char* = nullptr); // NOTE: comparison operators take the build component into account. // @@ -170,6 +176,18 @@ namespace butl { return o << x.string (); } + + inline semantic_version::flags + operator& (semantic_version::flags, semantic_version::flags); + + inline semantic_version::flags + operator| (semantic_version::flags, semantic_version::flags); + + inline semantic_version::flags + operator&= (semantic_version::flags&, semantic_version::flags); + + inline semantic_version::flags + operator|= (semantic_version::flags&, semantic_version::flags); } #include <libbutl/semantic-version.ixx> diff --git a/libbutl/semantic-version.ixx b/libbutl/semantic-version.ixx index 6bf7584..67cd8c0 100644 --- a/libbutl/semantic-version.ixx +++ b/libbutl/semantic-version.ixx @@ -15,23 +15,9 @@ namespace butl { } - // Note: the order is important to MinGW GCC (DLL linkage). - // inline semantic_version:: - semantic_version (const std::string& s, std::size_t p, bool ab) - : semantic_version (s, p, 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, bool ab) - : semantic_version (s, ab ? "-+" : nullptr) + semantic_version (const std::string& s, flags fs, const char* bs) + : semantic_version (s, 0, fs, bs) { } @@ -42,29 +28,53 @@ namespace butl }; LIBBUTL_SYMEXPORT semantic_version_result - parse_semantic_version_impl (const std::string&, std::size_t, const char*); + parse_semantic_version_impl (const std::string&, + std::size_t, + semantic_version::flags, + const char*); inline optional<semantic_version> - parse_semantic_version (const std::string& s, bool ab) + parse_semantic_version (const std::string& s, + semantic_version::flags fs, + const char* bs) { - return parse_semantic_version (s, ab ? "-+" : nullptr); + return parse_semantic_version_impl (s, 0, fs, bs).version; } inline optional<semantic_version> - parse_semantic_version (const std::string& s, const char* bs) + parse_semantic_version (const std::string& s, + std::size_t p, + semantic_version::flags fs, + const char* bs) { - return parse_semantic_version_impl (s, 0, bs).version; + return parse_semantic_version_impl (s, p, fs, bs).version; } - inline optional<semantic_version> - parse_semantic_version (const std::string& s, std::size_t p, bool ab) + inline semantic_version::flags + operator& (semantic_version::flags x, semantic_version::flags y) { - return parse_semantic_version (s, p, ab ? "-+" : nullptr); + return x &= y; } - inline optional<semantic_version> - parse_semantic_version (const std::string& s, std::size_t p, const char* bs) + inline semantic_version::flags + operator| (semantic_version::flags x, semantic_version::flags y) + { + return x |= y; + } + + inline semantic_version::flags + operator&= (semantic_version::flags& x, semantic_version::flags y) + { + return x = static_cast<semantic_version::flags> ( + static_cast<std::uint16_t> (x) & + static_cast<std::uint16_t> (y)); + } + + inline semantic_version::flags + operator|= (semantic_version::flags& x, semantic_version::flags y) { - return parse_semantic_version_impl (s, p, bs).version; + return x = static_cast<semantic_version::flags> ( + static_cast<std::uint16_t> (x) | + static_cast<std::uint16_t> (y)); } } diff --git a/tests/semantic-version/driver.cxx b/tests/semantic-version/driver.cxx index 2bdd415..3c20a6c 100644 --- a/tests/semantic-version/driver.cxx +++ b/tests/semantic-version/driver.cxx @@ -23,7 +23,6 @@ main () 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 ()); @@ -46,17 +45,27 @@ main () // 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 (semver ("1", semver::allow_omit_minor) == semver (1, 0, 0)); + assert (semver ("1-2", semver::allow_omit_minor | semver::allow_build) == semver (1, 0, 0, "-2")); + assert (semver ("1.2", semver::allow_omit_minor) == semver (1, 2, 0)); + assert (semver ("1.2+a", semver::allow_omit_minor | semver::allow_build) == semver (1, 2, 0, "+a")); + assert (semver ("1.2", semver::allow_omit_patch) == semver (1, 2, 0)); + assert (semver ("1.2-3", semver::allow_omit_patch | semver::allow_build) == semver (1, 2, 0, "-3")); + assert (semver ("1.2.a1", semver::allow_omit_patch | semver::allow_build, ".+-") == semver (1, 2, 0, ".a1")); + assert (semver ("1.2.3") == semver (1, 2, 3)); + assert (semver ("1.2.3-4", semver::allow_build) == semver (1, 2, 3, "-4")); + assert (semver ("1.2.3+4", semver::allow_build) == semver (1, 2, 3, "+4")); + assert (semver ("1.2.3.4", semver::allow_build, "+-.") == semver (1, 2, 3, ".4")); + assert (semver ("1.2.3a", semver::allow_build, "") == semver (1, 2, 3, "a")); + + try {semver v ("1"); assert (false);} catch (failed) {} + try {semver v ("1.x.2"); assert (false);} catch (failed) {} + try {semver v ("1.2"); assert (false);} catch (failed) {} + try {semver v ("1.2.x"); assert (false);} catch (failed) {} + try {semver v ("1.2.3-4"); 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. |