aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--butl/standard-version107
-rw-r--r--butl/standard-version.cxx178
-rw-r--r--butl/standard-version.ixx14
-rw-r--r--tests/standard-version/driver.cxx10
-rw-r--r--tests/standard-version/testscript83
5 files changed, 372 insertions, 20 deletions
diff --git a/butl/standard-version b/butl/standard-version
index 0888900..c9637d8 100644
--- a/butl/standard-version
+++ b/butl/standard-version
@@ -12,6 +12,8 @@
#include <butl/export>
+#include <butl/optional>
+
namespace butl
{
// The build2 "standard version":
@@ -34,10 +36,13 @@ namespace butl
std::string snapshot_id; // Empty if not specified.
std::uint16_t revision = 0; // 0 if not specified.
- std::uint16_t major () const;
- std::uint16_t minor () const;
- std::uint16_t patch () const;
- std::uint16_t pre_release () const; // Note: 0 is ambiguous (-a.0.z).
+ std::uint16_t major () const noexcept;
+ std::uint16_t minor () const noexcept;
+ std::uint16_t patch () const noexcept;
+
+ // Note: 0 is ambiguous (-a.0.z).
+ //
+ std::uint16_t pre_release () const noexcept;
// Note: return empty if the corresponding component is unspecified.
//
@@ -47,14 +52,14 @@ namespace butl
std::string string_pre_release () const; // Pre-release part only (a.1).
std::string string_snapshot () const; // Snapshot part only (1234.1f23).
- bool empty () const {return version == 0;}
+ bool empty () const noexcept {return version == 0;}
- bool alpha () const;
- bool beta () const;
- bool snapshot () const {return snapshot_sn != 0;}
+ bool alpha () const noexcept;
+ bool beta () const noexcept;
+ bool snapshot () const noexcept {return snapshot_sn != 0;}
int
- compare (const standard_version&) const;
+ compare (const standard_version&) const noexcept;
// Parse the version. Throw std::invalid_argument if the format is not
// recognizable or components are invalid.
@@ -88,13 +93,37 @@ namespace butl
};
inline bool
- operator== (const standard_version& x, const standard_version& y)
+ operator< (const standard_version& x, const standard_version& y) noexcept
+ {
+ return x.compare (y) < 0;
+ }
+
+ inline bool
+ operator> (const standard_version& x, const standard_version& y) noexcept
+ {
+ return x.compare (y) > 0;
+ }
+
+ inline bool
+ operator== (const standard_version& x, const standard_version& y) noexcept
{
return x.compare (y) == 0;
}
inline bool
- operator!= (const standard_version& x, const standard_version& y)
+ operator<= (const standard_version& x, const standard_version& y) noexcept
+ {
+ return x.compare (y) <= 0;
+ }
+
+ inline bool
+ operator>= (const standard_version& x, const standard_version& y) noexcept
+ {
+ return x.compare (y) >= 0;
+ }
+
+ inline bool
+ operator!= (const standard_version& x, const standard_version& y) noexcept
{
return !(x == y);
}
@@ -104,6 +133,62 @@ namespace butl
{
return o << x.string ();
}
+
+ // The build2 "standard version" constraint:
+ //
+ // ('==' | '>' | '<' | '>=' | '<=') <version>
+ // ('(' | '[') <version> <version> (')' | ']')
+ //
+ struct LIBBUTL_EXPORT standard_version_constraint
+ {
+ butl::optional<standard_version> min_version;
+ butl::optional<standard_version> max_version;
+ bool min_open;
+ bool max_open;
+
+ // Parse the version constraint. Throw std::invalid_argument on error.
+ //
+ explicit
+ standard_version_constraint (const std::string&);
+
+ // Throw std::invalid_argument if the specified version range is invalid.
+ //
+ standard_version_constraint (
+ butl::optional<standard_version> min_version, bool min_open,
+ butl::optional<standard_version> max_version, bool max_open);
+
+ standard_version_constraint (const standard_version& v)
+ : standard_version_constraint (v, false, v, false) {}
+
+ standard_version_constraint () = default;
+
+ std::string
+ string () const;
+
+ bool
+ empty () const noexcept {return !min_version && !max_version;}
+ };
+
+ inline bool
+ operator== (const standard_version_constraint& x,
+ const standard_version_constraint& y)
+ {
+ return x.min_version == y.min_version && x.max_version == y.max_version &&
+ x.min_open == y.min_open && x.max_open == y.max_open;
+ }
+
+ inline bool
+ operator!= (const standard_version_constraint& x,
+ const standard_version_constraint& y)
+ {
+ return !(x == y);
+ }
+
+ inline std::ostream&
+ operator<< (std::ostream& o, const standard_version_constraint& x)
+ {
+ return o << x.string ();
+ }
}
#include <butl/standard-version.ixx>
diff --git a/butl/standard-version.cxx b/butl/standard-version.cxx
index 7e144a6..552aac8 100644
--- a/butl/standard-version.cxx
+++ b/butl/standard-version.cxx
@@ -4,8 +4,10 @@
#include <butl/standard-version>
+#include <cassert>
#include <cstdlib> // strtoull()
#include <cstddef> // size_t
+#include <utility> // move()
#include <stdexcept> // invalid_argument
#include <butl/utility> // alnum()
@@ -90,7 +92,7 @@ namespace butl
ep = *e == '~';
}
- size_t p (0), n (s.size ());
+ size_t p (0);
if (ep)
{
@@ -152,7 +154,7 @@ namespace butl
if (s[p] == '+')
revision = parse_num (s, ++p, "invalid revision", 1, uint16_t (~0));
- if (p != n)
+ if (p != s.size ())
bail ("junk after version");
if (ab != 0)
@@ -330,4 +332,176 @@ namespace butl
return r;
}
+
+ // standard_version_constraint
+ //
+ standard_version_constraint::
+ standard_version_constraint (const std::string& s)
+ {
+ using std::string; // Not to confuse with string().
+
+ auto bail = [] (const string& m) {throw invalid_argument (m);};
+ const char* spaces (" \t");
+
+ size_t p (0);
+ char c (s[p]);
+
+ if (c == '(' || c == '[') // Can be '\0'.
+ {
+ bool min_open = c == '(';
+
+ p = s.find_first_not_of (spaces, ++p);
+ if (p == string::npos)
+ bail ("no min version");
+
+ size_t e (s.find_first_of (spaces, p));
+
+ standard_version min_version;
+
+ try
+ {
+ min_version = standard_version (s.substr (p, e - p));
+ }
+ catch (const invalid_argument& e)
+ {
+ bail (string ("invalid min version: ") + e.what ());
+ }
+
+ p = s.find_first_not_of (spaces, e);
+ if (p == string::npos)
+ bail ("no max version");
+
+ e = s.find_first_of (" \t])", p);
+
+ standard_version max_version;
+
+ try
+ {
+ max_version = standard_version (s.substr (p, e - p));
+ }
+ catch (const invalid_argument& e)
+ {
+ bail (string ("invalid max version: ") + e.what ());
+ }
+
+ p = s.find_first_of ("])", e);
+ if (p == string::npos)
+ bail ("no closing bracket");
+
+ bool max_open = s[p] == ')';
+
+ if (++p != s.size ())
+ bail ("junk after constraint");
+
+ // Verify and copy the constraint.
+ //
+ *this = standard_version_constraint (min_version, min_open,
+ max_version, max_open);
+ }
+ else
+ {
+ enum comparison {eq, lt, gt, le, ge};
+ comparison operation (eq); // Uninitialized warning.
+
+ if (s.compare (0, p = 2, "==") == 0)
+ operation = eq;
+ else if (s.compare (0, p = 2, ">=") == 0)
+ operation = ge;
+ else if (s.compare (0, p = 2, "<=") == 0)
+ operation = le;
+ else if (s.compare (0, p = 1, ">") == 0)
+ operation = gt;
+ else if (s.compare (0, p = 1, "<") == 0)
+ operation = lt;
+ else
+ bail ("invalid constraint");
+
+ p = s.find_first_not_of (spaces, p);
+
+ if (p == string::npos)
+ bail ("no version");
+
+ standard_version v;
+
+ try
+ {
+ v = standard_version (s.substr (p));
+ }
+ catch (const invalid_argument& e)
+ {
+ bail (string ("invalid version: ") + e.what ());
+ }
+
+ // Verify and copy the constraint.
+ //
+ switch (operation)
+ {
+ case comparison::eq:
+ *this = standard_version_constraint (v);
+ break;
+ case comparison::lt:
+ *this = standard_version_constraint (nullopt, true, move (v), true);
+ break;
+ case comparison::le:
+ *this = standard_version_constraint (nullopt, true, move (v), false);
+ break;
+ case comparison::gt:
+ *this = standard_version_constraint (move (v), true, nullopt, true);
+ break;
+ case comparison::ge:
+ *this = standard_version_constraint (move (v), false, nullopt, true);
+ break;
+ }
+ }
+ }
+
+ standard_version_constraint::
+ standard_version_constraint (optional<standard_version> mnv, bool mno,
+ optional<standard_version> mxv, bool mxo)
+ : min_version (move (mnv)),
+ max_version (move (mxv)),
+ min_open (mno),
+ max_open (mxo)
+ {
+ assert (
+ // Min and max versions can't both be absent.
+ //
+ (min_version || max_version) &&
+
+ // Version should be non-empty.
+ //
+ (!min_version || !min_version->empty ()) &&
+ (!max_version || !max_version->empty ()) &&
+
+ // Absent version endpoint (infinity) should be open.
+ //
+ (min_version || min_open) && (max_version || max_open));
+
+ if (min_version && max_version)
+ {
+ if (*min_version > *max_version)
+ throw invalid_argument ("min version is greater than max version");
+
+ if (*min_version == *max_version && (min_open || max_open))
+ throw invalid_argument ("equal version endpoints not closed");
+ }
+ }
+
+ string standard_version_constraint::
+ string () const
+ {
+ assert (!empty ());
+
+ if (!min_version)
+ return (max_open ? "< " : "<= ") + max_version->string ();
+
+ if (!max_version)
+ return (min_open ? "> " : ">= ") + min_version->string ();
+
+ if (*min_version == *max_version)
+ return "== " + min_version->string ();
+
+ return (min_open ? '(' : '[') + min_version->string () + ' ' +
+ max_version->string () + (max_open ? ')' : ']');
+ }
}
diff --git a/butl/standard-version.ixx b/butl/standard-version.ixx
index e414deb..b01faef 100644
--- a/butl/standard-version.ixx
+++ b/butl/standard-version.ixx
@@ -18,7 +18,7 @@ namespace butl
}
inline std::uint16_t standard_version::
- major () const
+ major () const noexcept
{
std::uint64_t v (version / 10);
std::uint64_t ab (v % 1000);
@@ -29,7 +29,7 @@ namespace butl
}
inline std::uint16_t standard_version::
- minor () const
+ minor () const noexcept
{
std::uint64_t v (version / 10);
std::uint64_t ab (v % 1000);
@@ -40,7 +40,7 @@ namespace butl
}
inline std::uint16_t standard_version::
- patch () const
+ patch () const noexcept
{
std::uint64_t v (version / 10);
std::uint64_t ab (v % 1000);
@@ -51,7 +51,7 @@ namespace butl
}
inline std::uint16_t standard_version::
- pre_release () const
+ pre_release () const noexcept
{
std::uint64_t ab (version / 10 % 1000);
if (ab > 500)
@@ -61,21 +61,21 @@ namespace butl
}
inline bool standard_version::
- alpha () const
+ alpha () const noexcept
{
std::uint64_t abe (version % 10000);
return abe > 0 && abe < 5000;
}
inline bool standard_version::
- beta () const
+ beta () const noexcept
{
std::uint64_t abe (version % 10000);
return abe > 5000;
}
inline int standard_version::
- compare (const standard_version& v) const
+ compare (const standard_version& v) const noexcept
{
if (epoch != v.epoch)
return epoch < v.epoch ? -1 : 1;
diff --git a/tests/standard-version/driver.cxx b/tests/standard-version/driver.cxx
index 1299090..d301c8c 100644
--- a/tests/standard-version/driver.cxx
+++ b/tests/standard-version/driver.cxx
@@ -72,11 +72,13 @@ version (const string& s)
// argv[0] -a <version>
// argv[0] -b <version>
// argv[0] -c <version> <version>
+// argv[0] -r
// argv[0]
//
// -a output 'y' for alpha-version, 'n' otherwise
// -b output 'y' for beta-version, 'n' otherwise
// -c output 0 if versions are equal, -1 if the first one is less, 1 otherwise
+// -r create version constraints from STDIN lines, and print them to STDOUT
//
// If no options are specified, then create versions from STDIN lines, and
// print them to STDOUT.
@@ -117,6 +119,14 @@ try
int r (version (argv[2]).compare (version (argv[3])));
cout << r << endl;
}
+ else if (o == "-r")
+ {
+ assert (argc == 2);
+
+ string s;
+ while (getline (cin, s))
+ cout << standard_version_constraint (s) << endl;
+ }
else
assert (false);
diff --git a/tests/standard-version/testscript b/tests/standard-version/testscript
index 335bed9..9c80ddb 100644
--- a/tests/standard-version/testscript
+++ b/tests/standard-version/testscript
@@ -188,3 +188,86 @@
$* '1.2.3-a.1.2.xy' '1.2.3-a.1.2' >'0' : ignore-snapshot-id
}
}
+
+: constraints
+:
+{
+ test.options += -r
+
+ : range
+ :
+ {
+ : valid
+ :
+ $* <<EOI >>EOE
+ [1.2.3 1.2.4]
+ (1.2.3 1.2.4)
+ [ 1.2.3 1.2.4 ]
+ EOI
+ [1.2.3 1.2.4]
+ (1.2.3 1.2.4)
+ [1.2.3 1.2.4]
+ EOE
+
+ : invalid
+ :
+ {
+ $* <'' 2>'invalid constraint' == 1 : empty
+ $* <'1' 2>'invalid constraint' == 1 : no-opening
+ $* <'[ ' 2>'no min version' == 1 : no-min
+ $* <'[1.2.3' 2>'no max version' == 1 : no-max
+ $* <'[1.2.3 1.2.4' 2>'no closing bracket' == 1 : no-closing
+ $* <'[1.2.3 1.2.4)]' 2>'junk after constraint' == 1 : junk
+
+ : invalid-min
+ :
+ $* <'[1' 2>"invalid min version: '.' expected after major version" == 1
+
+ : invalid-max
+ :
+ $* <'[1.2.3 1' 2>"invalid max version: '.' expected after major version" == 1
+
+ : min-gt-max
+ :
+ $* <'[1.2.4 1.2.3]' 2>'min version is greater than max version' == 1
+
+ : open-end
+ :
+ $* <'[1.2.3 1.2.3)' 2>'equal version endpoints not closed' == 1
+ }
+ }
+
+ : comparison
+ :
+ {
+ : valid
+ :
+ $* <<EOI >>EOE
+ == 1.2.3
+ >= 1.2.3
+ <= 1.2.3
+ > 1.2.3
+ < 1.2.3
+ <=1.2.3
+ <1.2.3
+ EOI
+ == 1.2.3
+ >= 1.2.3
+ <= 1.2.3
+ > 1.2.3
+ < 1.2.3
+ <= 1.2.3
+ < 1.2.3
+ EOE
+
+ : invalid
+ :
+ {
+ $* <'>=' 2>'no version' == 1 : no-version
+
+ : junk
+ :
+ $* <'>= 1.2.3-a.1.1.ads@' 2>'invalid version: junk after version' == 1
+ }
+ }
+}