aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2021-10-12 18:57:03 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2021-10-13 10:29:56 +0300
commitbaf3e0359fe3b384f015702e337c3b4c9aea3ab0 (patch)
treece20de7982fadbddef4fadc9c9a1d12626b4b14f
parent67d7cdd57d3076ccf4c44de3a6b8045bb67d1441 (diff)
Add support for version iteration in string representation
-rw-r--r--libbpkg/manifest.cxx115
-rw-r--r--libbpkg/manifest.hxx55
-rw-r--r--tests/package-version/driver.cxx20
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 <libbpkg/manifest.hxx>
#include <string>
+#include <limits>
#include <ostream>
#include <sstream>
#include <cassert>
-#include <cstring> // strncmp(), strcmp()
-#include <utility> // move()
-#include <cstdint> // uint*_t, UINT16_MAX
-#include <algorithm> // find(), find_if_not(), find_first_of(), replace()
-#include <stdexcept> // invalid_argument
+#include <cstdlib> // strtoull()
+#include <cstring> // strncmp(), strcmp(), strchr()
+#include <utility> // move()
+#include <cstdint> // uint*_t
+#include <algorithm> // find(), find_if_not(), find_first_of(), replace()
+#include <stdexcept> // invalid_argument
+#include <type_traits> // remove_reference
#include <libbutl/url.hxx>
#include <libbutl/path.hxx>
@@ -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<uint16_t> 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<decltype (r)>::type;
- if (v <= UINT16_MAX) // From <cstdint>.
- return static_cast<uint16_t> (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<type>::max ())
+ {
+ r = static_cast<type> (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<uint16_t> 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<std::string> release;
butl::optional<std::uint16_t> 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 () ? "<empty-version>" : v.string ());
}
+ inline version::flags
+ operator&= (version::flags& x, version::flags y)
+ {
+ return x = static_cast<version::flags> (
+ static_cast<std::uint16_t> (x) &
+ static_cast<std::uint16_t> (y));
+ }
+
+ inline version::flags
+ operator|= (version::flags& x, version::flags y)
+ {
+ return x = static_cast<version::flags> (
+ static_cast<std::uint16_t> (x) |
+ static_cast<std::uint16_t> (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)
{