version.hpp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. // Copyright (c) 2023, QuantStack and Mamba Contributors
  2. //
  3. // Distributed under the terms of the BSD 3-Clause License.
  4. //
  5. // The full license is in the file LICENSE, distributed with this software.
  6. #ifndef MAMBA_SPECS_VERSION_HPP
  7. #define MAMBA_SPECS_VERSION_HPP
  8. #include <string>
  9. #include <string_view>
  10. #include <utility>
  11. #include <vector>
  12. #include <fmt/format.h>
  13. namespace mamba::specs
  14. {
  15. /**
  16. * A succession of a number and a lowercase literal.
  17. *
  18. * Comparison is done lexicographically, with the number first and the literal second.
  19. * Certain literals have special meaning:
  20. * "*" < "dev" < "_"< any other literal < "" < "post"
  21. */
  22. class VersionPartAtom
  23. {
  24. public:
  25. VersionPartAtom() noexcept = default;
  26. VersionPartAtom(std::size_t numeral) noexcept;
  27. VersionPartAtom(std::size_t numeral, std::string_view literal);
  28. // The use of a template is only meant to prevent ambiguous conversions
  29. template <typename Char>
  30. VersionPartAtom(std::size_t numeral, std::basic_string<Char>&& literal);
  31. auto numeral() const noexcept -> std::size_t;
  32. auto literal() const& noexcept -> const std::string&;
  33. auto literal() && noexcept -> std::string;
  34. auto str() const -> std::string;
  35. auto operator==(const VersionPartAtom& other) const -> bool;
  36. auto operator!=(const VersionPartAtom& other) const -> bool;
  37. auto operator<(const VersionPartAtom& other) const -> bool;
  38. auto operator<=(const VersionPartAtom& other) const -> bool;
  39. auto operator>(const VersionPartAtom& other) const -> bool;
  40. auto operator>=(const VersionPartAtom& other) const -> bool;
  41. private:
  42. // Stored in decreasing size order for performance
  43. std::string m_literal = "";
  44. std::size_t m_numeral = 0;
  45. };
  46. extern template VersionPartAtom::VersionPartAtom(std::size_t, std::string&&);
  47. /**
  48. * A sequence of VersionPartAtom meant to represent a part of a version (e.g. major, minor).
  49. *
  50. * Version parts can have a arbitrary number of atoms, such as {0, "post"} {1, "dev"}
  51. * in 0post1dev
  52. *
  53. * @see Version::parse for how this is computed from strings.
  54. * @todo Use a small vector of expected size 1 if performance ar not good enough.
  55. */
  56. using VersionPart = std::vector<VersionPartAtom>;
  57. /**
  58. * A sequence of VersionPart meant to represent all parts of a version.
  59. *
  60. * CommonVersion are composed of an aribtrary postive number parts, such as major, minor.
  61. * They are typically separated by dots, for instance the three parts in 3.0post1dev.4 are
  62. * {{3, ""}}, {{0, "post"}, {1, "dev"}}, and {{4, ""}}.
  63. *
  64. * @see Version::parse for how this is computed from strings.
  65. * @todo Use a small vector of expected size 4 if performance ar not good enough.
  66. */
  67. using CommonVersion = std::vector<VersionPart>;
  68. /**
  69. * A version according to Conda specifications.
  70. *
  71. * A verison is composed of
  72. * - A epoch number, usually 0;
  73. * - A regular version,
  74. * - An optional local.
  75. * These elements are used to lexicographicaly compare two versions.
  76. *
  77. * @see https://github.com/conda/conda/blob/main/conda/models/version.py
  78. */
  79. class Version
  80. {
  81. public:
  82. static constexpr char epoch_delim = '!';
  83. static constexpr char local_delim = '+';
  84. static constexpr char part_delim = '.';
  85. static constexpr char part_delim_alt = '-';
  86. static constexpr char part_delim_special = '_';
  87. static auto parse(std::string_view str) -> Version;
  88. Version(std::size_t epoch, CommonVersion&& version, CommonVersion&& local = {}) noexcept;
  89. auto epoch() const noexcept -> std::size_t;
  90. auto version() const noexcept -> const CommonVersion&;
  91. auto local() const noexcept -> const CommonVersion&;
  92. auto str() const -> std::string;
  93. auto operator==(const Version& other) const -> bool;
  94. auto operator!=(const Version& other) const -> bool;
  95. auto operator<(const Version& other) const -> bool;
  96. auto operator<=(const Version& other) const -> bool;
  97. auto operator>(const Version& other) const -> bool;
  98. auto operator>=(const Version& other) const -> bool;
  99. private:
  100. // Stored in decreasing size order for performance
  101. CommonVersion m_version = {};
  102. CommonVersion m_local = {};
  103. std::size_t m_epoch = 0;
  104. };
  105. }
  106. template <>
  107. struct fmt::formatter<mamba::specs::VersionPartAtom>
  108. {
  109. constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin())
  110. {
  111. // make sure that range is empty
  112. if (ctx.begin() != ctx.end() && *ctx.begin() != '}')
  113. {
  114. throw fmt::format_error("Invalid format");
  115. }
  116. return ctx.begin();
  117. }
  118. template <class FormatContext>
  119. auto format(const ::mamba::specs::VersionPartAtom atom, FormatContext& ctx)
  120. {
  121. return fmt::format_to(ctx.out(), "{}{}", atom.numeral(), atom.literal());
  122. }
  123. };
  124. template <>
  125. struct fmt::formatter<mamba::specs::Version>
  126. {
  127. constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin())
  128. {
  129. // make sure that range is empty
  130. if (ctx.begin() != ctx.end() && *ctx.begin() != '}')
  131. {
  132. throw fmt::format_error("Invalid format");
  133. }
  134. return ctx.begin();
  135. }
  136. template <class FormatContext>
  137. auto format(const ::mamba::specs::Version v, FormatContext& ctx)
  138. {
  139. auto out = ctx.out();
  140. if (v.epoch() != 0)
  141. {
  142. out = fmt::format_to(ctx.out(), "{}!", v.epoch());
  143. }
  144. auto format_version_to = [](auto l_out, const auto& version)
  145. {
  146. bool first = true;
  147. for (const auto& part : version)
  148. {
  149. if (first)
  150. {
  151. first = false;
  152. }
  153. else
  154. {
  155. l_out = fmt::format_to(l_out, ".");
  156. }
  157. for (const auto& atom : part)
  158. {
  159. l_out = fmt::format_to(l_out, "{}", atom);
  160. }
  161. }
  162. return l_out;
  163. };
  164. out = format_version_to(out, v.version());
  165. if (!v.local().empty())
  166. {
  167. out = fmt::format_to(out, "+");
  168. out = format_version_to(out, v.local());
  169. }
  170. return out;
  171. }
  172. };
  173. #endif