From baf3e0359fe3b384f015702e337c3b4c9aea3ab0 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Tue, 12 Oct 2021 18:57:03 +0300 Subject: Add support for version iteration in string representation --- libbpkg/manifest.cxx | 115 ++++++++++++++++++++++++++++----------- libbpkg/manifest.hxx | 55 ++++++++++++++++--- tests/package-version/driver.cxx | 20 ++++++- 3 files changed, 145 insertions(+), 45 deletions(-) diff --git a/libbpkg/manifest.cxx b/libbpkg/manifest.cxx index c8bf456..48ec383 100644 --- a/libbpkg/manifest.cxx +++ b/libbpkg/manifest.cxx @@ -4,14 +4,17 @@ #include #include +#include #include #include #include -#include // strncmp(), strcmp() -#include // move() -#include // uint*_t, UINT16_MAX -#include // find(), find_if_not(), find_first_of(), replace() -#include // invalid_argument +#include // strtoull() +#include // strncmp(), strcmp(), strchr() +#include // move() +#include // uint*_t +#include // find(), find_if_not(), find_first_of(), replace() +#include // invalid_argument +#include // remove_reference #include #include @@ -172,12 +175,12 @@ namespace bpkg canonical_upstream ( data_type (upstream.c_str (), data_type::parse::upstream, - false /* fold_zero_revision */). + none). canonical_upstream), canonical_release ( data_type (release ? release->c_str () : nullptr, data_type::parse::release, - false /* fold_zero_revision */). + none). canonical_release) { // Check members constrains. @@ -254,9 +257,12 @@ namespace bpkg } version::data_type:: - data_type (const char* v, parse pr, bool fold_zero_rev) + data_type (const char* v, parse pr, version::flags fl) { - if (fold_zero_rev) + if ((fl & version::fold_zero_revision) != 0) + assert (pr == parse::full); + + if ((fl & version::allow_iteration) != 0) assert (pr == parse::full); // Otherwise compiler gets confused with string() member. @@ -271,32 +277,75 @@ namespace bpkg return; } - assert (v != nullptr); - - optional ep; - auto bad_arg = [](const string& d) {throw invalid_argument (d);}; - auto uint16 = [&bad_arg](const string& s, const char* what) -> uint16_t + auto parse_uint = [&bad_arg](const string& s, auto& r, const char* what) { - try - { - uint64_t v (stoull (s)); + using type = typename remove_reference::type; - if (v <= UINT16_MAX) // From . - return static_cast (v); - } - catch (const std::exception&) + if (!s.empty () && s[0] != '-' && s[0] != '+') // strtoull() allows these. { - // Fall through. + const char* b (s.c_str ()); + char* e (nullptr); + errno = 0; // We must clear it according to POSIX. + uint64_t v (strtoull (b, &e, 10)); // Can't throw. + + if (errno != ERANGE && + e == b + s.size () && + v <= numeric_limits::max ()) + { + r = static_cast (v); + return; + } } - bad_arg (string (what) + " should be 2-byte unsigned integer"); + bad_arg (string (what) + " should be " + + std::to_string (sizeof (type)) + "-byte unsigned integer"); + }; - assert (false); // Can't be here. - return 0; + auto parse_uint16 = [&parse_uint](const string& s, const char* what) + { + uint16_t r; + parse_uint (s, r, what); + return r; + }; + + auto parse_uint32 = [&parse_uint](const string& s, const char* what) + { + uint32_t r; + parse_uint (s, r, what); + return r; }; + assert (v != nullptr); + + // Parse the iteration, if allowed. + // + // Note that allowing iteration is not very common, so let's handle it in + // an ad hoc way not to complicate the subsequent parsing. + // + string storage; + if (pr == parse::full) + { + iteration = 0; + + // Note that if not allowed but the iteration is present, then the below + // version parsing code will fail with appropriate diagnostics. + // + if ((fl & version::allow_iteration) != 0) + { + if (const char* p = strchr (v, '#')) + { + iteration = parse_uint32 (p + 1, "iteration"); + + storage.assign (v, p - v); + v = storage.c_str (); + } + } + } + + optional ep; + enum class mode {epoch, upstream, release, revision}; mode m (pr == parse::full ? (v[0] == '+' @@ -351,7 +400,7 @@ namespace bpkg if (lnn >= cb) // Contains non-digits. bad_arg ("epoch should be 2-byte unsigned integer"); - ep = uint16 (string (cb, p), "epoch"); + ep = parse_uint16 (string (cb, p), "epoch"); } else canon_part->add (cb, p, lnn < cb); @@ -424,9 +473,9 @@ namespace bpkg if (lnn >= cb) // Contains non-digits. bad_arg ("revision should be 2-byte unsigned integer"); - std::uint16_t rev (uint16 (cb, "revision")); + uint16_t rev (parse_uint16 (cb, "revision")); - if (rev != 0 || !fold_zero_rev) + if (rev != 0 || (fl & fold_zero_revision) == 0) revision = rev; } else if (cb != p) @@ -658,7 +707,7 @@ namespace bpkg if (mnv != "$") try { - min_version = version (mnv, false /* fold_zero_revision */); + min_version = version (mnv, version::none); } catch (const invalid_argument& e) { @@ -685,7 +734,7 @@ namespace bpkg if (mxv != "$") try { - max_version = version (mxv, false /* fold_zero_revision */); + max_version = version (mxv, version::none); } catch (const invalid_argument& e) { @@ -789,7 +838,7 @@ namespace bpkg // version. // if (vs != "$") - v = version (vs, false /* fold_zero_revision */); + v = version (vs, version::none); switch (operation) { @@ -4901,13 +4950,13 @@ namespace bpkg } version - extract_package_version (const char* s, bool fold_zero_revision) + extract_package_version (const char* s, version::flags fl) { using traits = string::traits_type; if (const char* p = traits::find (s, traits::length (s), '/')) { - version r (p + 1, fold_zero_revision); + version r (p + 1, fl); if (r.release && r.release->empty ()) throw invalid_argument ("earliest version"); diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx index 8a11a85..f487a90 100644 --- a/libbpkg/manifest.hxx +++ b/libbpkg/manifest.hxx @@ -68,13 +68,20 @@ namespace bpkg // std::invalid_argument if the passed string is not a valid version // representation. // + enum flags + { + none = 0, + fold_zero_revision = 0x01, + allow_iteration = 0x02 + }; + explicit - version (const std::string& v, bool fold_zero_revision = true) - : version (v.c_str (), fold_zero_revision) {} + version (const std::string& v, flags fl = fold_zero_revision) + : version (v.c_str (), fl) {} explicit - version (const char* v, bool fold_zero_revision = true) - : version (data_type (v, data_type::parse::full, fold_zero_revision)) + version (const char* v, flags fl = fold_zero_revision) + : version (data_type (v, data_type::parse::full, fl)) { } @@ -169,7 +176,7 @@ namespace bpkg { enum class parse {full, upstream, release}; - data_type (const char*, parse, bool fold_zero_revision); + data_type (const char*, parse, flags); // Note that there is no iteration component as it can't be present in // the string representation passed to the ctor. @@ -178,6 +185,7 @@ namespace bpkg std::string upstream; butl::optional release; butl::optional revision; + std::uint32_t iteration; std::string canonical_upstream; std::string canonical_release; }; @@ -188,7 +196,7 @@ namespace bpkg upstream (std::move (d.upstream)), release (std::move (d.release)), revision (d.revision), - iteration (0), + iteration (d.iteration), canonical_upstream (std::move (d.canonical_upstream)), canonical_release (std::move (d.canonical_release)) {} }; @@ -199,6 +207,34 @@ namespace bpkg return os << (v.empty () ? "" : v.string ()); } + inline version::flags + operator&= (version::flags& x, version::flags y) + { + return x = static_cast ( + static_cast (x) & + static_cast (y)); + } + + inline version::flags + operator|= (version::flags& x, version::flags y) + { + return x = static_cast ( + static_cast (x) | + static_cast (y)); + } + + inline version::flags + operator& (version::flags x, version::flags y) + { + return x &= y; + } + + inline version::flags + operator| (version::flags x, version::flags y) + { + return x |= y; + } + // priority // class priority @@ -1707,13 +1743,14 @@ namespace bpkg // Note: the package name is not verified. // LIBBPKG_EXPORT version - extract_package_version (const char*, bool fold_zero_revision = true); + extract_package_version (const char*, + version::flags fl = version::fold_zero_revision); inline version extract_package_version (const std::string& s, - bool fold_zero_revision = true) + version::flags fl = version::fold_zero_revision) { - return extract_package_version (s.c_str (), fold_zero_revision); + return extract_package_version (s.c_str (), fl); } } diff --git a/tests/package-version/driver.cxx b/tests/package-version/driver.cxx index 0c84429..0a5ff43 100644 --- a/tests/package-version/driver.cxx +++ b/tests/package-version/driver.cxx @@ -24,11 +24,12 @@ namespace bpkg using butl::nullopt; static bool - bad_version (const string& v) + bad_version (const string& v, + version::flags fl = version::fold_zero_revision) { try { - version bv (v); + version bv (v, fl); return false; } catch (const invalid_argument&) @@ -138,6 +139,16 @@ namespace bpkg assert (bad_version (0, "", "", 0)); // Same. assert (bad_version (0, "", "", 0, 1)); // Unexpected iteration. + assert (bad_version ("1.0.0#1")); // Iteration disallowed. + + // Bad iteration. + // + assert (bad_version ("1.0.0#a", version::allow_iteration)); + assert (bad_version ("1.0.0#1a", version::allow_iteration)); + assert (bad_version ("1.0.0#", version::allow_iteration)); + assert (bad_version ("1.0.0#5000000000", version::allow_iteration)); + assert (bad_version ("1.0.0#+1", version::allow_iteration)); + { version v1; assert (v1.empty ()); @@ -258,7 +269,7 @@ namespace bpkg assert (test_constructor (v)); } { - version v ("+10-B+0", false /* fold_zero_revision */); + version v ("+10-B+0", version::none); assert (v.string () == "+10-B+0"); assert (v.canonical_upstream == "b"); assert (test_constructor (v)); @@ -407,6 +418,9 @@ namespace bpkg assert (version (1, "2.0", nullopt, 3, 4).compare ( version (1, "2.0", nullopt, 5, 6), true) == 0); + + assert (version ("1.1.1-a.0.1+2#34", version::flags::allow_iteration) == + version (1, "1.1.1", string ("a.0.1"), 2, 34)); } catch (const exception& e) { -- cgit v1.1