index is looking good

- mod dependencies reworked to be much less bad and actually good now
 - mod dependencies finally check the version
 - support for dynamic versions for dependencies (<=vX.X.X, ==vX.X.X, >=vX.X.X)
 - index is now ready for implementing mod installation, then we're done with this massive sausage package
This commit is contained in:
HJfod 2022-12-09 00:28:05 +02:00
parent 28b413d636
commit 7397193e57
16 changed files with 392 additions and 182 deletions

View file

@ -113,6 +113,7 @@ elseif(EXISTS ${GEODE_PLATFORM_BIN_PATH})
else()
message(FATAL_ERROR
"No valid loader binary to link to! Install prebuilts with `geode sdk install-prebuilts`, "
"or build Geode from source and add `set(GEODE_LINK_NIGHTLY On)` to your CMakeLists.txt."
"or build Geode from source and add `set(GEODE_LINK_NIGHTLY On)` to your CMakeLists.txt "
"in the line before calling add_subdirectory for Geode."
)
endif()

View file

@ -97,12 +97,23 @@ namespace geode {
std::vector<std::string> getSources() const;
std::vector<IndexItemHandle> getItems() const;
bool isKnownItem(std::string const& id, std::optional<size_t> version) const;
IndexItemHandle getItem(std::string const& id, std::optional<size_t> version) const;
bool isKnownItem(std::string const& id, std::optional<VersionInfo> version) const;
IndexItemHandle getItem(
std::string const& id,
std::optional<VersionInfo> version
) const;
IndexItemHandle getItem(
std::string const& id,
ComparableVersionInfo version
) const;
IndexItemHandle getItem(ModInfo const& info) const;
IndexItemHandle getItem(Mod* mod) const;
bool isUpdateAvailable(IndexItemHandle item) const;
bool areUpdatesAvailable() const;
void install(IndexItemHandle item);
Result<std::vector<IndexItemHandle>> getInstallList(
IndexItemHandle item
) const;
bool hasTriedToUpdate() const;
bool isUpToDate() const;

View file

@ -63,10 +63,6 @@ namespace geode {
* Whether the mod binary is loaded or not
*/
bool m_binaryLoaded = false;
/**
* Whether the mod is loadable or not
*/
bool m_resolved = false;
/**
* Mod temp directory name
*/
@ -370,7 +366,7 @@ namespace geode {
* @returns True if the mod has unresolved
* dependencies, false if not.
*/
bool updateDependencyStates();
Result<> updateDependencies();
/**
* Get a list of all the unresolved
* dependencies this mod has

View file

@ -12,14 +12,12 @@ namespace geode {
using ModJson = nlohmann::ordered_json;
struct Dependency {
std::string m_id;
// todo: Dynamic versions (1.*.*)
VersionInfo m_version { 1, 0, 0 };
ModResolveState m_state = ModResolveState::Unloaded;
bool m_required = false;
Mod* m_mod = nullptr;
bool isUnresolved() const;
struct GEODE_DLL Dependency {
std::string id;
ComparableVersionInfo version;
bool required = false;
Mod* mod = nullptr;
bool isResolved() const;
};
struct IssuesInfo {

View file

@ -129,25 +129,6 @@ namespace geode {
}
};
/**
* Represents if a mod has been loaded &
* its dependencies resolved
*/
enum class ModResolveState {
// Mod has not been loaded at all
Unloaded,
// Mod has unresolved dependencies
Unresolved,
// Mod has all dependencies resolved,
// but is not loaded yet
Resolved,
// Mod is loaded
Loaded,
// Mod is loaded, however it is also
// disabled and therefore can't be used
Disabled,
};
constexpr std::string_view GEODE_MOD_EXTENSION = ".geode";
class Mod;

View file

@ -5,11 +5,17 @@
#include "../external/json/json.hpp"
namespace geode {
enum class VersionCompare {
LessEq,
Exact,
MoreEq,
};
/**
* Class representing version information
* @class VersionInfo
*/
class GEODE_DLL VersionInfo {
class GEODE_DLL VersionInfo final {
protected:
int m_major = 1;
int m_minor = 0;
@ -23,6 +29,7 @@ namespace geode {
m_patch = patch;
}
VersionInfo(std::string const& versionString);
static bool validate(std::string const& string);
constexpr int getMajor() const {
return m_major;
@ -40,12 +47,40 @@ namespace geode {
return std::tie(m_major, m_minor, m_patch) <=>
std::tie(other.m_major, other.m_minor, other.m_patch);
}
static bool validate(std::string const& string);
constexpr bool operator==(VersionInfo const& other) const = default;
std::string toString() const;
};
void GEODE_DLL to_json(nlohmann::json& json, VersionInfo const& info);
void GEODE_DLL from_json(nlohmann::json const& json, VersionInfo& info);
GEODE_DLL std::ostream& operator<<(std::ostream& stream, VersionInfo const& version);
std::ostream& GEODE_DLL operator<<(std::ostream& stream, VersionInfo const& version);
class GEODE_DLL ComparableVersionInfo final {
protected:
VersionInfo m_version;
VersionCompare m_compare = VersionCompare::Exact;
public:
constexpr ComparableVersionInfo() = default;
constexpr ComparableVersionInfo(
VersionInfo const& version,
VersionCompare const& compare
) : m_version(version), m_compare(compare) {}
ComparableVersionInfo(std::string const& versionString);
static bool validate(std::string const& string);
constexpr bool compare(VersionInfo const& version) const {
switch (m_compare) {
case VersionCompare::Exact: return m_version == version; break;
case VersionCompare::LessEq: return m_version <= version; break;
case VersionCompare::MoreEq: return m_version >= version; break;
}
return false;
}
std::string toString() const;
};
void GEODE_DLL to_json(nlohmann::json& json, ComparableVersionInfo const& info);
void GEODE_DLL from_json(nlohmann::json const& json, ComparableVersionInfo& info);
GEODE_DLL std::ostream& operator<<(std::ostream& stream, ComparableVersionInfo const& version);
}

View file

@ -275,4 +275,24 @@ namespace geode::utils::ranges {
}
return member(*it);
}
template <ValidConstContainer C>
struct ConstReverseWrapper {
C const& iter;
};
template <ValidConstContainer C>
auto begin(ConstReverseWrapper<C> const& c) {
return std::rbegin(c.iter);
}
template <ValidConstContainer C>
auto end(ConstReverseWrapper<C> const& c) {
return std::rend(c.iter);
}
template <ValidConstContainer C>
ConstReverseWrapper<C> reverse(C const& iter) {
return { iter };
}
}

View file

@ -454,28 +454,21 @@ std::vector<IndexItemHandle> Index::getItems() const {
bool Index::isKnownItem(
std::string const& id,
std::optional<size_t> version
std::optional<VersionInfo> version
) const {
if (m_items.count(id)) {
if (version) {
return m_items.at(id).count(version.value());
} else {
return true;
}
} else {
return false;
}
return this->getItem(id, version).get();
}
IndexItemHandle Index::getItem(
std::string const& id,
std::optional<size_t> version
std::optional<VersionInfo> version
) const {
if (m_items.count(id)) {
auto versions = m_items.at(id);
if (version) {
if (versions.count(version.value())) {
return versions.at(version.value());
auto major = version.value().getMajor();
if (versions.count(major)) {
return versions.at(major);
}
} else {
if (versions.size()) {
@ -486,12 +479,27 @@ IndexItemHandle Index::getItem(
return nullptr;
}
IndexItemHandle Index::getItem(
std::string const& id,
ComparableVersionInfo version
) const {
if (m_items.count(id)) {
// prefer most major version
for (auto& [_, item] : ranges::reverse(m_items.at(id))) {
if (version.compare(item->info.m_version)) {
return item;
}
}
}
return nullptr;
}
IndexItemHandle Index::getItem(ModInfo const& info) const {
return this->getItem(info.m_id, info.m_version.getMajor());
return this->getItem(info.m_id, info.m_version);
}
IndexItemHandle Index::getItem(Mod* mod) const {
return this->getItem(mod->getID(), mod->getVersion().getMajor());
return this->getItem(mod->getID(), mod->getVersion());
}
bool Index::isUpdateAvailable(IndexItemHandle item) const {
@ -511,3 +519,39 @@ bool Index::areUpdatesAvailable() const {
}
return false;
}
Result<std::vector<IndexItemHandle>> Index::getInstallList(
IndexItemHandle item
) const {
std::vector<IndexItemHandle> list;
for (auto& dep : item->info.m_dependencies) {
if (!dep.isResolved()) {
// check if this dep is available in the index
if (auto depItem = this->getItem(dep.id, dep.version)) {
// recursively add dependencies
GEODE_UNWRAP_INTO(auto deps, this->getInstallList(depItem));
ranges::push(list, deps);
}
// otherwise user must get this dependency manually from somewhere
// else
else {
return Err(
"Dependency {} version {} not found in the index! Likely "
"reason is that the version of the dependency this mod "
"depends on is not available. Please let the the developer "
"({}) of the mod know!",
dep.id, dep.version.toString(), item->info.m_developer
);
}
}
// if the dep is resolved, then all its dependencies must be installed
// already in order for that to have happened
}
// add this item to the end of the list
list.push_back(item);
return Ok(list);
}
void Index::install(IndexItemHandle item) {
// todo
}

View file

@ -137,7 +137,7 @@ Result<Mod*> Loader::loadModFromInfo(ModInfo const& info) {
"should-load-" + info.m_id, true
);
// this loads the mod if its dependencies are resolved
mod->updateDependencyStates();
GEODE_UNWRAP(mod->updateDependencies());
// add mod resources
this->queueInGDThread([this, mod]() {
@ -288,7 +288,7 @@ std::vector<InvalidGeodeFile> Loader::getFailedMods() const {
void Loader::updateAllDependencies() {
for (auto const& [_, mod] : m_mods) {
mod->updateDependencyStates();
(void)mod->updateDependencies();
}
}

View file

@ -192,71 +192,82 @@ bool Mod::hasSetting(std::string const& key) const {
// Loading, Toggling, Installing
Result<> Mod::loadBinary() {
if (!m_binaryLoaded) {
GEODE_UNWRAP(this->createTempDir());
if (this->hasUnresolvedDependencies()) return Err("Mod has unresolved dependencies");
GEODE_UNWRAP(this->loadPlatformBinary());
m_binaryLoaded = true;
// Call implicit entry point to place hooks etc.
m_implicitLoadFunc(this);
ModStateEvent(this, ModEventType::Loaded).post();
auto loadRes = this->loadData();
if (!loadRes) {
log::warn("Unable to load data for \"{}\": {}", m_info.m_id, loadRes.unwrapErr());
}
Loader::get()->updateAllDependencies();
GEODE_UNWRAP(this->enable());
if (m_binaryLoaded) {
return Ok();
}
GEODE_UNWRAP(this->createTempDir());
if (this->hasUnresolvedDependencies()) {
return Err("Mod has unresolved dependencies");
}
GEODE_UNWRAP(this->loadPlatformBinary());
m_binaryLoaded = true;
// Call implicit entry point to place hooks etc.
m_implicitLoadFunc(this);
ModStateEvent(this, ModEventType::Loaded).post();
auto loadRes = this->loadData();
if (!loadRes) {
log::warn("Unable to load data for \"{}\": {}", m_info.m_id, loadRes.unwrapErr());
}
Loader::get()->updateAllDependencies();
GEODE_UNWRAP(this->enable());
return Ok();
}
Result<> Mod::unloadBinary() {
if (m_binaryLoaded) {
if (!m_info.m_supportsUnloading) return Err("Mod does not support unloading");
GEODE_UNWRAP(this->saveData());
GEODE_UNWRAP(this->disable());
ModStateEvent(this, ModEventType::Unloaded).post();
// Disabling unhooks and unpatches already
for (auto const& hook : m_hooks) {
delete hook;
}
m_hooks.clear();
for (auto const& patch : m_patches) {
delete patch;
}
m_patches.clear();
GEODE_UNWRAP(this->unloadPlatformBinary());
m_binaryLoaded = false;
Loader::get()->updateAllDependencies();
if (!m_binaryLoaded) {
return Ok();
}
if (!m_info.m_supportsUnloading) {
return Err("Mod does not support unloading");
}
GEODE_UNWRAP(this->saveData());
GEODE_UNWRAP(this->disable());
ModStateEvent(this, ModEventType::Unloaded).post();
// Disabling unhooks and unpatches already
for (auto const& hook : m_hooks) {
delete hook;
}
m_hooks.clear();
for (auto const& patch : m_patches) {
delete patch;
}
m_patches.clear();
GEODE_UNWRAP(this->unloadPlatformBinary());
m_binaryLoaded = false;
Loader::get()->updateAllDependencies();
return Ok();
}
Result<> Mod::enable() {
if (!m_binaryLoaded) return this->loadBinary();
if (!m_binaryLoaded) {
return this->loadBinary();
}
for (auto const& hook : m_hooks) {
GEODE_UNWRAP(this->enableHook(hook));
}
for (auto const& patch : m_patches) {
if (!patch->apply())
if (!patch->apply()) {
return Err("Unable to apply patch at " + std::to_string(patch->getAddress()));
}
}
ModStateEvent(this, ModEventType::Enabled).post();
@ -266,21 +277,25 @@ Result<> Mod::enable() {
}
Result<> Mod::disable() {
if (m_enabled) {
if (!m_info.m_supportsDisabling) return Err("Mod does not support disabling");
ModStateEvent(this, ModEventType::Disabled).post();
for (auto const& hook : m_hooks) {
GEODE_UNWRAP(this->disableHook(hook));
}
for (auto const& patch : m_patches) {
if (!patch->restore())
return Err("Unable to restore patch at " + std::to_string(patch->getAddress()));
}
m_enabled = false;
if (!m_enabled) {
return Ok();
}
if (!m_info.m_supportsDisabling) {
return Err("Mod does not support disabling");
}
ModStateEvent(this, ModEventType::Disabled).post();
for (auto const& hook : m_hooks) {
GEODE_UNWRAP(this->disableHook(hook));
}
for (auto const& patch : m_patches) {
if (!patch->restore()) {
return Err("Unable to restore patch at " + std::to_string(patch->getAddress()));
}
}
m_enabled = false;
return Ok();
}
@ -309,73 +324,59 @@ bool Mod::isUninstalled() const {
// Dependencies
bool Dependency::isUnresolved() const {
return m_required &&
(m_state == ModResolveState::Unloaded || m_state == ModResolveState::Unresolved ||
m_state == ModResolveState::Disabled);
}
bool Mod::updateDependencyStates() {
Result<> Mod::updateDependencies() {
bool hasUnresolved = false;
for (auto& dep : m_info.m_dependencies) {
if (!dep.m_mod) dep.m_mod = Loader::get()->getLoadedMod(dep.m_id);
if (dep.m_mod) {
dep.m_mod->updateDependencyStates();
if (dep.m_mod->hasUnresolvedDependencies()) {
dep.m_state = ModResolveState::Unresolved;
// set the dependency's loaded mod if such exists
if (!dep.mod) {
dep.mod = Loader::get()->getLoadedMod(dep.id);
// verify loaded dependency version
if (dep.mod && !dep.version.compare(dep.mod->getVersion())) {
dep.mod = nullptr;
}
else {
if (!dep.m_mod->m_resolved) {
dep.m_mod->m_resolved = true;
dep.m_state = ModResolveState::Resolved;
auto r = dep.m_mod->loadBinary();
if (!r) {
dep.m_state = ModResolveState::Unloaded;
log::log(Severity::Error, dep.m_mod, "{}", r.unwrapErr());
}
}
else {
if (dep.m_mod->isEnabled()) {
dep.m_state = ModResolveState::Loaded;
}
else {
dep.m_state = ModResolveState::Disabled;
}
}
// check if the dependency is loaded
if (dep.mod) {
// update the dependency recursively
GEODE_UNWRAP(dep.mod->updateDependencies());
// enable mod if it's resolved & enabled
if (!dep.mod->hasUnresolvedDependencies()) {
if (dep.mod->isEnabled()) {
GEODE_UNWRAP(dep.mod->loadBinary()
.expect("Unable to load dependency: {error}")
);
}
}
}
else {
dep.m_state = ModResolveState::Unloaded;
}
if (dep.isUnresolved()) {
m_resolved = false;
(void)this->unloadBinary();
// check if the dependency is resolved now
if (!dep.isResolved()) {
GEODE_UNWRAP(this->unloadBinary()
.expect("Unable to unload mod: {error}")
);
hasUnresolved = true;
}
}
if (!hasUnresolved && !m_resolved) {
// load if there weren't any unresolved dependencies
if (!hasUnresolved) {
log::debug("All dependencies for {} found", m_info.m_id);
m_resolved = true;
if (m_enabled) {
log::debug("Resolved & loading {}", m_info.m_id);
auto r = this->loadBinary();
if (!r) {
log::error("{} Error loading: {}", this, r.unwrapErr());
}
GEODE_UNWRAP(this->loadBinary());
}
else {
log::debug("Resolved {}, however not loading it as it is disabled", m_info.m_id);
}
}
return hasUnresolved;
return Ok();
}
bool Mod::hasUnresolvedDependencies() const {
for (auto const& dep : m_info.m_dependencies) {
if (dep.isUnresolved()) return true;
if (!dep.isResolved()) {
return true;
}
}
return false;
}
@ -383,15 +384,20 @@ bool Mod::hasUnresolvedDependencies() const {
std::vector<Dependency> Mod::getUnresolvedDependencies() {
std::vector<Dependency> unresolved;
for (auto const& dep : m_info.m_dependencies) {
if (dep.isUnresolved()) unresolved.push_back(dep);
if (!dep.isResolved()) {
unresolved.push_back(dep);
}
}
return unresolved;
}
bool Mod::depends(std::string const& id) const {
return utils::ranges::contains(m_info.m_dependencies, [id](Dependency const& t) {
return t.m_id == id;
});
return utils::ranges::contains(
m_info.m_dependencies,
[id](Dependency const& t) {
return t.id == id;
}
);
}
// Hooks

View file

@ -6,6 +6,17 @@
USE_GEODE_NAMESPACE();
bool Dependency::isResolved() const {
return
!this->required ||
(
this->mod &&
this->mod->isLoaded() &&
this->mod->isEnabled() &&
this->version.compare(this->mod->getVersion())
);
}
static std::string sanitizeDetailsData(std::string const& str) {
// delete CRLF
return utils::string::replace(str, "\r", "");
@ -37,7 +48,7 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
using nlohmann::detail::value_t;
root.needs("id").validate(&ModInfo::validateID).into(info.m_id);
root.needs("version").validate(&VersionInfo::validate).intoAs<std::string>(info.m_version);
root.needs("version").validate(&VersionInfo::validate).into(info.m_version);
root.needs("name").into(info.m_name);
root.needs("developer").into(info.m_developer);
root.has("description").into(info.m_description);
@ -50,9 +61,11 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
auto obj = dep.obj();
auto depobj = Dependency {};
obj.needs("id").validate(&ModInfo::validateID).into(depobj.m_id);
obj.needs("version").validate(&VersionInfo::validate).intoAs<std::string>(depobj.m_version);
obj.has("required").into(depobj.m_required);
obj.needs("id").validate(&ModInfo::validateID).into(depobj.id);
obj.needs("version")
.validate(&ComparableVersionInfo::validate)
.into(depobj.version);
obj.has("required").into(depobj.required);
obj.checkUnknownKeys();
info.m_dependencies.push_back(depobj);

View file

@ -49,9 +49,7 @@ void geode::openInfoPopup(Mod* mod) {
}
void geode::openIndexPopup(Mod* mod) {
if (auto item = Index::get()->getItem(
mod->getID(), mod->getVersion().getMajor()
)) {
if (auto item = Index::get()->getItem(mod)) {
IndexItemInfoPopup::create(item, nullptr)->show();
}
}

View file

@ -514,7 +514,7 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
switch (layer->getTag()) {
case TAG_CONFIRM_UNINSTALL: {
if (btn2) {
this->uninstall();
this->doUninstall();
}
} break;
@ -537,7 +537,7 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
}
}
void LocalModInfoPopup::uninstall() {
void LocalModInfoPopup::doUninstall() {
auto res = m_mod->uninstall();
if (!res) {
return FLAlertLayer::create(
@ -584,7 +584,7 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
m_installBtnSpr->setScale(.6f);
m_installBtn = CCMenuItemSpriteExtra::create(
m_installBtnSpr, this, nullptr
m_installBtnSpr, this, menu_selector(IndexItemInfoPopup::onInstall)
);
m_installBtn->setPosition(-143.0f, 75.f);
m_buttonMenu->addChild(m_installBtn);
@ -600,6 +600,48 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
return true;
}
void IndexItemInfoPopup::onInstall(CCObject*) {
auto list = Index::get()->getInstallList(m_item);
if (!list) {
return FLAlertLayer::create(
"Unable to Install",
list.unwrapErr(),
"OK"
)->show();
}
FLAlertLayer::create(
this,
"Confirm Install",
fmt::format(
"The following mods will be installed:\n {}",
// le nest
ranges::join(
ranges::map<std::vector<std::string>>(
list.unwrap(),
[](IndexItemHandle handle) {
return fmt::format(
" - <cr>{}</c> (<cy>{}</c>)",
handle->info.m_name, handle->info.m_id
);
}
),
"\n "
)
),
"Cancel", "OK"
)->show();
}
void IndexItemInfoPopup::doInstall() {
Index::get()->install(m_item);
}
void IndexItemInfoPopup::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
if (btn2) {
this->doInstall();
}
}
CCNode* IndexItemInfoPopup::createLogo(CCSize const& size) {
return geode::createIndexItemLogo(m_item, size);
}

View file

@ -66,7 +66,7 @@ protected:
void onUninstall(CCObject*);
void onOpenConfigDir(CCObject*);
void onAdvancedSettings(CCObject*);
void uninstall();
void doUninstall();
void FLAlert_Clicked(FLAlertLayer*, bool) override;
@ -77,12 +77,17 @@ public:
static LocalModInfoPopup* create(Mod* mod, ModListLayer* list);
};
class IndexItemInfoPopup : public ModInfoPopup {
class IndexItemInfoPopup : public ModInfoPopup, public FLAlertLayerProtocol {
protected:
IndexItemHandle m_item;
bool init(IndexItemHandle item, ModListLayer* list);
void onInstall(CCObject*);
void doInstall();
void FLAlert_Clicked(FLAlertLayer*, bool) override;
CCNode* createLogo(CCSize const& size) override;
ModInfo getModInfo() const override;

View file

@ -162,7 +162,7 @@ void ModCell::onUnresolvedInfo(CCObject*) {
for (auto const& dep : m_mod->getUnresolvedDependencies()) {
info += fmt::format(
"<cg>{}</c> (<cy>{}</c>), ",
dep.m_id, dep.m_version.toString()
dep.id, dep.version.toString()
);
}
info.pop_back();

View file

@ -13,6 +13,8 @@ USE_GEODE_NAMESPACE();
#define GEODE_SSCANF sscanf
#endif
// VersionInfo
bool VersionInfo::validate(std::string const& string) {
int buf0, buf1, buf2;
if (GEODE_SSCANF(string.c_str(), "v%d.%d.%d", &buf0, &buf1, &buf2)) return true;
@ -34,7 +36,65 @@ void geode::to_json(nlohmann::json& json, VersionInfo const& info) {
json = info.toString();
}
std::ostream& geode::operator<<(std::ostream& stream, VersionInfo const& version) {
stream << version.toString();
return stream;
void geode::from_json(nlohmann::json const& json, VersionInfo& info) {
info = VersionInfo(json.template get<std::string>());
}
std::ostream& geode::operator<<(std::ostream& stream, VersionInfo const& version) {
return stream << version.toString();
}
// ComparableVersionInfo
ComparableVersionInfo::ComparableVersionInfo(std::string const& rawStr) {
auto version = rawStr;
if (version.starts_with("<=")) {
m_compare = VersionCompare::LessEq;
version.erase(0, 2);
}
else if (version.starts_with(">=")) {
m_compare = VersionCompare::MoreEq;
version.erase(0, 2);
}
else if (version.starts_with("==")) {
m_compare = VersionCompare::Exact;
version.erase(0, 2);
}
m_version = VersionInfo(version);
}
bool ComparableVersionInfo::validate(std::string const& rawStr) {
auto version = rawStr;
// remove prefix
if (
version.starts_with("<=") ||
version.starts_with(">=") ||
version.starts_with("==")
) {
version.erase(0, 2);
}
// otherwise there's no prefix or it's invalid
return VersionInfo::validate(version);
}
std::string ComparableVersionInfo::toString() const {
std::string prefix = "";
switch (m_compare) {
case VersionCompare::Exact: prefix = "=="; break;
case VersionCompare::LessEq: prefix = "<="; break;
case VersionCompare::MoreEq: prefix = ">="; break;
}
return prefix + m_version.toString();
}
void geode::to_json(nlohmann::json& json, ComparableVersionInfo const& info) {
json = info.toString();
}
void geode::from_json(nlohmann::json const& json, ComparableVersionInfo& info) {
info = ComparableVersionInfo(json.template get<std::string>());
}
std::ostream& geode::operator<<(std::ostream& stream, ComparableVersionInfo const& version) {
return stream << version.toString();
}