// Copyright (c) 2023, QuantStack and Mamba Contributors // // Distributed under the terms of the BSD 3-Clause License. // // The full license is in the file LICENSE, distributed with this software. #ifndef MAMBA_SPECS_VERSION_HPP #define MAMBA_SPECS_VERSION_HPP #include #include #include #include #include namespace mamba::specs { /** * A succession of a number and a lowercase literal. * * Comparison is done lexicographically, with the number first and the literal second. * Certain literals have special meaning: * "*" < "dev" < "_"< any other literal < "" < "post" */ class VersionPartAtom { public: VersionPartAtom() noexcept = default; VersionPartAtom(std::size_t numeral) noexcept; VersionPartAtom(std::size_t numeral, std::string_view literal); // The use of a template is only meant to prevent ambiguous conversions template VersionPartAtom(std::size_t numeral, std::basic_string&& literal); auto numeral() const noexcept -> std::size_t; auto literal() const& noexcept -> const std::string&; auto literal() && noexcept -> std::string; auto str() const -> std::string; auto operator==(const VersionPartAtom& other) const -> bool; auto operator!=(const VersionPartAtom& other) const -> bool; auto operator<(const VersionPartAtom& other) const -> bool; auto operator<=(const VersionPartAtom& other) const -> bool; auto operator>(const VersionPartAtom& other) const -> bool; auto operator>=(const VersionPartAtom& other) const -> bool; private: // Stored in decreasing size order for performance std::string m_literal = ""; std::size_t m_numeral = 0; }; extern template VersionPartAtom::VersionPartAtom(std::size_t, std::string&&); /** * A sequence of VersionPartAtom meant to represent a part of a version (e.g. major, minor). * * Version parts can have a arbitrary number of atoms, such as {0, "post"} {1, "dev"} * in 0post1dev * * @see Version::parse for how this is computed from strings. * @todo Use a small vector of expected size 1 if performance ar not good enough. */ using VersionPart = std::vector; /** * A sequence of VersionPart meant to represent all parts of a version. * * CommonVersion are composed of an aribtrary postive number parts, such as major, minor. * They are typically separated by dots, for instance the three parts in 3.0post1dev.4 are * {{3, ""}}, {{0, "post"}, {1, "dev"}}, and {{4, ""}}. * * @see Version::parse for how this is computed from strings. * @todo Use a small vector of expected size 4 if performance ar not good enough. */ using CommonVersion = std::vector; /** * A version according to Conda specifications. * * A verison is composed of * - A epoch number, usually 0; * - A regular version, * - An optional local. * These elements are used to lexicographicaly compare two versions. * * @see https://github.com/conda/conda/blob/main/conda/models/version.py */ class Version { public: static constexpr char epoch_delim = '!'; static constexpr char local_delim = '+'; static constexpr char part_delim = '.'; static constexpr char part_delim_alt = '-'; static constexpr char part_delim_special = '_'; static auto parse(std::string_view str) -> Version; Version(std::size_t epoch, CommonVersion&& version, CommonVersion&& local = {}) noexcept; auto epoch() const noexcept -> std::size_t; auto version() const noexcept -> const CommonVersion&; auto local() const noexcept -> const CommonVersion&; auto str() const -> std::string; auto operator==(const Version& other) const -> bool; auto operator!=(const Version& other) const -> bool; auto operator<(const Version& other) const -> bool; auto operator<=(const Version& other) const -> bool; auto operator>(const Version& other) const -> bool; auto operator>=(const Version& other) const -> bool; private: // Stored in decreasing size order for performance CommonVersion m_version = {}; CommonVersion m_local = {}; std::size_t m_epoch = 0; }; } template <> struct fmt::formatter { constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { // make sure that range is empty if (ctx.begin() != ctx.end() && *ctx.begin() != '}') { throw fmt::format_error("Invalid format"); } return ctx.begin(); } template auto format(const ::mamba::specs::VersionPartAtom atom, FormatContext& ctx) { return fmt::format_to(ctx.out(), "{}{}", atom.numeral(), atom.literal()); } }; template <> struct fmt::formatter { constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { // make sure that range is empty if (ctx.begin() != ctx.end() && *ctx.begin() != '}') { throw fmt::format_error("Invalid format"); } return ctx.begin(); } template auto format(const ::mamba::specs::Version v, FormatContext& ctx) { auto out = ctx.out(); if (v.epoch() != 0) { out = fmt::format_to(ctx.out(), "{}!", v.epoch()); } auto format_version_to = [](auto l_out, const auto& version) { bool first = true; for (const auto& part : version) { if (first) { first = false; } else { l_out = fmt::format_to(l_out, "."); } for (const auto& atom : part) { l_out = fmt::format_to(l_out, "{}", atom); } } return l_out; }; out = format_version_to(out, v.version()); if (!v.local().empty()) { out = fmt::format_to(out, "+"); out = format_version_to(out, v.local()); } return out; } }; #endif