From 1f45506b373eeaf88c66137e7a5870ad9ca9901a Mon Sep 17 00:00:00 2001 From: Proddy Date: Sun, 25 Dec 2022 13:03:09 +0100 Subject: [PATCH] add semver lib --- lib/semver/LICENSE | 22 ++++ lib/semver/README.md | 7 + lib/semver/Semver200_comparator.cpp | 121 +++++++++++++++++ lib/semver/Semver200_parser.cpp | 194 ++++++++++++++++++++++++++++ lib/semver/semver200.h | 53 ++++++++ lib/semver/version.h | 149 +++++++++++++++++++++ lib/semver/version.inl | 132 +++++++++++++++++++ 7 files changed, 678 insertions(+) create mode 100644 lib/semver/LICENSE create mode 100644 lib/semver/README.md create mode 100644 lib/semver/Semver200_comparator.cpp create mode 100644 lib/semver/Semver200_parser.cpp create mode 100644 lib/semver/semver200.h create mode 100644 lib/semver/version.h create mode 100644 lib/semver/version.inl diff --git a/lib/semver/LICENSE b/lib/semver/LICENSE new file mode 100644 index 000000000..53234651b --- /dev/null +++ b/lib/semver/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Marko Živanović + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/lib/semver/README.md b/lib/semver/README.md new file mode 100644 index 000000000..82c86d7d3 --- /dev/null +++ b/lib/semver/README.md @@ -0,0 +1,7 @@ +# About + +This project is MIT-licensed, C++14 implementation of [semantic versioning](http://semver.org) parser and comparator with support for modifying parsed version strings. Semantic versioning 2.0.0 specification is supported out-of-the-box and the code should be flexible-enough to support future revisions or other similar versioning schemes. + +Copyright (c) 2015 Marko Zivanovic + +Based on https://github.com/zmarko/semver diff --git a/lib/semver/Semver200_comparator.cpp b/lib/semver/Semver200_comparator.cpp new file mode 100644 index 000000000..1430bd14b --- /dev/null +++ b/lib/semver/Semver200_comparator.cpp @@ -0,0 +1,121 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 Marko Zivanovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include "semver200.h" + +using namespace std; + +namespace version { + +namespace { + +// Compare normal version identifiers. +int compare_normal(const Version_data & l, const Version_data & r) { + if (l.major > r.major) + return 1; + if (l.major < r.major) + return -1; + if (l.minor > r.minor) + return 1; + if (l.minor < r.minor) + return -1; + if (l.patch > r.patch) + return 1; + if (l.patch < r.patch) + return -1; + return 0; +} + +// Compare alphanumeric prerelease identifiers. +inline int cmp_alnum_prerel_ids(const string & l, const string & r) { + auto cmp = l.compare(r); + if (cmp == 0) { + return cmp; + } else { + return cmp > 0 ? 1 : -1; + } +} + +// Compare numeric prerelease identifiers. +inline int cmp_num_prerel_ids(const string & l, const string & r) { + long long li = stoll(l); + long long ri = stoll(r); + if (li == ri) + return 0; + return li > ri ? 1 : -1; +} + +using Prerel_type_pair = pair; +using Prerel_id_comparator = function; +const map comparators = {{{Id_type::alnum, Id_type::alnum}, cmp_alnum_prerel_ids}, + {{Id_type::alnum, Id_type::num}, [](const string &, const string &) { return 1; }}, + {{Id_type::num, Id_type::alnum}, [](const string &, const string &) { return -1; }}, + {{Id_type::num, Id_type::num}, cmp_num_prerel_ids}}; + +// Compare prerelease identifiers based on their types. +inline int compare_prerel_identifiers(const Prerelease_identifier & l, const Prerelease_identifier & r) { + auto cmp = comparators.at({l.second, r.second}); + return cmp(l.first, r.first); +} + +inline int cmp_rel_prerel(const Prerelease_identifiers & l, const Prerelease_identifiers & r) { + if (l.empty() && !r.empty()) + return 1; + if (r.empty() && !l.empty()) + return -1; + return 0; +} +} // namespace + +int Semver200_comparator::compare(const Version_data & l, const Version_data & r) const { + // Compare normal version components. + int cmp = compare_normal(l, r); + if (cmp != 0) + return cmp; + + // Compare if one version is release and the other prerelease - release is always higher. + cmp = cmp_rel_prerel(l.prerelease_ids, r.prerelease_ids); + if (cmp != 0) + return cmp; + + // Compare prerelease by looking at each identifier: numeric ones are compared as numbers, + // alphanum as ASCII strings. + auto shorter = min(l.prerelease_ids.size(), r.prerelease_ids.size()); + for (size_t i = 0; i < shorter; i++) { + cmp = compare_prerel_identifiers(l.prerelease_ids[i], r.prerelease_ids[i]); + if (cmp != 0) + return cmp; + } + + // Prerelease identifiers are the same, to the length of the shorter version string; + // if they are the same length, then versions are equal, otherwise, longer one wins. + if (l.prerelease_ids.size() == r.prerelease_ids.size()) + return 0; + return l.prerelease_ids.size() > r.prerelease_ids.size() ? 1 : -1; +} + +} // namespace version diff --git a/lib/semver/Semver200_parser.cpp b/lib/semver/Semver200_parser.cpp new file mode 100644 index 000000000..77bcbd5a7 --- /dev/null +++ b/lib/semver/Semver200_parser.cpp @@ -0,0 +1,194 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 Marko Zivanovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include "semver200.h" + +#include "../../src/emsesp_stub.hpp" // for logging + +#ifdef _MSC_VER +// disable symbol name too long warning +#pragma warning(disable : 4503) +#endif + +using namespace std; + +namespace version { + +namespace { +enum class Parser_state { major, minor, patch, prerelease, build }; + +using Validator = function; +using State_transition_hook = function; +/// State transition is described by a character that triggers it, a state to transition to and +/// optional hook to be invoked on transition. +using Transition = tuple; +using Transitions = vector; +using State = tuple; +using State_machine = std::map; + +// Ranges of characters allowed in prerelease and build identifiers. +const vector> allowed_prerel_id_chars = {{'0', '9'}, {'A', 'Z'}, {'a', 'z'}, {'-', '-'}}; + +inline Transition mkx(const char c, Parser_state p, State_transition_hook pth) { + return make_tuple(c, p, pth); +} + +inline void Parse_error(const std::string & s) { + emsesp::EMSESP::logger().err("parse error: %s", s.c_str()); +} + +/// Advance parser state machine by a single step. +/** + Perform single step of parser state machine: if character matches one from transition tables - + trigger transition to next state; otherwise, validate if current token is in legal state + (throw Parse_error if not) and then add character to current token; State transition includes + preparing various vars for next state and invoking state transition hook (if specified) which is + where whole tokens are validated. + */ +inline void process_char(const char c, Parser_state & cstate, Parser_state & pstate, const Transitions & transitions, string & target, Validator validate) { + for (const auto & transition : transitions) { + if (c == get<0>(transition)) { + if (get<2>(transition)) + get<2>(transition)(target); + pstate = cstate; + cstate = get<1>(transition); + return; + } + } + validate(target, c); + target.push_back(c); +} + +/// Validate normal (major, minor, patch) version components. +inline void normal_version_validator(const string & tgt, const char c) { + if (c < '0' || c > '9') + Parse_error("invalid character encountered: " + string(1, c)); + if (tgt.compare(0, 1, "0") == 0) + Parse_error("leading 0 not allowed"); +} + +/// Validate that prerelease and build version identifiers are comprised of allowed chars only. +inline void prerelease_version_validator(const string &, const char c) { + bool res = false; + for (const auto & r : allowed_prerel_id_chars) { + res |= (c >= r.first && c <= r.second); + } + if (!res) + Parse_error("invalid character encountered: " + string(1, c)); +} + +inline bool is_identifier_numeric(const string & id) { + return id.find_first_not_of("0123456789") == string::npos; +} + +inline bool check_for_leading_0(const string & str) { + return str.length() > 1 && str[0] == '0'; +} + +/// Validate every individual prerelease identifier, determine it's type and add it to collection. +void prerelease_hook_impl(string & id, Prerelease_identifiers & prerelease) { + if (id.empty()) + Parse_error("version identifier cannot be empty"); + Id_type t = Id_type::alnum; + if (is_identifier_numeric(id)) { + t = Id_type::num; + if (check_for_leading_0(id)) { + Parse_error("numeric identifiers cannot have leading 0"); + } + } + prerelease.push_back(Prerelease_identifier(id, t)); + id.clear(); +} + +/// Validate every individual build identifier and add it to collection. +void build_hook_impl(string & id, Parser_state & pstate, Build_identifiers & build, std::string & prerelease_id, Prerelease_identifiers & prerelease) { + // process last token left from parsing prerelease data + if (pstate == Parser_state::prerelease) + prerelease_hook_impl(prerelease_id, prerelease); + if (id.empty()) + Parse_error("version identifier cannot be empty"); + build.push_back(id); + id.clear(); +} + +} // namespace + +/// Parse semver 2.0.0-compatible string to Version_data structure. +/** + Version text parser is implemented as a state machine. In each step one successive character from version + string is consumed and is either added to current token or triggers state transition. Hooks can be + injected into state transitions for validation/customization purposes. + */ +Version_data Semver200_parser::parse(const string & s) const { + string major; + string minor; + string patch; + string prerelease_id; + string build_id; + Prerelease_identifiers prerelease; + Build_identifiers build; + Parser_state cstate{Parser_state::major}; + Parser_state pstate; + + auto prerelease_hook = [&](string & id) { prerelease_hook_impl(id, prerelease); }; + + auto build_hook = [&](string & id) { build_hook_impl(id, pstate, build, prerelease_id, prerelease); }; + + // State transition tables + auto major_trans = {mkx('.', Parser_state::minor, {})}; + auto minor_trans = {mkx('.', Parser_state::patch, {})}; + auto patch_trans = {mkx('-', Parser_state::prerelease, {}), mkx('+', Parser_state::build, {})}; + auto prerelease_trans = {// When identifier separator (.) is found, stay in the same state but invoke hook + // in order to process each individual identifier separately. + mkx('.', Parser_state::prerelease, prerelease_hook), + mkx('+', Parser_state::build, {})}; + auto build_trans = {// Same stay-in-the-same-state-but-invoke-hook trick from above. + mkx('.', Parser_state::build, build_hook)}; + + State_machine state_machine = {{Parser_state::major, State{major_trans, major, normal_version_validator}}, + {Parser_state::minor, State{minor_trans, minor, normal_version_validator}}, + {Parser_state::patch, State{patch_trans, patch, normal_version_validator}}, + {Parser_state::prerelease, State{prerelease_trans, prerelease_id, prerelease_version_validator}}, + {Parser_state::build, State{build_trans, build_id, prerelease_version_validator}}}; + + // Main loop. + for (const auto & c : s) { + auto state = state_machine.at(cstate); + process_char(c, cstate, pstate, get<0>(state), get<1>(state), get<2>(state)); + } + + // Trigger appropriate hooks in order to process last token, because no state transition was + // triggered for it. + if (cstate == Parser_state::prerelease) { + prerelease_hook(prerelease_id); + } else if (cstate == Parser_state::build) { + build_hook(build_id); + } + + return Version_data{stoi(major), stoi(minor), stoi(patch), prerelease, build}; +} + +} // namespace version diff --git a/lib/semver/semver200.h b/lib/semver/semver200.h new file mode 100644 index 000000000..5c19f07ff --- /dev/null +++ b/lib/semver/semver200.h @@ -0,0 +1,53 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 Marko Zivanovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#pragma once + +#include "version.h" + +namespace version { + +/// Parse string into Version_data structure according to semantic versioning 2.0.0 rules. +struct Semver200_parser { + Version_data parse(const std::string &) const; +}; + +/// Compare Version_data to another using semantic versioning 2.0.0 rules. +struct Semver200_comparator { + int compare(const Version_data &, const Version_data &) const; +}; + +/// Concrete version class that binds all semver 2.0.0 functionality together. +class Semver200_version : public Basic_version { + public: + Semver200_version() + : Basic_version{Semver200_parser(), Semver200_comparator()} { + } + + Semver200_version(const std::string & v) + : Basic_version{v, Semver200_parser(), Semver200_comparator()} { + } +}; + +} // namespace version \ No newline at end of file diff --git a/lib/semver/version.h b/lib/semver/version.h new file mode 100644 index 000000000..517d6f435 --- /dev/null +++ b/lib/semver/version.h @@ -0,0 +1,149 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 Marko Zivanovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#pragma once + +#include +#include + +namespace version { + +/// Type of prerelease identifier: alphanumeric or numeric. +/** + Type of identifier affects comparison: alphanumeric identifiers are compared as ASCII strings, while + numeric identifiers are compared as numbers. + */ +enum class Id_type { + alnum, ///< Identifier is alphanumerical + num ///< Identifier is numeric +}; + +/// Container for prerelease identifier value and it's type. +/** + Prerelease version string consist of an optional series of dot-separated identifiers. + These identifiers can be either numerical or alphanumerical. + This structure describes one such identifier. + */ +using Prerelease_identifier = std::pair; + +/// Container for all prerelease identifiers for a given version string. +using Prerelease_identifiers = std::vector; + +/// Build identifier is arbitrary string with no special meaning with regards to version precedence. +using Build_identifier = std::string; + +/// Container for all build identifiers of a given version string. +using Build_identifiers = std::vector; + +/// Description of version broken into parts, as per semantic versioning specification. +struct Version_data { + Version_data(const int M, const int m, const int p, const Prerelease_identifiers & pr, const Build_identifiers & b) + : major{M} + , minor{m} + , patch{p} + , prerelease_ids{pr} + , build_ids{b} { + } + + int major; ///< Major version, change only on incompatible API modifications. + int minor; ///< Minor version, change on backwards-compatible API modifications. + int patch; ///< Patch version, change only on bugfixes. + + /// Optional series of prerelease identifiers. + Prerelease_identifiers prerelease_ids; + + /// Optional series of build identifiers. + Build_identifiers build_ids; +}; + +// Forward declaration required for operators' template declarations. +template +class Basic_version; + +/// Test if left-hand version operand is of lower precedence than the right-hand version. +template +bool operator<(const Basic_version &, const Basic_version &); + +/// Test if left-hand version operand if of equal precedence as the right-hand version. +template +bool operator==(const Basic_version &, const Basic_version &); + +/// Test if left-hand version and right-hand version are of different precedence. +template +bool operator!=(const Basic_version &, const Basic_version &); + +/// Test if left-hand version operand is of higher precedence than the right-hand version. +template +bool operator>(const Basic_version &, const Basic_version &); + +/// Test if left-hand version operand is of higher or equal precedence as the right-hand version. +template +bool operator>=(const Basic_version &, const Basic_version &); + +/// Test if left-hand version operand is of lower or equal precedence as the right-hand version. +template +bool operator<=(const Basic_version &, const Basic_version &); + +/// Base class for various version parsing, precedence ordering and data manipulation schemes. +/** + Basic_version class describes general version object without prescribing parsing, + validation, comparison and modification rules. These rules are implemented by supplied Parser, Comparator + and Modifier objects. + */ +template +class Basic_version { + public: + /// Construct Basic_version object using Parser object to parse default ("0.0.0") version string, Comparator for comparison and Modifier for modification. + Basic_version(Parser, Comparator); + + /// Construct Basic_version object using Parser to parse supplied version string, Comparator for comparison and Modifier for modification. + Basic_version(const std::string &, Parser, Comparator); + + /// Construct Basic_version object using supplied Version_data, Parser, Comparator and Modifier objects. + Basic_version(const Version_data &, Parser, Comparator); + + /// Construct Basic_version by copying data from another one. + Basic_version(const Basic_version &); + + /// Copy version data from another Basic_version to this one. + Basic_version & operator=(const Basic_version &); + + int major() const; ///< Get major version. + int minor() const; ///< Get minor version. + int patch() const; ///< Get patch version. + const std::string prerelease() const; ///< Get prerelease version string. + const std::string build() const; ///< Get build version string. + + friend bool operator< <>(const Basic_version &, const Basic_version &); + friend bool operator==<>(const Basic_version &, const Basic_version &); + + private: + Parser parser_; + Comparator comparator_; + Version_data ver_; +}; + +} // namespace version + +#include "version.inl" diff --git a/lib/semver/version.inl b/lib/semver/version.inl new file mode 100644 index 000000000..1ace6abdd --- /dev/null +++ b/lib/semver/version.inl @@ -0,0 +1,132 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 Marko Zivanovic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#pragma once + +#include "version.h" + +namespace version { + +namespace { + +/// Utility function to splice all vector elements to output stream, using designated separator +/// between elements and function object for getting values from vector elements. +template +std::string & splice(std::string & ss, const std::vector & vec, const std::string & sep, F read) { + if (!vec.empty()) { + for (auto it = vec.cbegin(); it < vec.cend() - 1; ++it) { + ss += read(*it) + sep; + } + ss += read(*vec.crbegin()); + } + return ss; +} + +} // namespace + +template +Basic_version::Basic_version(Parser p, Comparator c) + : parser_(p) + , comparator_(c) + , ver_(parser_.parse("0.0.0")) { +} + +template +Basic_version::Basic_version(const std::string & v, Parser p, Comparator c) + : parser_(p) + , comparator_(c) + , ver_(parser_.parse(v)) { +} + +template +Basic_version::Basic_version(const Version_data & v, Parser p, Comparator c) + : parser_(p) + , comparator_(c) + , ver_(v) { +} + +template +Basic_version::Basic_version(const Basic_version &) = default; + +template +Basic_version & Basic_version::operator=(const Basic_version &) = default; + +template +int Basic_version::major() const { + return ver_.major; +} + +template +int Basic_version::minor() const { + return ver_.minor; +} + +template +int Basic_version::patch() const { + return ver_.patch; +} + +template +const std::string Basic_version::prerelease() const { + std::string ss; + return splice(ss, ver_.prerelease_ids, ".", [](const auto & id) { return id.first; }); +} + +template +const std::string Basic_version::build() const { + std::string ss; + return splice(ss, ver_.build_ids, ".", [](const auto & id) { return id; }); +} + +template +bool operator<(const Basic_version & l, const Basic_version & r) { + return l.comparator_.compare(l.ver_, r.ver_) == -1; +} + +template +bool operator==(const Basic_version & l, const Basic_version & r) { + return l.comparator_.compare(l.ver_, r.ver_) == 0; +} + +template +inline bool operator!=(const Basic_version & l, const Basic_version & r) { + return !(l == r); +} + +template +inline bool operator>(const Basic_version & l, const Basic_version & r) { + return r < l; +} + +template +inline bool operator>=(const Basic_version & l, const Basic_version & r) { + return !(l < r); +} + +template +inline bool operator<=(const Basic_version & l, const Basic_version & r) { + return !(l > r); +} + +} // namespace version