|
| 1 | +#pragma once |
| 2 | + |
| 3 | +#include <compare> |
| 4 | +#include <string> |
| 5 | +#include <variant> |
| 6 | +#include <vector> |
| 7 | + |
| 8 | +#include <QFlags> |
| 9 | +#include <QString> |
| 10 | + |
| 11 | +#include "dllimport.h" |
| 12 | +#include "exceptions.h" |
| 13 | + |
| 14 | +namespace MOBase |
| 15 | +{ |
| 16 | + |
| 17 | +class InvalidVersionException : public Exception |
| 18 | +{ |
| 19 | +public: |
| 20 | + using Exception::Exception; |
| 21 | +}; |
| 22 | + |
| 23 | +// class representing a Version object |
| 24 | +// |
| 25 | +// valid versions are an "extension" of SemVer (see https://semver.org/) with the |
| 26 | +// following tweaks: |
| 27 | +// - version can have a sub-patch, i.e., x.y.z.p, which are normally not allowed by |
| 28 | +// SemVer |
| 29 | +// - non-integer pre-release identifiers are limited to dev, alpha (a), beta (b) and rc, |
| 30 | +// and dev is lower than alpha (according to SemVer, the pre-release should be |
| 31 | +// ordered alphabetically) |
| 32 | +// - the '-' between version and pre-release can be made optional, and also the '.' |
| 33 | +// between pre-releases segment |
| 34 | +// |
| 35 | +// the extension from SemVer are only meant to be used by MO2 and USVFS versioning, |
| 36 | +// plugins and extensions should follow SemVer standard (and not use dev), this is |
| 37 | +// mainly |
| 38 | +// - for back-compatibility purposes, because USVFS versioning contains sub-patches and |
| 39 | +// there are old MO2 releases with sub-patch |
| 40 | +// - because MO2 is not going to become MO3, so having an extra level make sense |
| 41 | +// |
| 42 | +// unlike VersionInfo, this class is immutable and only hold valid versions |
| 43 | +// |
| 44 | +class QDLLEXPORT Version |
| 45 | +{ |
| 46 | +public: |
| 47 | + enum class ParseMode |
| 48 | + { |
| 49 | + // official semver parsing with pre-release limited to dev, alpha/a, beta/b and rc |
| 50 | + // |
| 51 | + SemVer, |
| 52 | + |
| 53 | + // MO2 parsing, e.g., 2.5.1rc1 - this either parse a string with no pre-release |
| 54 | + // information (e.g. 2.5.1) or with a single pre-release + a version (e.g., 2.5.1a1 |
| 55 | + // or 2.5.2rc1) |
| 56 | + // |
| 57 | + // this mode can parse sub-patch (SemVer mode cannot) |
| 58 | + // |
| 59 | + MO2 |
| 60 | + }; |
| 61 | + |
| 62 | + enum class FormatMode |
| 63 | + { |
| 64 | + // show subpatch even if subpatch is 0 |
| 65 | + // |
| 66 | + ForceSubPatch = 0b0001, |
| 67 | + |
| 68 | + // do not add separators between version and pre-release (-) or between pre-release |
| 69 | + // segments (.) |
| 70 | + // |
| 71 | + NoSeparator = 0b0010, |
| 72 | + |
| 73 | + // uses short form for alpha and beta (a/b instead of alpha/beta) |
| 74 | + // |
| 75 | + ShortAlphaBeta = 0b0100, |
| 76 | + |
| 77 | + // do not add metadata even if present |
| 78 | + // |
| 79 | + NoMetadata = 0b1000 |
| 80 | + }; |
| 81 | + Q_DECLARE_FLAGS(FormatModes, FormatMode); |
| 82 | + |
| 83 | + // condensed format, no separator, short alpha/beta and no metadata |
| 84 | + // |
| 85 | + static constexpr auto FormatCondensed = FormatModes{ |
| 86 | + FormatMode::NoSeparator, FormatMode::ShortAlphaBeta, FormatMode::NoMetadata}; |
| 87 | + |
| 88 | + enum class ReleaseType |
| 89 | + { |
| 90 | + Development, // -dev |
| 91 | + Alpha, // -alpha, -a |
| 92 | + Beta, // -beta, -b |
| 93 | + ReleaseCandidate, // -rc |
| 94 | + }; |
| 95 | + using enum ReleaseType; |
| 96 | + |
| 97 | +public: // parsing |
| 98 | + // parse version from the given string, throw InvalidVersionException if the string |
| 99 | + // cannot be parsed |
| 100 | + // |
| 101 | + static Version parse(QString const& value, ParseMode mode = ParseMode::SemVer); |
| 102 | + |
| 103 | +public: // constructors |
| 104 | + Version(int major, int minor, int patch, QString metadata = {}); |
| 105 | + Version(int major, int minor, int patch, int subpatch, QString metadata = {}); |
| 106 | + |
| 107 | + Version(int major, int minor, int patch, ReleaseType type, QString metadata = {}); |
| 108 | + Version(int major, int minor, int patch, int subpatch, ReleaseType type, |
| 109 | + QString metadata = {}); |
| 110 | + |
| 111 | + Version(int major, int minor, int patch, ReleaseType type, int prerelease, |
| 112 | + QString metadata = {}); |
| 113 | + Version(int major, int minor, int patch, int subpatch, ReleaseType type, |
| 114 | + int prerelease, QString metadata = {}); |
| 115 | + |
| 116 | + Version(int major, int minor, int patch, int subpatch, |
| 117 | + std::vector<std::variant<int, ReleaseType>> prereleases, |
| 118 | + QString metadata = {}); |
| 119 | + |
| 120 | +public: // special member functions |
| 121 | + Version(const Version&) = default; |
| 122 | + Version(Version&&) = default; |
| 123 | + |
| 124 | + Version& operator=(const Version&) = default; |
| 125 | + Version& operator=(Version&&) = default; |
| 126 | + |
| 127 | +public: |
| 128 | + // check if this version corresponds to a pre-release version (dev, alpha, beta, etc.) |
| 129 | + // |
| 130 | + bool isPreRelease() const { return !m_PreReleases.empty(); } |
| 131 | + |
| 132 | + // retrieve major, minor, patch and sub-patch of this version |
| 133 | + // |
| 134 | + int major() const { return m_Major; } |
| 135 | + int minor() const { return m_Minor; } |
| 136 | + int patch() const { return m_Patch; } |
| 137 | + int subpatch() const { return m_SubPatch; } |
| 138 | + |
| 139 | + // retrieve pre-releases information for this version |
| 140 | + // |
| 141 | + const auto& preReleases() const { return m_PreReleases; } |
| 142 | + |
| 143 | + // retrieve build metadata, if any, otherwise return an empty string |
| 144 | + // |
| 145 | + const auto& buildMetadata() const { return m_BuildMetadata; } |
| 146 | + |
| 147 | + // convert this version to a string |
| 148 | + // |
| 149 | + QString string(const FormatModes& modes = {}) const; |
| 150 | + |
| 151 | +private: |
| 152 | + // major.minor.patch |
| 153 | + int m_Major, m_Minor, m_Patch, m_SubPatch; |
| 154 | + |
| 155 | + // pre-release information |
| 156 | + std::vector<std::variant<int, ReleaseType>> m_PreReleases; |
| 157 | + |
| 158 | + // metadata |
| 159 | + QString m_BuildMetadata; |
| 160 | +}; |
| 161 | + |
| 162 | +QDLLEXPORT std::strong_ordering operator<=>(const Version& lhs, const Version& rhs); |
| 163 | + |
| 164 | +inline bool operator==(const Version& lhs, const Version& rhs) |
| 165 | +{ |
| 166 | + return (lhs <=> rhs) == 0; |
| 167 | +} |
| 168 | + |
| 169 | +Q_DECLARE_OPERATORS_FOR_FLAGS(Version::FormatModes); |
| 170 | + |
| 171 | +} // namespace MOBase |
| 172 | + |
| 173 | +template <class CharT> |
| 174 | +struct std::formatter<MOBase::Version, CharT> : std::formatter<QString, CharT> |
| 175 | +{ |
| 176 | + template <class FmtContext> |
| 177 | + FmtContext::iterator format(const MOBase::Version& v, FmtContext& ctx) const |
| 178 | + { |
| 179 | + return std::formatter<QString, CharT>::format(v.string(), ctx); |
| 180 | + } |
| 181 | +}; |
0 commit comments