diff options
-rw-r--r-- | butl/standard-version | 107 | ||||
-rw-r--r-- | butl/standard-version.cxx | 178 | ||||
-rw-r--r-- | butl/standard-version.ixx | 14 | ||||
-rw-r--r-- | tests/standard-version/driver.cxx | 10 | ||||
-rw-r--r-- | tests/standard-version/testscript | 83 |
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 + } + } +} |