From e13873e57383a617da352b38af016fed62eda5e9 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Fri, 21 Dec 2018 20:14:19 +0300 Subject: Add support for $ in package manifest dependency constraint --- libbpkg/manifest.cxx | 470 ++++++++++++++++++++++++++++++++++------------ libbpkg/manifest.hxx | 54 ++++-- tests/manifest/driver.cxx | 87 +++++++-- tests/manifest/testscript | 134 ++++++++++++- 4 files changed, 592 insertions(+), 153 deletions(-) diff --git a/libbpkg/manifest.cxx b/libbpkg/manifest.cxx index b94b4da..7180875 100644 --- a/libbpkg/manifest.cxx +++ b/libbpkg/manifest.cxx @@ -622,10 +622,15 @@ namespace bpkg bail (no_max_version); version min_version; + string mnv (s, p, e - p); + // Leave the min version empty if it refers to the dependent package + // version. + // + if (mnv != "$") try { - min_version = version (string (s, p, e - p)); + min_version = version (mnv); } catch (const invalid_argument& e) { @@ -645,10 +650,15 @@ namespace bpkg bail (invalid_range); version max_version; + string mxv (s, p, e - p); + // Leave the max version empty if it refers to the dependent package + // version. + // + if (mxv != "$") try { - max_version = version (string (s, p, e - p)); + max_version = version (mxv); } catch (const invalid_argument& e) { @@ -677,36 +687,51 @@ namespace bpkg } else if (c == '~' || c == '^') // The shortcut operator. { - // To be used in the shortcut operator the package version must - // be a standard version. + // If the shortcut operator refers to the dependent package version (the + // '$' character), then create an incomplete constraint. Otherwise, + // assume the version is standard and parse the operator representation + // as the standard version constraint. // - standard_version_constraint vc; + size_t p (s.find_first_not_of (spaces, 1)); - try + if (p != string::npos && s[p] == '$' && p + 1 == s.size ()) { - vc = standard_version_constraint (s); + *this = dependency_constraint (version (), c == '~', + version (), c == '^'); } - catch (const invalid_argument& e) + else { - bail (string ("invalid dependency constraint: ") + e.what ()); - } + // To be used in the shortcut operator the package version must be + // standard. + // + standard_version_constraint vc; - try - { - assert (vc.min_version && vc.max_version); + try + { + vc = standard_version_constraint (s); + } + catch (const invalid_argument& e) + { + bail (string ("invalid dependency constraint: ") + e.what ()); + } - *this = dependency_constraint ( - version (vc.min_version->string ()), - vc.min_open, - version (vc.max_version->string ()), - vc.max_open); - } - catch (const invalid_argument&) - { - // The standard version is a package version, so the conversion - // should never fail. - // - assert (false); + try + { + assert (vc.min_version && vc.max_version); + + *this = dependency_constraint ( + version (vc.min_version->string ()), + vc.min_open, + version (vc.max_version->string ()), + vc.max_open); + } + catch (const invalid_argument&) + { + // Any standard version is a valid package version, so the + // conversion should never fail. + // + assert (false); + } } } else // The version comparison notation. @@ -741,7 +766,14 @@ namespace bpkg try { - version v (string (s, p)); + version v; + string vs (s, p); + + // Leave the version empty if it refers to the dependent package + // version. + // + if (vs != "$") + v = version (vs); switch (operation) { @@ -783,50 +815,208 @@ namespace bpkg // (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) + bool mxe (max_version->empty ()); + + // If endpoint versions do not refer to the dependent package version + // then the min version must (naturally) be lower than or equal to the + // max version. + // + if (*min_version > *max_version && !mxe) throw invalid_argument ("min version is greater than max version"); if (*min_version == *max_version) { - if (min_open || max_open) + // For non-empty versions, both endpoints must be closed, representing + // the `== ` constraint. For empty versions no greater than + // one endpoint can be open, representing the `== $`, `~$`, or `^$` + // constraints. + // + if ((!mxe && (min_open || max_open)) || (mxe && min_open && max_open)) throw invalid_argument ("equal version endpoints not closed"); - if (min_version->release && min_version->release->empty ()) + // If endpoint versions do not refer to the dependent package version + // then they can't be earliest. Note: an empty version is earliest. + // + if (!mxe && max_version->release && max_version->release->empty ()) throw invalid_argument ("equal version endpoints are earliest"); } } } + dependency_constraint dependency_constraint:: + effective (version v) const + { + // The dependent package version can't be empty or earliest. + // + if (v.empty ()) + throw invalid_argument ("empty version"); + + if (v.release && v.release->empty ()) + throw invalid_argument ("earliest version"); + + // For the more detailed description of the following semantics refer to + // the depends value documentation. + + // Strip the revision and iteration. + // + v = version (v.epoch, + move (v.upstream), + move (v.release), + 0 /* revision */, + 0 /* iteration */); + + // Calculate effective constraint for a shortcut operator. + // + if (min_version && + min_version->empty () && + max_version == min_version && + (min_open || max_open)) + { + assert (!min_open || !max_open); // Endpoints cannot be both open. + + string vs (v.string ()); + + if (optional sv = parse_standard_version (vs)) + try + { + char op (min_open ? '~' : '^'); + standard_version_constraint vc (op + vs); + + // The shortcut operators' representation is a range. + // + assert (vc.min_version && vc.max_version); + + standard_version& mnv (*vc.min_version); + standard_version& mxv (*vc.max_version); + + // For a release adjust the min version endpoint, setting its patch to + // zero. For ^ also set the minor version to zero, unless the major + // version is zero (reduced to ~). + // + if (sv->release ()) + { + mnv = standard_version ( + sv->epoch, + sv->major (), + op == '^' && sv->major () != 0 ? 0 : sv->minor (), + 0 /* patch */); + } + // + // For a final pre-release or a patch snapshot we check if there has + // been a compatible final release (patch is not zero for ~ and + // minor/patch are not zero for ^). If that's the case, then fallback + // to the release case and start the range from the first alpha + // otherwise. + // + else if (sv->final () || (sv->snapshot () && sv->patch () != 0)) + { + mnv = standard_version ( + sv->epoch, + sv->major (), + op == '^' && sv->major () != 0 ? 0 : sv->minor (), + 0 /* patch */, + sv->patch () != 0 || (op == '^' && sv->minor () != 0) + ? 0 + : 1 /* pre-release */); + } + // + // For a major/minor snapshot we assume that all the packages are + // developed in the lockstep and convert the constraint range to + // represent this "snapshot series". + // + else + { + assert (sv->snapshot () && sv->patch () == 0); + + uint16_t pr (*sv->pre_release ()); + + mnv = standard_version (sv->epoch, + sv->major (), + sv->minor (), + 0 /* patch */, + pr, + 1 /* snapshot_sn */, + "" /* snapshot_id */); + + // Note: the max version endpoint is already open. + // + mxv = standard_version (sv->epoch, + sv->major (), + sv->minor (), + 0 /* patch */, + pr + 1); + } + + return dependency_constraint (version (mnv.string ()), vc.min_open, + version (mxv.string ()), vc.max_open); + } + catch (const invalid_argument&) + { + // There shouldn't be a reason for dependency_constraint() to throw. + // + assert (false); + } + + throw invalid_argument (vs + " is not a standard version"); + } + + // Calculate effective constraint for a range. + // + return dependency_constraint ( + min_version && min_version->empty () ? v : min_version, min_open, + max_version && max_version->empty () ? v : max_version, max_open); + } + ostream& operator<< (ostream& o, const dependency_constraint& c) { assert (!c.empty ()); + auto print = [&o] (const version& v) -> ostream& + { + return v.empty () ? (o << '$') : (o << v); + }; + if (!c.min_version) - return o << (c.max_open ? "< " : "<= ") << *c.max_version; + { + o << (c.max_open ? "< " : "<= "); + return print (*c.max_version); + } if (!c.max_version) - return o << (c.min_open ? "> " : ">= ") << *c.min_version; + { + o << (c.min_open ? "> " : ">= "); + return print (*c.min_version); + } if (*c.min_version == *c.max_version) - return o << "== " << *c.min_version; + { + const version& v (*c.min_version); + + if (!c.min_open && !c.max_open) + { + o << "== "; + return print (v); + } + + assert (v.empty () && (!c.min_open || !c.max_open)); + return o << (c.min_open ? "~$" : "^$"); + } // If the range can potentially be represented as a range shortcut // operator (^ or ~), having the [ ) // form, then print it using the standard version constraint code. // - if (!c.min_open && c.max_open) + if (!c.min_open && + c.max_open && + !c.min_version->empty () && + !c.max_version->empty ()) { if (optional mnv = parse_standard_version (c.min_version->string (), @@ -852,8 +1042,11 @@ namespace bpkg // Print as a range. // - return o << (c.min_open ? '(' : '[') << *c.min_version << " " - << *c.max_version << (c.max_open ? ')' : ']'); + o << (c.min_open ? '(' : '['); + print (*c.min_version); + o << ' '; + print (*c.max_version); + return o << (c.max_open ? ')' : ']'); } ostream& @@ -1258,6 +1451,7 @@ namespace bpkg parse_package_manifest (parser& p, name_value nv, bool iu, + bool cd, package_manifest_flags fl, package_manifest& m) { @@ -1323,6 +1517,11 @@ namespace bpkg return (fl & f) != package_manifest_flags::none; }; + // We will cache the depends manifest values to parse and, if requested, + // complete the dependency constraints later, after the version value is + // parsed. + // + vector dependencies; for (nv = p.next (); !nv.empty (); nv = p.next ()) { string& n (nv.name); @@ -1622,78 +1821,7 @@ namespace bpkg } else if (n == "depends") { - // Allow specifying ?* in any order. - // - size_t n (v.size ()); - size_t cond ((n > 0 && v[0] == '?') || (n > 1 && v[1] == '?') ? 1 : 0); - size_t btim ((n > 0 && v[0] == '*') || (n > 1 && v[1] == '*') ? 1 : 0); - - auto vc (parser::split_comment (v)); - - const string& vl (vc.first); - dependency_alternatives da (cond != 0, btim != 0, move (vc.second)); - - string::const_iterator b (vl.begin ()); - string::const_iterator e (vl.end ()); - - if (da.conditional || da.buildtime) - { - string::size_type p (vl.find_first_not_of (spaces, cond + btim)); - b = p == string::npos ? e : b + p; - } - - list_parser lp (b, e, '|'); - for (string lv (lp.next ()); !lv.empty (); lv = lp.next ()) - { - using iterator = string::const_iterator; - - iterator b (lv.begin ()); - iterator i (b); - iterator ne (b); // End of name. - iterator e (lv.end ()); - - // Find end of name (ne). - // - const string cb ("=<>([~^"); - for (char c; i != e && cb.find (c = *i) == string::npos; ++i) - { - if (!space (c)) - ne = i + 1; - } - - package_name nm; - - try - { - nm = package_name (i == e ? move (lv) : string (b, ne)); - } - catch (const invalid_argument& e) - { - bad_value ( - string ("invalid prerequisite package name: ") + e.what ()); - } - - if (i == e) - da.push_back (dependency {move (nm), nullopt}); - else - { - try - { - da.push_back ( - dependency {move (nm), dependency_constraint (string (i, e))}); - } - catch (const invalid_argument& e) - { - bad_value ( - string ("invalid dependency constraint: ") + e.what ()); - } - } - } - - if (da.empty ()) - bad_value ("empty package dependency specification"); - - m.dependencies.push_back (da); + dependencies.push_back (move (nv)); } else if (n == "location") { @@ -1761,6 +1889,99 @@ namespace bpkg else if (m.license_alternatives.empty ()) bad_value ("no project license specified"); + // Now, when the version manifest value is parsed, we can parse the + // dependencies and complete their constrains, if requested. + // + for (name_value& d: dependencies) + { + nv = move (d); // Restore as bad_value() uses its line/column. + + const string& v (nv.value); + + // Allow specifying ?* in any order. + // + size_t n (v.size ()); + size_t cond ((n > 0 && v[0] == '?') || (n > 1 && v[1] == '?') ? 1 : 0); + size_t btim ((n > 0 && v[0] == '*') || (n > 1 && v[1] == '*') ? 1 : 0); + + auto vc (parser::split_comment (v)); + + const string& vl (vc.first); + dependency_alternatives da (cond != 0, btim != 0, move (vc.second)); + + string::const_iterator b (vl.begin ()); + string::const_iterator e (vl.end ()); + + if (da.conditional || da.buildtime) + { + string::size_type p (vl.find_first_not_of (spaces, cond + btim)); + b = p == string::npos ? e : b + p; + } + + list_parser lp (b, e, '|'); + for (string lv (lp.next ()); !lv.empty (); lv = lp.next ()) + { + using iterator = string::const_iterator; + + iterator b (lv.begin ()); + iterator i (b); + iterator ne (b); // End of name. + iterator e (lv.end ()); + + // Find end of name (ne). + // + const string cb ("=<>([~^"); + for (char c; i != e && cb.find (c = *i) == string::npos; ++i) + { + if (!space (c)) + ne = i + 1; + } + + package_name nm; + + try + { + nm = package_name (i == e ? move (lv) : string (b, ne)); + } + catch (const invalid_argument& e) + { + bad_value ( + string ("invalid prerequisite package name: ") + e.what ()); + } + + if (i == e) + da.push_back (dependency {move (nm), nullopt}); + else + { + try + { + dependency_constraint dc (string (i, e)); + + if (!dc.complete () && + flag (package_manifest_flags::forbid_incomplete_depends)) + bad_value ("$ not allowed"); + + // Complete the constraint. + // + if (cd) + dc = dc.effective (m.version); + + da.push_back (dependency {move (nm), move (dc)}); + } + catch (const invalid_argument& e) + { + bad_value ( + string ("invalid dependency constraint: ") + e.what ()); + } + } + } + + if (da.empty ()) + bad_value ("empty package dependency specification"); + + m.dependencies.push_back (da); + } + if (!m.location && flag (package_manifest_flags::require_location)) bad_name ("no package location specified"); @@ -1771,20 +1992,26 @@ namespace bpkg package_manifest pkg_package_manifest (parser& p, name_value nv, bool iu) { - return package_manifest (p, - move (nv), - iu, - package_manifest_flags::forbid_file | - package_manifest_flags::require_location | - package_manifest_flags::forbid_fragment); + return package_manifest ( + p, + move (nv), + iu, + false /* complete_depends */, + package_manifest_flags::forbid_file | + package_manifest_flags::require_location | + package_manifest_flags::forbid_fragment | + package_manifest_flags::forbid_incomplete_depends); } // package_manifest // package_manifest:: - package_manifest (manifest_parser& p, bool iu, package_manifest_flags fl) + package_manifest (manifest_parser& p, + bool iu, + bool cd, + package_manifest_flags fl) { - parse_package_manifest (p, p.next (), iu, fl, *this); + parse_package_manifest (p, p.next (), iu, cd, fl, *this); // Make sure this is the end. // @@ -1798,9 +2025,10 @@ namespace bpkg package_manifest (manifest_parser& p, name_value nv, bool iu, + bool cd, package_manifest_flags fl) { - parse_package_manifest (p, move (nv), iu, fl, *this); + parse_package_manifest (p, move (nv), iu, cd, fl, *this); } static const string description_file ("description-file"); diff --git a/libbpkg/manifest.hxx b/libbpkg/manifest.hxx index 3d8f5f5..b7216fc 100644 --- a/libbpkg/manifest.hxx +++ b/libbpkg/manifest.hxx @@ -35,7 +35,8 @@ namespace bpkg { public: // Let's keep the members in the order they appear in the string - // representation. + // representation. We also make them const to make sure things stay + // consistent. // const std::uint16_t epoch; const std::string upstream; @@ -279,6 +280,17 @@ namespace bpkg // depends // + // Represented as a version range. Note that the versions may refer to the + // dependent package version and can be completed with the actual versions + // using the effective() function. Such versions are represented as an empty + // version object and have the dollar character string representation. + // + // If the version endpoints are equal and both are closed, then this is the + // `== ` constraint (in particular, `== $` if empty endpoints). If + // both endpoints are empty and one of them is open, then this is either + // `~$` (min endpoint is open) or `^$` (max endpoint is open). Note that + // equal endpoints can never be both open. + // class LIBBPKG_EXPORT dependency_constraint { public: @@ -299,6 +311,21 @@ namespace bpkg bool empty () const noexcept {return !min_version && !max_version;} + + bool + complete () const noexcept + { + return (!min_version || !min_version->empty ()) && + (!max_version || !max_version->empty ()); + } + + // Return the completed constraint if it refers to the dependent package + // version and copy of itself otherwise. Throw std::invalid_argument if + // the resulting constraint is invalid (max version is less than min + // version in range, non-standard version for a shortcut operator, etc.). + // + dependency_constraint + effective (version) const; }; LIBBPKG_EXPORT std::ostream& @@ -382,7 +409,7 @@ namespace bpkg comment (std::move (c)) {} }; - // Package manifest value forbid/require flags. + // Package manifest value validation forbid/require flags. // // Some package manifest values can be forbidden or required for certain // repository types and in specific contexts (for example, when parsing an @@ -393,15 +420,16 @@ namespace bpkg // enum class package_manifest_flags: std::uint16_t { - none = 0x0, + none = 0x00, - forbid_file = 0x1, // Forbid *-file manifest values. - forbid_location = 0x2, - forbid_sha256sum = 0x4, - forbid_fragment = 0x8, + forbid_file = 0x01, // Forbid *-file manifest values. + forbid_location = 0x02, + forbid_sha256sum = 0x04, + forbid_fragment = 0x08, + forbid_incomplete_depends = 0x10, - require_location = 0x10, - require_sha256sum = 0x20 + require_location = 0x20, + require_sha256sum = 0x40 }; inline package_manifest_flags @@ -616,6 +644,7 @@ namespace bpkg // package_manifest (butl::manifest_parser&, bool ignore_unknown = false, + bool complete_depends = true, package_manifest_flags = package_manifest_flags::forbid_location | package_manifest_flags::forbid_sha256sum | @@ -626,6 +655,7 @@ namespace bpkg package_manifest (butl::manifest_parser&, butl::manifest_name_value start, bool ignore_unknown, + bool complete_depends, package_manifest_flags); void @@ -654,9 +684,11 @@ namespace bpkg // Create individual package manifest. // inline package_manifest - pkg_package_manifest (butl::manifest_parser& p, bool ignore_unknown = false) + pkg_package_manifest (butl::manifest_parser& p, + bool ignore_unknown = false, + bool complete_depends = true) { - return package_manifest (p, ignore_unknown); + return package_manifest (p, ignore_unknown, complete_depends); } LIBBPKG_EXPORT package_manifest diff --git a/tests/manifest/driver.cxx b/tests/manifest/driver.cxx index 0f2262f..de03606 100644 --- a/tests/manifest/driver.cxx +++ b/tests/manifest/driver.cxx @@ -16,10 +16,14 @@ using namespace std; using namespace butl; using namespace bpkg; -// Usage: argv[0] (-pp|-dp|-gp|-pr|-dr|-gr|-s) +// Usages: // -// Read and parse manifest from STDIN and serialize it to STDOUT. The -// following options specify the manifest type. +// argv[0] (-pp|-dp|-gp|-pr|-dr|-gr|-s) +// argv[0] [-c] -p +// argv[0] -ec +// +// In the first form read and parse manifest list from stdin and serialize it +// to stdout. The following options specify the manifest type. // // -pp parse pkg package manifest list // -dp parse dir package manifest list @@ -29,13 +33,30 @@ using namespace bpkg; // -gr parse git repository manifest list // -s parse signature manifest // +// In the second form read and parse the package manifest from stdin and +// serialize it to stdout. Complete the dependency constraints if -c is +// specified. Note: -c, if specified, should go before -p on the command line. +// +// In the third form read and parse dependency constraints from stdin and +// roundtrip them to stdout together with their effective constraints, +// calculated using version passed as an argument. +// int main (int argc, char* argv[]) { - assert (argc == 2); + assert (argc <= 3); string opt (argv[1]); - cin.exceptions (ios_base::failbit | ios_base::badbit); + bool complete_depends (opt == "-c"); + + if (complete_depends) + { + opt = argv[2]; + assert (opt == "-p"); + } + + assert ((opt == "-ec" || complete_depends) == (argc == 3)); + cout.exceptions (ios_base::failbit | ios_base::badbit); manifest_parser p (cin, "stdin"); @@ -43,22 +64,48 @@ main (int argc, char* argv[]) try { - if (opt == "-pp") - pkg_package_manifests (p).serialize (s); - else if (opt == "-dp") - dir_package_manifests (p).serialize (s); - else if (opt == "-gp") - git_package_manifests (p).serialize (s); - else if (opt == "-pr") - pkg_repository_manifests (p).serialize (s); - else if (opt == "-dr") - dir_repository_manifests (p).serialize (s); - else if (opt == "-gr") - git_repository_manifests (p).serialize (s); - else if (opt == "-s") - signature_manifest (p).serialize (s); + if (opt == "-ec") + { + version v (argv[2]); + + cin.exceptions (ios_base::badbit); + + string s; + while (!eof (getline (cin, s))) + { + dependency_constraint c (s); + dependency_constraint ec (c.effective (v)); + + assert (c.complete () == (c == ec)); + + cout << c << " " << ec << endl; + } + } else - assert (false); + { + cin.exceptions (ios_base::failbit | ios_base::badbit); + + if (opt == "-p") + pkg_package_manifest (p, + false /* ignore_unknown */, + complete_depends).serialize (s); + else if (opt == "-pp") + pkg_package_manifests (p).serialize (s); + else if (opt == "-dp") + dir_package_manifests (p).serialize (s); + else if (opt == "-gp") + git_package_manifests (p).serialize (s); + else if (opt == "-pr") + pkg_repository_manifests (p).serialize (s); + else if (opt == "-dr") + dir_repository_manifests (p).serialize (s); + else if (opt == "-gr") + git_repository_manifests (p).serialize (s); + else if (opt == "-s") + signature_manifest (p).serialize (s); + else + assert (false); + } } catch (const manifest_parsing& e) { diff --git a/tests/manifest/testscript b/tests/manifest/testscript index b77740f..fa0de85 100644 --- a/tests/manifest/testscript +++ b/tests/manifest/testscript @@ -74,13 +74,40 @@ : dependency : - $* -pp <'stdin:5:10: error: invalid prerequisite package name: length is less than two characters' != 0 + $* -pp <'stdin:8:10: error: invalid prerequisite package name: length is less than two characters' != 0 : 1 sha256sum: a2b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 : name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 depends: b EOI + + : dependency-constraint + : + $* -pp <'stdin:8:10: error: $ not allowed' != 0 + : 1 + sha256sum: a2b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + : + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar == $ + EOI + + : dependency-constraint-version + : + $* -c -p <'stdin:6:10: error: invalid dependency constraint: min version is greater than max version' != 0 + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar [$ 1.0.0] + EOI } } @@ -89,6 +116,39 @@ { : manifest : + { + : complete + : + $* -c -p <>EOO + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar == $ | libbaz ~$ | libbox ^$ | libfox [1.0 $) + EOI + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar == 2.0.0 | libbaz ~2.0.0 | libbox ^2.0.0 | libfox [1.0 2.0.0) + EOO + + : incomplete + : + $* -p <>EOF + : 1 + name: foo + version: 2.0.0 + summary: Modern C++ parser + license: LGPLv2 + depends: bar == $ | libbaz ~$ | libbox ^$ | libfox [1.0 $) + EOF + } + + : manifest-list + : : Roundtrip the pkg package manifest list. : $* -pp <>EOF @@ -515,3 +575,75 @@ \ EOF } + +: effective-constraints +: +{ + : regular + : + $* -ec '1.2.3+1' <>EOO + [1.0 $) + [1.0 $] + [$ 1.2.4) + [$ 1.2.4] + == $ + >= $ + EOI + [1.0 $) [1.0 1.2.3) + [1.0 $] [1.0 1.2.3] + [$ 1.2.4) [1.2.3 1.2.4) + [$ 1.2.4] [1.2.3 1.2.4] + == $ == 1.2.3 + >= $ >= 1.2.3 + EOO + + : shortcut + : + { + : final + : + { + $* -ec '1.2.3+1' <'~$' >'~$ ~1.2.0' : tilda + $* -ec '1.2.3+1' <'^$' >'^$ ^1.0.0' : carrot + } + + : pre-release + : + { + : tilda + : + { + $* -ec '1.2.0-b.2' <'~$' >'~$ ~1.2.0-a.1' : no-final + $* -ec '1.2.1-a.1' <'~$' >'~$ ~1.2.0' : final-patch + } + + : carrot + : + { + $* -ec '1.0.0-b.2' <'^$' >'^$ ^1.0.0-a.1' : no-final + $* -ec '1.0.1-a.1' <'^$' >'^$ ^1.0.0' : final-patch + $* -ec '1.1.0-b.2' <'^$' >'^$ ^1.0.0' : final-minor + } + } + + : snapshot + : + { + : tilda + : + { + $* -ec '1.2.1-a.2.345' <'~$' >'~$ ~1.2.0' : patch + $* -ec '1.2.0-a.0.345' <'~$' >'~$ [1.2.0-a.0.1 1.2.0-a.1)' : minor + $* -ec '1.0.0-a.0.345' <'~$' >'~$ [1.0.0-a.0.1 1.0.0-a.1)' : major + } + + : carrot + : + { + $* -ec '1.2.1-a.2.345' <'^$' >'^$ ^1.0.0' : patch + $* -ec '1.2.0-a.0.345' <'^$' >'^$ [1.2.0-a.0.1 1.2.0-a.1)' : minor + $* -ec '1.0.0-a.0.345' <'^$' >'^$ [1.0.0-a.0.1 1.0.0-a.1)' : major + } + } + } +} -- cgit v1.1