mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-14 19:15:05 -05:00
new settings stuff that i need to commit because main broke
This commit is contained in:
parent
677bd762de
commit
89d1a5140f
11 changed files with 1623 additions and 247 deletions
|
@ -184,7 +184,12 @@ namespace geode {
|
|||
* Mod settings
|
||||
* @note Not a map because insertion order must be preserved
|
||||
*/
|
||||
[[nodiscard]] std::vector<std::pair<std::string, Setting>> getSettings() const;
|
||||
[[nodiscard, deprecated("Use getSettingsV3")]] std::vector<std::pair<std::string, Setting>> getSettings() const;
|
||||
/**
|
||||
* Mod settings
|
||||
* @note Not a map because insertion order must be preserved
|
||||
*/
|
||||
[[nodiscard]] std::vector<std::pair<std::string, matjson::Value>> getSettingsV3() const;
|
||||
/**
|
||||
* Get the tags for this mod
|
||||
*/
|
||||
|
@ -232,7 +237,9 @@ namespace geode {
|
|||
void setDependencies(std::vector<Dependency> const& value);
|
||||
void setIncompatibilities(std::vector<Incompatibility> const& value);
|
||||
void setSpritesheets(std::vector<std::string> const& value);
|
||||
[[deprecated("This function does NOTHING")]]
|
||||
void setSettings(std::vector<std::pair<std::string, Setting>> const& value);
|
||||
void setSettings(std::vector<std::pair<std::string, matjson::Value>> const& value);
|
||||
void setTags(std::unordered_set<std::string> const& value);
|
||||
void setNeedsEarlyLoad(bool const& value);
|
||||
void setIsAPI(bool const& value);
|
||||
|
|
325
loader/include/Geode/loader/SettingV3.hpp
Normal file
325
loader/include/Geode/loader/SettingV3.hpp
Normal file
|
@ -0,0 +1,325 @@
|
|||
#pragma once
|
||||
|
||||
#include "../DefaultInclude.hpp"
|
||||
#include <optional>
|
||||
#include <cocos2d.h>
|
||||
// todo: remove this header in 4.0.0
|
||||
#include "Setting.hpp"
|
||||
|
||||
namespace geode {
|
||||
class SettingNodeV3;
|
||||
class JsonExpectedValue;
|
||||
|
||||
class GEODE_DLL SettingV3 {
|
||||
private:
|
||||
class GeodeImpl;
|
||||
std::shared_ptr<GeodeImpl> m_impl;
|
||||
|
||||
public:
|
||||
SettingV3(std::string const& key, std::string const& modID);
|
||||
virtual ~SettingV3();
|
||||
|
||||
/**
|
||||
* Get the key of this setting
|
||||
*/
|
||||
std::string getKey() const;
|
||||
/**
|
||||
* Get the mod ID this setting is for
|
||||
*/
|
||||
std::string getModID() const;
|
||||
/**
|
||||
* Get the mod this setting is for. Note that this may return null
|
||||
* while the mod is still being initialized
|
||||
*/
|
||||
Mod* getMod() const;
|
||||
|
||||
virtual Result<> parse(std::string const& modID, matjson::Value const& json) = 0;
|
||||
virtual bool load(matjson::Value const& json) = 0;
|
||||
virtual bool save(matjson::Value& json) const = 0;
|
||||
virtual SettingNodeV3* createNode(float width) = 0;
|
||||
|
||||
virtual bool isDefaultValue() const = 0;
|
||||
/**
|
||||
* Reset this setting's value back to its original value
|
||||
*/
|
||||
virtual void reset() = 0;
|
||||
|
||||
[[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]]
|
||||
virtual std::optional<Setting> convertToLegacy() const;
|
||||
[[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]]
|
||||
virtual std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const;
|
||||
|
||||
static Result<std::shared_ptr<SettingV3>> parseBuiltin(std::string const& modID, matjson::Value const& json);
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
class GEODE_DLL GeodeSettingBaseV3 : public SettingV3 {
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
protected:
|
||||
Result<> parseShared(JsonExpectedValue& json);
|
||||
|
||||
public:
|
||||
std::string getName() const;
|
||||
std::optional<std::string> getDescription() const;
|
||||
std::optional<std::string> getEnableIf() const;
|
||||
};
|
||||
}
|
||||
|
||||
class GEODE_DLL TitleSettingV3 final : public SettingV3 {
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
std::string getTitle() const;
|
||||
|
||||
Result<> parse(std::string const& modID, matjson::Value const& json) override;
|
||||
bool load(matjson::Value const& json) override;
|
||||
bool save(matjson::Value& json) const override;
|
||||
SettingNodeV3* createNode(float width) override;
|
||||
|
||||
bool isDefaultValue() const override;
|
||||
void reset() override;
|
||||
};
|
||||
|
||||
class GEODE_DLL UnresolvedCustomSettingV3 final : public SettingV3 {
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
Result<> parse(std::string const& modID, matjson::Value const& json) override;
|
||||
bool load(matjson::Value const& json) override;
|
||||
bool save(matjson::Value& json) const override;
|
||||
SettingNodeV3* createNode(float width) override;
|
||||
|
||||
bool isDefaultValue() const override;
|
||||
void reset() override;
|
||||
|
||||
std::optional<Setting> convertToLegacy() const override;
|
||||
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
|
||||
};
|
||||
|
||||
class GEODE_DLL BoolSettingV3 final : public detail::GeodeSettingBaseV3 {
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
bool getValue() const;
|
||||
void setValue(bool value);
|
||||
Result<> isValid(bool value) const;
|
||||
|
||||
bool getDefaultValue() const;
|
||||
|
||||
Result<> parse(std::string const& modID, matjson::Value const& json) override;
|
||||
bool load(matjson::Value const& json) override;
|
||||
bool save(matjson::Value& json) const override;
|
||||
SettingNodeV3* createNode(float width) override;
|
||||
|
||||
bool isDefaultValue() const override;
|
||||
void reset() override;
|
||||
|
||||
std::optional<Setting> convertToLegacy() const override;
|
||||
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
|
||||
};
|
||||
|
||||
class GEODE_DLL IntSettingV3 final : public detail::GeodeSettingBaseV3 {
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
int64_t getValue() const;
|
||||
void setValue(int64_t value);
|
||||
Result<> isValid(int64_t value) const;
|
||||
|
||||
int64_t getDefaultValue() const;
|
||||
std::optional<int64_t> getMinValue() const;
|
||||
std::optional<int64_t> getMaxValue() const;
|
||||
|
||||
bool isArrowsEnabled() const;
|
||||
bool isBigArrowsEnabled() const;
|
||||
size_t getArrowStepSize() const;
|
||||
size_t getBigArrowStepSize() const;
|
||||
bool isSliderEnabled() const;
|
||||
std::optional<int64_t> getSliderSnap() const;
|
||||
bool isInputEnabled() const;
|
||||
|
||||
Result<> parse(std::string const& modID, matjson::Value const& json) override;
|
||||
bool load(matjson::Value const& json) override;
|
||||
bool save(matjson::Value& json) const override;
|
||||
SettingNodeV3* createNode(float width) override;
|
||||
|
||||
bool isDefaultValue() const override;
|
||||
void reset() override;
|
||||
|
||||
std::optional<Setting> convertToLegacy() const override;
|
||||
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
|
||||
};
|
||||
|
||||
class GEODE_DLL FloatSettingV3 final : public detail::GeodeSettingBaseV3 {
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
double getValue() const;
|
||||
void setValue(double value);
|
||||
Result<> isValid(double value) const;
|
||||
|
||||
double getDefaultValue() const;
|
||||
std::optional<double> getMinValue() const;
|
||||
std::optional<double> getMaxValue() const;
|
||||
|
||||
bool isArrowsEnabled() const;
|
||||
bool isBigArrowsEnabled() const;
|
||||
size_t getArrowStepSize() const;
|
||||
size_t getBigArrowStepSize() const;
|
||||
bool isSliderEnabled() const;
|
||||
std::optional<double> getSliderSnap() const;
|
||||
bool isInputEnabled() const;
|
||||
|
||||
Result<> parse(std::string const& modID, matjson::Value const& json) override;
|
||||
bool load(matjson::Value const& json) override;
|
||||
bool save(matjson::Value& json) const override;
|
||||
SettingNodeV3* createNode(float width) override;
|
||||
|
||||
bool isDefaultValue() const override;
|
||||
void reset() override;
|
||||
|
||||
std::optional<Setting> convertToLegacy() const override;
|
||||
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
|
||||
};
|
||||
|
||||
class GEODE_DLL StringSettingV3 final : public detail::GeodeSettingBaseV3 {
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
std::string getValue() const;
|
||||
void setValue(std::string_view value);
|
||||
Result<> isValid(std::string_view value) const;
|
||||
|
||||
std::string getDefaultValue() const;
|
||||
|
||||
std::optional<std::string> getRegexValidator() const;
|
||||
std::optional<std::string> getAllowedCharacters() const;
|
||||
std::optional<std::vector<std::string>> getEnumOptions() const;
|
||||
|
||||
Result<> parse(std::string const& modID, matjson::Value const& json) override;
|
||||
bool load(matjson::Value const& json) override;
|
||||
bool save(matjson::Value& json) const override;
|
||||
SettingNodeV3* createNode(float width) override;
|
||||
|
||||
bool isDefaultValue() const override;
|
||||
void reset() override;
|
||||
|
||||
std::optional<Setting> convertToLegacy() const override;
|
||||
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
|
||||
};
|
||||
|
||||
class GEODE_DLL FileSettingV3 final : public detail::GeodeSettingBaseV3 {
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
std::filesystem::path getValue() const;
|
||||
void setValue(std::filesystem::path const& value);
|
||||
Result<> isValid(std::filesystem::path value) const;
|
||||
|
||||
std::filesystem::path getDefaultValue() const;
|
||||
std::optional<std::vector<utils::file::FilePickOptions::Filter>> getFilters() const;
|
||||
|
||||
Result<> parse(std::string const& modID, matjson::Value const& json) override;
|
||||
bool load(matjson::Value const& json) override;
|
||||
bool save(matjson::Value& json) const override;
|
||||
SettingNodeV3* createNode(float width) override;
|
||||
|
||||
bool isDefaultValue() const override;
|
||||
void reset() override;
|
||||
|
||||
std::optional<Setting> convertToLegacy() const override;
|
||||
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
|
||||
};
|
||||
|
||||
class GEODE_DLL Color3BSettingV3 final : public detail::GeodeSettingBaseV3 {
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
cocos2d::ccColor3B getValue() const;
|
||||
void setValue(cocos2d::ccColor3B value);
|
||||
Result<> isValid(cocos2d::ccColor3B value) const;
|
||||
|
||||
cocos2d::ccColor3B getDefaultValue() const;
|
||||
|
||||
Result<> parse(std::string const& modID, matjson::Value const& json) override;
|
||||
bool load(matjson::Value const& json) override;
|
||||
bool save(matjson::Value& json) const override;
|
||||
SettingNodeV3* createNode(float width) override;
|
||||
|
||||
bool isDefaultValue() const override;
|
||||
void reset() override;
|
||||
|
||||
std::optional<Setting> convertToLegacy() const override;
|
||||
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
|
||||
};
|
||||
|
||||
class GEODE_DLL Color4BSettingV3 final : public detail::GeodeSettingBaseV3 {
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
cocos2d::ccColor4B getValue() const;
|
||||
void setValue(cocos2d::ccColor4B value);
|
||||
Result<> isValid(cocos2d::ccColor4B value) const;
|
||||
|
||||
cocos2d::ccColor4B getDefaultValue() const;
|
||||
|
||||
Result<> parse(std::string const& modID, matjson::Value const& json) override;
|
||||
bool load(matjson::Value const& json) override;
|
||||
bool save(matjson::Value& json) const override;
|
||||
SettingNodeV3* createNode(float width) override;
|
||||
|
||||
bool isDefaultValue() const override;
|
||||
void reset() override;
|
||||
|
||||
std::optional<Setting> convertToLegacy() const override;
|
||||
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
|
||||
};
|
||||
|
||||
class GEODE_DLL SettingNodeV3 : public cocos2d::CCNode {
|
||||
protected:
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* Mark this setting as changed. This updates the UI for committing
|
||||
* the value
|
||||
*/
|
||||
void markChanged();
|
||||
|
||||
/**
|
||||
* When the setting value is committed (aka can't be undone), this
|
||||
* function will be called. This should take care of actually saving
|
||||
* the value in some sort of global manager
|
||||
*/
|
||||
virtual void onCommit() = 0;
|
||||
|
||||
void dispatchChanged();
|
||||
void dispatchCommitted();
|
||||
|
||||
public:
|
||||
virtual void commit() = 0;
|
||||
virtual bool hasUncommittedChanges() = 0;
|
||||
virtual bool hasNonDefaultValue() = 0;
|
||||
virtual void resetToDefault() = 0;
|
||||
};
|
||||
}
|
|
@ -77,7 +77,9 @@ namespace geode {
|
|||
struct JsonMaybeObject;
|
||||
struct JsonMaybeValue;
|
||||
|
||||
struct GEODE_DLL JsonMaybeSomething {
|
||||
struct GEODE_DLL
|
||||
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
|
||||
JsonMaybeSomething {
|
||||
protected:
|
||||
JsonChecker& m_checker;
|
||||
matjson::Value& m_json;
|
||||
|
@ -102,7 +104,9 @@ namespace geode {
|
|||
operator bool() const;
|
||||
};
|
||||
|
||||
struct GEODE_DLL JsonMaybeValue : public JsonMaybeSomething {
|
||||
struct GEODE_DLL
|
||||
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
|
||||
JsonMaybeValue : public JsonMaybeSomething {
|
||||
bool m_inferType = true;
|
||||
|
||||
JsonMaybeValue(
|
||||
|
@ -254,7 +258,9 @@ namespace geode {
|
|||
Iterator<std::pair<std::string, JsonMaybeValue>> items();
|
||||
};
|
||||
|
||||
struct GEODE_DLL JsonMaybeObject : JsonMaybeSomething {
|
||||
struct
|
||||
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
|
||||
GEODE_DLL JsonMaybeObject : JsonMaybeSomething {
|
||||
std::set<std::string> m_knownKeys;
|
||||
|
||||
JsonMaybeObject(
|
||||
|
@ -276,7 +282,9 @@ namespace geode {
|
|||
void checkUnknownKeys();
|
||||
};
|
||||
|
||||
struct GEODE_DLL JsonChecker {
|
||||
struct
|
||||
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
|
||||
GEODE_DLL JsonChecker {
|
||||
std::variant<std::monostate, std::string> m_result;
|
||||
matjson::Value& m_json;
|
||||
|
||||
|
@ -289,4 +297,179 @@ namespace geode {
|
|||
JsonMaybeValue root(std::string const& hierarchy);
|
||||
};
|
||||
|
||||
class GEODE_DLL JsonExpectedValue final {
|
||||
protected:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
|
||||
JsonExpectedValue();
|
||||
JsonExpectedValue(Impl* from, matjson::Value& scope, std::string_view key);
|
||||
|
||||
bool hasError() const;
|
||||
void setError(std::string_view error);
|
||||
|
||||
matjson::Value const& getJSONRef() const;
|
||||
|
||||
template <class... Args>
|
||||
void setError(fmt::format_string<Args...> error, Args&&... args) {
|
||||
this->setError(fmt::format(error, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::optional<T> tryGet() {
|
||||
if (this->hasError()) return std::nullopt;
|
||||
try {
|
||||
return this->getJSONRef().template as<T>();
|
||||
}
|
||||
catch(matjson::JsonException const& e) {
|
||||
this->setError("invalid json type: {}", e);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
public:
|
||||
JsonExpectedValue(matjson::Value const& value, std::string_view rootScopeName);
|
||||
~JsonExpectedValue();
|
||||
|
||||
JsonExpectedValue(JsonExpectedValue&&);
|
||||
JsonExpectedValue& operator=(JsonExpectedValue&&);
|
||||
JsonExpectedValue(JsonExpectedValue const&) = delete;
|
||||
JsonExpectedValue& operator=(JsonExpectedValue const&) = delete;
|
||||
|
||||
/**
|
||||
* Get a copy of the underlying raw JSON value
|
||||
*/
|
||||
matjson::Value json() const;
|
||||
/**
|
||||
* Get the key name of this JSON value. If this is an array index,
|
||||
* returns the index as a string. If this is the root object,
|
||||
* returns the root scope name.
|
||||
*/
|
||||
std::string key() const;
|
||||
|
||||
/**
|
||||
* Check the type of this JSON value. Does not set an error. If an
|
||||
* error is already set, always returns false
|
||||
*/
|
||||
bool is(matjson::Type type) const;
|
||||
bool isNull() const;
|
||||
bool isBool() const;
|
||||
bool isNumber() const;
|
||||
bool isString() const;
|
||||
bool isArray() const;
|
||||
bool isObject() const;
|
||||
/**
|
||||
* Asserts that this JSON value is of the specified type. If it is
|
||||
* not, an error is set and all subsequent operations are no-ops
|
||||
* @returns Itself
|
||||
*/
|
||||
JsonExpectedValue& assertIs(matjson::Type type);
|
||||
JsonExpectedValue& assertIsNull();
|
||||
JsonExpectedValue& assertIsBool();
|
||||
JsonExpectedValue& assertIsNumber();
|
||||
JsonExpectedValue& assertIsString();
|
||||
JsonExpectedValue& assertIsArray();
|
||||
JsonExpectedValue& assertIsObject();
|
||||
/**
|
||||
* Asserts that this JSON value is one of a list of specified types
|
||||
* @returns Itself
|
||||
*/
|
||||
JsonExpectedValue& assertIs(std::initializer_list<matjson::Type> type);
|
||||
|
||||
// -- Dealing with values --
|
||||
|
||||
template <class T>
|
||||
T get() {
|
||||
if (auto v = this->template tryGet<T>()) {
|
||||
return *std::move(v);
|
||||
}
|
||||
return T();
|
||||
}
|
||||
template <class T>
|
||||
JsonExpectedValue& into(T& value) {
|
||||
if (auto v = this->template tryGet<T>()) {
|
||||
value = *std::move(v);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
template <class T>
|
||||
JsonExpectedValue& into(std::optional<T>& value) {
|
||||
if (auto v = this->template tryGet<T>()) {
|
||||
value.emplace(*std::move(v));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
template <class T>
|
||||
JsonExpectedValue& mustBe(std::string_view name, auto predicate) requires requires {
|
||||
{ predicate(std::declval<T>()) } -> std::convertible_to<bool>;
|
||||
} {
|
||||
if (this->hasError()) return *this;
|
||||
if (auto v = this->template tryGet<T>()) {
|
||||
if (!predicate(v)) {
|
||||
this->setError("json value is not {}", name);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// -- Dealing with objects --
|
||||
|
||||
/**
|
||||
* Check if this object has an optional key. Asserts that this JSON
|
||||
* value is an object. If the key doesn't exist, returns a
|
||||
* `JsonExpectValue` that does nothing
|
||||
* @returns The key, which is a no-op value if it didn't exist
|
||||
*/
|
||||
JsonExpectedValue has(std::string_view key);
|
||||
/**
|
||||
* Check if this object has an optional key. Asserts that this JSON
|
||||
* value is an object. If the key doesn't exist, sets an error and
|
||||
* returns a `JsonExpectValue` that does nothing
|
||||
* @returns The key, which is a no-op value if it didn't exist
|
||||
*/
|
||||
JsonExpectedValue needs(std::string_view key);
|
||||
/**
|
||||
* Asserts that this JSON value is an object. Get all object
|
||||
* properties
|
||||
*/
|
||||
std::vector<std::pair<std::string, JsonExpectedValue>> properties();
|
||||
/**
|
||||
* Asserts that this JSON value is an object. Logs unknown keys to
|
||||
* the console as warnings
|
||||
*/
|
||||
void checkUnknownKeys();
|
||||
|
||||
// -- Dealing with arrays --
|
||||
|
||||
/**
|
||||
* Asserts that this JSON value is an array. Returns the length of
|
||||
* the array, or 0 on error
|
||||
*/
|
||||
size_t length();
|
||||
/**
|
||||
* Asserts that this JSON value is an array. Returns the value at
|
||||
* the specified index. If there is no value at that index, sets an
|
||||
* error
|
||||
*/
|
||||
JsonExpectedValue at(size_t index);
|
||||
/**
|
||||
* Asserts that this JSON value is an array. Returns the array items
|
||||
* @warning The old JsonChecker used `items` for iterating object
|
||||
* properties - on this new API that function is called `properties`!
|
||||
*/
|
||||
std::vector<JsonExpectedValue> items();
|
||||
|
||||
operator bool() const;
|
||||
|
||||
Result<> ok();
|
||||
template <class T>
|
||||
Result<T> ok(T&& value) {
|
||||
auto ok = this->ok();
|
||||
if (!ok) {
|
||||
return Err(ok.unwrapErr());
|
||||
}
|
||||
return Ok(std::move(value));
|
||||
}
|
||||
};
|
||||
GEODE_DLL JsonExpectedValue checkJson(matjson::Value const& json, std::string_view rootScopeName);
|
||||
}
|
||||
|
|
|
@ -341,9 +341,10 @@ std::optional<Setting> Mod::Impl::getSettingDefinition(std::string_view const ke
|
|||
|
||||
SettingValue* Mod::Impl::getSetting(std::string_view const key) const {
|
||||
auto keystr = std::string(key);
|
||||
|
||||
if (m_settings.count(keystr)) {
|
||||
return m_settings.at(keystr).get();
|
||||
if (auto value = m_settings.at(keystr)->convertToLegacyValue()) {
|
||||
return value->get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "ModPatch.hpp"
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <string_view>
|
||||
#include "ModSettingsManager.hpp"
|
||||
|
||||
namespace geode {
|
||||
class Mod::Impl {
|
||||
|
@ -48,9 +49,9 @@ namespace geode {
|
|||
*/
|
||||
matjson::Value m_saved = matjson::Object();
|
||||
/**
|
||||
* Setting values
|
||||
* Setting values. This is behind unique_ptr for interior mutability
|
||||
*/
|
||||
std::unordered_map<std::string, std::unique_ptr<SettingValue>> m_settings;
|
||||
std::unique_ptr<ModSettingsManager> m_settings = std::make_unique<ModSettingsManager>();
|
||||
/**
|
||||
* Settings save data. Stored for efficient loading of custom settings
|
||||
*/
|
||||
|
|
|
@ -125,45 +125,28 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
|
|||
}
|
||||
catch (...) { }
|
||||
|
||||
JsonChecker checker(impl->m_rawJSON);
|
||||
auto root = checker.root(checkerRoot).obj();
|
||||
|
||||
auto root = checkJson(impl->m_rawJSON, checkerRoot);
|
||||
root.needs("geode").into(impl->m_geodeVersion);
|
||||
root.addKnownKey("gd");
|
||||
|
||||
// Check GD version
|
||||
// (use rawJson because i dont like JsonMaybeValue)
|
||||
if (rawJson.contains("gd")) {
|
||||
std::string ver;
|
||||
if (rawJson["gd"].is_object()) {
|
||||
auto key = PlatformID::toShortString(GEODE_PLATFORM_TARGET, true);
|
||||
if (rawJson["gd"].contains(key) && rawJson["gd"][key].is_string())
|
||||
ver = rawJson["gd"][key].as_string();
|
||||
} else if (rawJson["gd"].is_string()) {
|
||||
|
||||
if (auto gd = root.needs("gd")) {
|
||||
// In the future when we get rid of support for string format just
|
||||
// change all of this to the gd.needs(...) stuff
|
||||
gd.assertIs({ matjson::Type::Object, matjson::Type::String });
|
||||
if (gd.isObject()) {
|
||||
gd.needs(GEODE_PLATFORM_SHORT_IDENTIFIER)
|
||||
.mustBe<std::string>("a valid gd version", [](auto const& str) {
|
||||
return str == "*" || numFromString<double>(str).isOk();
|
||||
})
|
||||
.into(impl->m_gdVersion);
|
||||
}
|
||||
else if (gd.isString()) {
|
||||
impl->m_softInvalidReason = "mod.json uses old syntax";
|
||||
goto dontCheckVersion;
|
||||
} else {
|
||||
return Err("[mod.json] has invalid target GD version");
|
||||
}
|
||||
if (ver.empty()) {
|
||||
// this will show an error later on, but will at least load the rest of the metadata
|
||||
ver = "0.000";
|
||||
}
|
||||
if (ver != "*") {
|
||||
auto res = numFromString<double>(ver);
|
||||
if (res.isErr()) {
|
||||
return Err("[mod.json] has invalid target GD version");
|
||||
}
|
||||
impl->m_gdVersion = ver;
|
||||
}
|
||||
} else {
|
||||
return Err("[mod.json] is missing target GD version");
|
||||
}
|
||||
dontCheckVersion:
|
||||
|
||||
constexpr auto ID_REGEX = "[a-z0-9\\-_]+\\.[a-z0-9\\-_]+";
|
||||
root.needs("id")
|
||||
// todo: make this use validateID in full 2.0.0 release
|
||||
.validate(MiniFunction<bool(std::string const&)>(&ModMetadata::Impl::validateOldID))
|
||||
.mustBe<std::string>(ID_REGEX, &ModMetadata::Impl::validateOldID)
|
||||
.into(impl->m_id);
|
||||
|
||||
// if (!isDeprecatedIDForm(impl->m_id)) {
|
||||
|
@ -180,7 +163,7 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
|
|||
if (root.has("developer")) {
|
||||
return Err("[mod.json] can not have both \"developer\" and \"developers\" specified");
|
||||
}
|
||||
for (auto& dev : root.needs("developers").iterate()) {
|
||||
for (auto& dev : root.needs("developers").items()) {
|
||||
impl->m_developers.push_back(dev.template get<std::string>());
|
||||
}
|
||||
}
|
||||
|
@ -205,11 +188,9 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
|
|||
});
|
||||
}
|
||||
|
||||
for (auto& dep : root.has("dependencies").iterate()) {
|
||||
auto obj = dep.obj();
|
||||
|
||||
bool onThisPlatform = !obj.has("platforms");
|
||||
for (auto& plat : obj.has("platforms").iterate()) {
|
||||
for (auto& dep : root.has("dependencies").items()) {
|
||||
bool onThisPlatform = !dep.has("platforms");
|
||||
for (auto& plat : dep.has("platforms").items()) {
|
||||
if (PlatformID::coveredBy(plat.get<std::string>(), GEODE_PLATFORM_TARGET)) {
|
||||
onThisPlatform = true;
|
||||
}
|
||||
|
@ -219,11 +200,10 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
|
|||
}
|
||||
|
||||
Dependency dependency;
|
||||
// todo: make this use validateID in full 2.0.0 release
|
||||
obj.needs("id").validate(MiniFunction<bool(std::string const&)>(&ModMetadata::Impl::validateOldID)).into(dependency.id);
|
||||
obj.needs("version").into(dependency.version);
|
||||
obj.has("importance").into(dependency.importance);
|
||||
obj.checkUnknownKeys();
|
||||
dep.needs("id").mustBe<std::string>(ID_REGEX, &ModMetadata::Impl::validateOldID).into(dependency.id);
|
||||
dep.needs("version").into(dependency.version);
|
||||
dep.has("importance").into(dependency.importance);
|
||||
dep.checkUnknownKeys();
|
||||
|
||||
if (
|
||||
dependency.version.getComparison() != VersionCompare::MoreEq &&
|
||||
|
@ -247,24 +227,20 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
|
|||
impl->m_dependencies.push_back(dependency);
|
||||
}
|
||||
|
||||
for (auto& incompat : root.has("incompatibilities").iterate()) {
|
||||
auto obj = incompat.obj();
|
||||
|
||||
for (auto& incompat : root.has("incompatibilities").items()) {
|
||||
Incompatibility incompatibility;
|
||||
obj.needs("id").validate(MiniFunction<bool(std::string const&)>(&ModMetadata::Impl::validateOldID)).into(incompatibility.id);
|
||||
obj.needs("version").into(incompatibility.version);
|
||||
obj.has("importance").into(incompatibility.importance);
|
||||
obj.checkUnknownKeys();
|
||||
|
||||
incompat.needs("id").mustBe<std::string>(ID_REGEX, &ModMetadata::Impl::validateOldID).into(incompatibility.id);
|
||||
incompat.needs("version").into(incompatibility.version);
|
||||
incompat.has("importance").into(incompatibility.importance);
|
||||
incompat.checkUnknownKeys();
|
||||
impl->m_incompatibilities.push_back(incompatibility);
|
||||
}
|
||||
|
||||
for (auto& [key, value] : root.has("settings").items()) {
|
||||
for (auto& [key, value] : root.has("settings").properties()) {
|
||||
// Skip settings not on this platform
|
||||
if (value.template is<matjson::Object>()) {
|
||||
auto obj = value.obj();
|
||||
bool onThisPlatform = !obj.has("platforms");
|
||||
for (auto& plat : obj.has("platforms").iterate()) {
|
||||
if (value.is(matjson::Type::Object)) {
|
||||
bool onThisPlatform = !value.has("platforms");
|
||||
for (auto& plat : value.has("platforms").items()) {
|
||||
if (PlatformID::coveredBy(plat.get<std::string>(), GEODE_PLATFORM_TARGET)) {
|
||||
onThisPlatform = true;
|
||||
}
|
||||
|
@ -274,24 +250,24 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
|
|||
}
|
||||
}
|
||||
|
||||
GEODE_UNWRAP_INTO(auto sett, Setting::parse(key, impl->m_id, value));
|
||||
GEODE_UNWRAP_INTO(auto sett, SettingV3::parseBuiltin(impl->m_id, value.json()));
|
||||
impl->m_settings.emplace_back(key, sett);
|
||||
}
|
||||
|
||||
if (auto resources = root.has("resources").obj()) {
|
||||
for (auto& [key, _] : resources.has("spritesheets").items()) {
|
||||
if (auto resources = root.has("resources")) {
|
||||
for (auto& [key, _] : resources.has("spritesheets").properties()) {
|
||||
impl->m_spritesheets.push_back(impl->m_id + "/" + key);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto issues = root.has("issues").obj()) {
|
||||
if (auto issues = root.has("issues")) {
|
||||
IssuesInfo issuesInfo;
|
||||
issues.needs("info").into(issuesInfo.info);
|
||||
issues.has("url").intoAs<std::string>(issuesInfo.url);
|
||||
issues.has("url").into(issuesInfo.url);
|
||||
impl->m_issues = issuesInfo;
|
||||
}
|
||||
|
||||
if (auto links = root.has("links").obj()) {
|
||||
if (auto links = root.has("links")) {
|
||||
links.has("homepage").into(info.getLinksMut().getImpl()->m_homepage);
|
||||
links.has("source").into(info.getLinksMut().getImpl()->m_source);
|
||||
links.has("community").into(info.getLinksMut().getImpl()->m_community);
|
||||
|
@ -299,19 +275,15 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
|
|||
}
|
||||
|
||||
// Tags. Actual validation is done when interacting with the server in the UI
|
||||
for (auto& tag : root.has("tags").iterate()) {
|
||||
for (auto& tag : root.has("tags").items()) {
|
||||
impl->m_tags.insert(tag.template get<std::string>());
|
||||
}
|
||||
|
||||
// with new cli, binary name is always mod id
|
||||
impl->m_binaryName = impl->m_id + GEODE_PLATFORM_EXTENSION;
|
||||
|
||||
if (checker.isError()) {
|
||||
return Err(checker.getError());
|
||||
}
|
||||
root.checkUnknownKeys();
|
||||
|
||||
return Ok(info);
|
||||
return root.ok(info);
|
||||
}
|
||||
|
||||
Result<ModMetadata> ModMetadata::Impl::create(ModJson const& json) {
|
||||
|
@ -542,6 +514,18 @@ std::vector<std::string> ModMetadata::getSpritesheets() const {
|
|||
return m_impl->m_spritesheets;
|
||||
}
|
||||
std::vector<std::pair<std::string, Setting>> ModMetadata::getSettings() const {
|
||||
std::vector<std::pair<std::string, Setting>> res;
|
||||
for (auto [key, sett] : m_impl->m_settings) {
|
||||
auto checker = JsonChecker(sett);
|
||||
auto value = checker.root("");
|
||||
auto legacy = Setting::parse(key, m_impl->m_id, value);
|
||||
if (!checker.isError() && legacy.isOk()) {
|
||||
res.push_back(std::make_pair(key, *legacy));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
std::vector<std::pair<std::string, matjson::Value>> ModMetadata::getSettingsV3() const {
|
||||
return m_impl->m_settings;
|
||||
}
|
||||
std::unordered_set<std::string> ModMetadata::getTags() const {
|
||||
|
@ -643,6 +627,10 @@ void ModMetadata::setSpritesheets(std::vector<std::string> const& value) {
|
|||
m_impl->m_spritesheets = value;
|
||||
}
|
||||
void ModMetadata::setSettings(std::vector<std::pair<std::string, Setting>> const& value) {
|
||||
// intentionally no-op because no one is supposed to be using this
|
||||
// without subscribing to "internals are not stable" mentality
|
||||
}
|
||||
void ModMetadata::setSettings(std::vector<std::pair<std::string, matjson::Value>> const& value) {
|
||||
m_impl->m_settings = value;
|
||||
}
|
||||
void ModMetadata::setTags(std::unordered_set<std::string> const& value) {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/utils/JsonValidation.hpp>
|
||||
#include <Geode/utils/VersionInfo.hpp>
|
||||
#include <Geode/loader/SettingV3.hpp>
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
|
@ -36,7 +37,7 @@ namespace geode {
|
|||
std::vector<Dependency> m_dependencies;
|
||||
std::vector<Incompatibility> m_incompatibilities;
|
||||
std::vector<std::string> m_spritesheets;
|
||||
std::vector<std::pair<std::string, Setting>> m_settings;
|
||||
std::vector<std::pair<std::string, matjson::Value>> m_settings;
|
||||
std::unordered_set<std::string> m_tags;
|
||||
bool m_needsEarlyLoad = false;
|
||||
bool m_isAPI = false;
|
||||
|
|
11
loader/src/loader/ModSettingsManager.cpp
Normal file
11
loader/src/loader/ModSettingsManager.cpp
Normal file
|
@ -0,0 +1,11 @@
|
|||
#include "ModSettingsManager.hpp"
|
||||
|
||||
SettingV3* ModSettingsManager::get(std::string const& id) {}
|
||||
|
||||
SettingValue* ModSettingsManager::getLegacy(std::string const& id) {
|
||||
// If this setting has alreay been given a legacy interface, give that
|
||||
if (m_legacy.count(id)) {
|
||||
return m_legacy.at(id).get();
|
||||
}
|
||||
if (m_v3.count(id)) {}
|
||||
}
|
25
loader/src/loader/ModSettingsManager.hpp
Normal file
25
loader/src/loader/ModSettingsManager.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <Geode/DefaultInclude.hpp>
|
||||
#include <Geode/loader/SettingV3.hpp>
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
// This class should NEVER be exposed in a header!!!
|
||||
// It is an implementation detail!!!
|
||||
|
||||
class ModSettingsManager final {
|
||||
private:
|
||||
struct SettingInfo final {
|
||||
std::unique_ptr<SettingV3> v3;
|
||||
std::unique_ptr<SettingValue> legacy;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::unique_ptr<SettingV3>> m_v3;
|
||||
// todo: remove in v4
|
||||
std::unordered_map<std::string, std::unique_ptr<SettingValue>> m_legacy;
|
||||
|
||||
public:
|
||||
SettingV3* get(std::string const& id);
|
||||
SettingValue* getLegacy(std::string const& id);
|
||||
};
|
746
loader/src/loader/SettingV3.cpp
Normal file
746
loader/src/loader/SettingV3.cpp
Normal file
|
@ -0,0 +1,746 @@
|
|||
#include <Geode/loader/SettingV3.hpp>
|
||||
#include <Geode/utils/JsonValidation.hpp>
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
class SettingV3::GeodeImpl {
|
||||
public:
|
||||
std::string modID;
|
||||
std::string key;
|
||||
};
|
||||
|
||||
SettingV3::~SettingV3() = default;
|
||||
|
||||
SettingV3::SettingV3(std::string const& key, std::string const& modID)
|
||||
: m_impl(std::make_shared<GeodeImpl>())
|
||||
{
|
||||
m_impl->key = key;
|
||||
m_impl->modID = modID;
|
||||
}
|
||||
|
||||
std::string SettingV3::getKey() const {
|
||||
return m_impl->key;
|
||||
}
|
||||
std::string SettingV3::getModID() const {
|
||||
return m_impl->modID;
|
||||
}
|
||||
Mod* SettingV3::getMod() const {
|
||||
return Loader::get()->getInstalledMod(m_impl->modID);
|
||||
}
|
||||
|
||||
Result<std::shared_ptr<SettingV3>> SettingV3::parseBuiltin(std::string const& modID, matjson::Value const& json) {
|
||||
auto root = checkJson(json, "SettingV3");
|
||||
std::string type;
|
||||
root.needs("type").into(type);
|
||||
std::shared_ptr<SettingV3> ret;
|
||||
switch (hash(type)) {
|
||||
case hash("bool"): ret = std::make_shared<BoolSettingV3>(); break;
|
||||
case hash("int"): ret = std::make_shared<IntSettingV3>(); break;
|
||||
case hash("float"): ret = std::make_shared<FloatSettingV3>(); break;
|
||||
case hash("string"): ret = std::make_shared<StringSettingV3>(); break;
|
||||
case hash("rgb"): case hash("color"): ret = std::make_shared<Color3BSettingV3>(); break;
|
||||
case hash("rgba"): ret = std::make_shared<Color4BSettingV3>(); break;
|
||||
case hash("path"): case hash("file"): ret = std::make_shared<FileSettingV3>(); break;
|
||||
case hash("custom"): ret = std::make_shared<UnresolvedCustomSettingV3>(); break;
|
||||
case hash("title"): ret = std::make_shared<TitleSettingV3>(); break;
|
||||
}
|
||||
GEODE_UNWRAP(ret->parse(modID, json));
|
||||
return root.ok(ret);
|
||||
}
|
||||
std::optional<Setting> SettingV3::convertToLegacy() const {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::optional<std::unique_ptr<SettingValue>> SettingV3::convertToLegacyValue() const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
class geode::detail::GeodeSettingBaseV3::Impl final {
|
||||
public:
|
||||
std::string name;
|
||||
std::optional<std::string> description;
|
||||
std::optional<std::string> enableIf;
|
||||
};
|
||||
|
||||
std::string geode::detail::GeodeSettingBaseV3::getName() const {
|
||||
return m_impl->name;
|
||||
}
|
||||
std::optional<std::string> geode::detail::GeodeSettingBaseV3::getDescription() const {
|
||||
return m_impl->description;
|
||||
}
|
||||
std::optional<std::string> geode::detail::GeodeSettingBaseV3::getEnableIf() const {
|
||||
return m_impl->enableIf;
|
||||
}
|
||||
|
||||
Result<> geode::detail::GeodeSettingBaseV3::parseShared(JsonExpectedValue& json) {
|
||||
json.needs("name").into(m_impl->name);
|
||||
json.needs("description").into(m_impl->description);
|
||||
json.needs("enable-if").into(m_impl->enableIf);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
class TitleSettingV3::Impl final {
|
||||
public:
|
||||
std::string title;
|
||||
};
|
||||
|
||||
std::string TitleSettingV3::getTitle() const {
|
||||
return m_impl->title;
|
||||
}
|
||||
|
||||
Result<> TitleSettingV3::parse(std::string const& modID, matjson::Value const& json) {
|
||||
auto root = checkJson(json, "TitleSettingV3");
|
||||
root.needs("title").into(m_impl->title);
|
||||
root.checkUnknownKeys();
|
||||
return root.ok();
|
||||
}
|
||||
bool TitleSettingV3::load(matjson::Value const& json) {
|
||||
return true;
|
||||
}
|
||||
bool TitleSettingV3::save(matjson::Value&) const {
|
||||
return true;
|
||||
}
|
||||
SettingNodeV3* TitleSettingV3::createNode(float width) {
|
||||
// todo
|
||||
}
|
||||
bool TitleSettingV3::isDefaultValue() const {
|
||||
return true;
|
||||
}
|
||||
void TitleSettingV3::reset() {}
|
||||
|
||||
class UnresolvedCustomSettingV3::Impl final {
|
||||
public:
|
||||
matjson::Value json;
|
||||
};
|
||||
|
||||
Result<> UnresolvedCustomSettingV3::parse(std::string const& modID, matjson::Value const& json) {
|
||||
m_impl->json = json;
|
||||
return Ok();
|
||||
}
|
||||
bool UnresolvedCustomSettingV3::load(matjson::Value const& json) {
|
||||
return true;
|
||||
}
|
||||
bool UnresolvedCustomSettingV3::save(matjson::Value& json) const {
|
||||
return true;
|
||||
}
|
||||
SettingNodeV3* UnresolvedCustomSettingV3::createNode(float width) {
|
||||
// todo
|
||||
}
|
||||
|
||||
bool UnresolvedCustomSettingV3::isDefaultValue() const {
|
||||
return true;
|
||||
}
|
||||
void UnresolvedCustomSettingV3::reset() {}
|
||||
|
||||
std::optional<Setting> UnresolvedCustomSettingV3::convertToLegacy() const {
|
||||
return Setting(this->getKey(), this->getModID(), SettingKind(CustomSetting {
|
||||
.json = std::make_shared<ModJson>(m_impl->json)
|
||||
}));
|
||||
}
|
||||
std::optional<std::unique_ptr<SettingValue>> UnresolvedCustomSettingV3::convertToLegacyValue() const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
class BoolSettingV3::Impl final {
|
||||
public:
|
||||
bool value;
|
||||
bool defaultValue;
|
||||
};
|
||||
|
||||
bool BoolSettingV3::getValue() const {
|
||||
return m_impl->value;
|
||||
}
|
||||
void BoolSettingV3::setValue(bool value) {
|
||||
m_impl->value = value;
|
||||
}
|
||||
Result<> BoolSettingV3::isValid(bool value) const {}
|
||||
bool BoolSettingV3::getDefaultValue() const {
|
||||
return m_impl->defaultValue;
|
||||
}
|
||||
|
||||
Result<> BoolSettingV3::parse(std::string const& modID, matjson::Value const& json) {
|
||||
auto root = checkJson(json, "BoolSettingV3");
|
||||
|
||||
GEODE_UNWRAP(this->parseShared(root));
|
||||
root.needs("default").into(m_impl->defaultValue);
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
|
||||
root.checkUnknownKeys();
|
||||
return root.ok();
|
||||
}
|
||||
bool BoolSettingV3::load(matjson::Value const& json) {
|
||||
if (json.is_bool()) {
|
||||
m_impl->value = json.as_bool();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool BoolSettingV3::save(matjson::Value& json) const {
|
||||
json = m_impl->value;
|
||||
return true;
|
||||
}
|
||||
SettingNodeV3* BoolSettingV3::createNode(float width) {
|
||||
// todo
|
||||
}
|
||||
bool BoolSettingV3::isDefaultValue() const {
|
||||
return m_impl->value == m_impl->defaultValue;
|
||||
}
|
||||
void BoolSettingV3::reset() {
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
}
|
||||
|
||||
std::optional<Setting> BoolSettingV3::convertToLegacy() const {
|
||||
return Setting(this->getKey(), this->getModID(), SettingKind(BoolSetting {
|
||||
.name = this->getName(),
|
||||
.description = this->getDescription(),
|
||||
.defaultValue = this->getDefaultValue(),
|
||||
}));
|
||||
}
|
||||
std::optional<std::unique_ptr<SettingValue>> BoolSettingV3::convertToLegacyValue() const {
|
||||
return std::make_unique<BoolSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
|
||||
}
|
||||
|
||||
class IntSettingV3::Impl final {
|
||||
public:
|
||||
int64_t value;
|
||||
int64_t defaultValue;
|
||||
std::optional<int64_t> minValue;
|
||||
std::optional<int64_t> maxValue;
|
||||
|
||||
struct {
|
||||
// 0 means not enabled
|
||||
size_t arrowStepSize;
|
||||
size_t bigArrowStepSize;
|
||||
bool sliderEnabled;
|
||||
std::optional<int64_t> sliderSnap;
|
||||
bool textInputEnabled;
|
||||
} controls;
|
||||
};
|
||||
|
||||
bool IntSettingV3::isArrowsEnabled() const {
|
||||
return m_impl->controls.arrowStepSize > 0;
|
||||
}
|
||||
bool IntSettingV3::isBigArrowsEnabled() const {
|
||||
return m_impl->controls.bigArrowStepSize > 0;
|
||||
}
|
||||
size_t IntSettingV3::getArrowStepSize() const {
|
||||
return m_impl->controls.arrowStepSize;
|
||||
}
|
||||
size_t IntSettingV3::getBigArrowStepSize() const {
|
||||
return m_impl->controls.bigArrowStepSize;
|
||||
}
|
||||
bool IntSettingV3::isSliderEnabled() const {
|
||||
return m_impl->controls.sliderEnabled;
|
||||
}
|
||||
std::optional<int64_t> IntSettingV3::getSliderSnap() const {
|
||||
return m_impl->controls.sliderSnap;
|
||||
}
|
||||
bool IntSettingV3::isInputEnabled() const {
|
||||
return m_impl->controls.textInputEnabled;
|
||||
}
|
||||
|
||||
int64_t IntSettingV3::getValue() const {
|
||||
return m_impl->value;
|
||||
}
|
||||
void IntSettingV3::setValue(int64_t value) {
|
||||
m_impl->value = clamp(
|
||||
value,
|
||||
m_impl->minValue.value_or(std::numeric_limits<int64_t>::min()),
|
||||
m_impl->maxValue.value_or(std::numeric_limits<int64_t>::max())
|
||||
);
|
||||
}
|
||||
int64_t IntSettingV3::getDefaultValue() const {
|
||||
return m_impl->defaultValue;
|
||||
}
|
||||
std::optional<int64_t> IntSettingV3::getMinValue() const {
|
||||
return m_impl->minValue;
|
||||
}
|
||||
std::optional<int64_t> IntSettingV3::getMaxValue() const {
|
||||
return m_impl->maxValue;
|
||||
}
|
||||
|
||||
Result<> IntSettingV3::parse(std::string const& modID, matjson::Value const& json) {
|
||||
auto root = checkJson(json, "IntSettingV3");
|
||||
|
||||
GEODE_UNWRAP(this->parseShared(root));
|
||||
root.needs("default").into(m_impl->defaultValue);
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
|
||||
root.has("min").into(m_impl->minValue);
|
||||
root.has("max").into(m_impl->maxValue);
|
||||
if (auto controls = root.has("control")) {
|
||||
controls.has("arrow-step").into(m_impl->controls.arrowStepSize);
|
||||
if (!controls.has("arrows").template get<bool>()) {
|
||||
m_impl->controls.arrowStepSize = 0;
|
||||
}
|
||||
controls.has("big-arrow-step").into(m_impl->controls.bigArrowStepSize);
|
||||
if (!controls.has("big-arrows").template get<bool>()) {
|
||||
m_impl->controls.bigArrowStepSize = 0;
|
||||
}
|
||||
controls.has("slider").into(m_impl->controls.sliderEnabled);
|
||||
controls.has("slider-step").into(m_impl->controls.sliderSnap);
|
||||
controls.has("input").into(m_impl->controls.textInputEnabled);
|
||||
controls.checkUnknownKeys();
|
||||
}
|
||||
|
||||
root.checkUnknownKeys();
|
||||
return root.ok();
|
||||
}
|
||||
bool IntSettingV3::load(matjson::Value const& json) {
|
||||
if (json.is_number()) {
|
||||
m_impl->value = json.as_int();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool IntSettingV3::save(matjson::Value& json) const {
|
||||
json = m_impl->value;
|
||||
return true;
|
||||
}
|
||||
SettingNodeV3* IntSettingV3::createNode(float width) {
|
||||
// todo
|
||||
}
|
||||
|
||||
bool IntSettingV3::isDefaultValue() const {
|
||||
return m_impl->value == m_impl->defaultValue;
|
||||
}
|
||||
void IntSettingV3::reset() {
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
}
|
||||
|
||||
std::optional<Setting> IntSettingV3::convertToLegacy() const {
|
||||
return Setting(this->getKey(), this->getModID(), SettingKind(IntSetting {
|
||||
.name = this->getName(),
|
||||
.description = this->getDescription(),
|
||||
.defaultValue = this->getDefaultValue(),
|
||||
.min = this->getMinValue(),
|
||||
.max = this->getMaxValue(),
|
||||
.controls = {
|
||||
.arrows = this->isArrowsEnabled(),
|
||||
.bigArrows = this->isBigArrowsEnabled(),
|
||||
.arrowStep = this->getArrowStepSize(),
|
||||
.bigArrowStep = this->getBigArrowStepSize(),
|
||||
.slider = this->isSliderEnabled(),
|
||||
.sliderStep = this->getSliderSnap(),
|
||||
.input = this->isInputEnabled(),
|
||||
},
|
||||
}));
|
||||
}
|
||||
std::optional<std::unique_ptr<SettingValue>> IntSettingV3::convertToLegacyValue() const {
|
||||
return std::make_unique<IntSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
|
||||
}
|
||||
|
||||
class FloatSettingV3::Impl final {
|
||||
public:
|
||||
double value;
|
||||
double defaultValue;
|
||||
std::optional<double> minValue;
|
||||
std::optional<double> maxValue;
|
||||
|
||||
struct {
|
||||
// 0 means not enabled
|
||||
size_t arrowStepSize;
|
||||
size_t bigArrowStepSize;
|
||||
bool sliderEnabled;
|
||||
std::optional<double> sliderSnap;
|
||||
bool textInputEnabled;
|
||||
} controls;
|
||||
};
|
||||
|
||||
bool FloatSettingV3::isArrowsEnabled() const {
|
||||
return m_impl->controls.arrowStepSize > 0;
|
||||
}
|
||||
bool FloatSettingV3::isBigArrowsEnabled() const {
|
||||
return m_impl->controls.bigArrowStepSize > 0;
|
||||
}
|
||||
size_t FloatSettingV3::getArrowStepSize() const {
|
||||
return m_impl->controls.arrowStepSize;
|
||||
}
|
||||
size_t FloatSettingV3::getBigArrowStepSize() const {
|
||||
return m_impl->controls.bigArrowStepSize;
|
||||
}
|
||||
bool FloatSettingV3::isSliderEnabled() const {
|
||||
return m_impl->controls.sliderEnabled;
|
||||
}
|
||||
std::optional<double> FloatSettingV3::getSliderSnap() const {
|
||||
return m_impl->controls.sliderSnap;
|
||||
}
|
||||
bool FloatSettingV3::isInputEnabled() const {
|
||||
return m_impl->controls.textInputEnabled;
|
||||
}
|
||||
|
||||
double FloatSettingV3::getValue() const {
|
||||
return m_impl->value;
|
||||
}
|
||||
Result<> FloatSettingV3::setValue(double value) {
|
||||
if (m_impl->minValue && value < *m_impl->minValue) {
|
||||
return Err("Value must be under ");
|
||||
}
|
||||
m_impl->value = clamp(
|
||||
value,
|
||||
m_impl->minValue.value_or(std::numeric_limits<double>::min()),
|
||||
m_impl->maxValue.value_or(std::numeric_limits<double>::max())
|
||||
);
|
||||
}
|
||||
double FloatSettingV3::getDefaultValue() const {
|
||||
return m_impl->defaultValue;
|
||||
}
|
||||
std::optional<double> FloatSettingV3::getMinValue() const {
|
||||
return m_impl->minValue;
|
||||
}
|
||||
std::optional<double> FloatSettingV3::getMaxValue() const {
|
||||
return m_impl->maxValue;
|
||||
}
|
||||
|
||||
Result<> FloatSettingV3::parse(std::string const& modID, matjson::Value const& json) {
|
||||
auto root = checkJson(json, "FloatSettingV3");
|
||||
|
||||
GEODE_UNWRAP(this->parseShared(root));
|
||||
root.needs("default").into(m_impl->defaultValue);
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
|
||||
root.has("min").into(m_impl->minValue);
|
||||
root.has("max").into(m_impl->maxValue);
|
||||
if (auto controls = root.has("control")) {
|
||||
controls.has("arrow-step").into(m_impl->controls.arrowStepSize);
|
||||
if (!controls.has("arrows").template get<bool>()) {
|
||||
m_impl->controls.arrowStepSize = 0;
|
||||
}
|
||||
controls.has("big-arrow-step").into(m_impl->controls.bigArrowStepSize);
|
||||
if (!controls.has("big-arrows").template get<bool>()) {
|
||||
m_impl->controls.bigArrowStepSize = 0;
|
||||
}
|
||||
controls.has("slider").into(m_impl->controls.sliderEnabled);
|
||||
controls.has("slider-step").into(m_impl->controls.sliderSnap);
|
||||
controls.has("input").into(m_impl->controls.textInputEnabled);
|
||||
controls.checkUnknownKeys();
|
||||
}
|
||||
|
||||
root.checkUnknownKeys();
|
||||
return root.ok();
|
||||
}
|
||||
bool FloatSettingV3::load(matjson::Value const& json) {
|
||||
if (json.is_number()) {
|
||||
m_impl->value = json.as_double();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool FloatSettingV3::save(matjson::Value& json) const {
|
||||
json = m_impl->value;
|
||||
return true;
|
||||
}
|
||||
SettingNodeV3* FloatSettingV3::createNode(float width) {
|
||||
// todo
|
||||
}
|
||||
|
||||
bool FloatSettingV3::isDefaultValue() const {
|
||||
return m_impl->value == m_impl->defaultValue;
|
||||
}
|
||||
void FloatSettingV3::reset() {
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
}
|
||||
|
||||
std::optional<Setting> FloatSettingV3::convertToLegacy() const {
|
||||
return Setting(this->getKey(), this->getModID(), SettingKind(FloatSetting {
|
||||
.name = this->getName(),
|
||||
.description = this->getDescription(),
|
||||
.defaultValue = this->getDefaultValue(),
|
||||
.min = this->getMinValue(),
|
||||
.max = this->getMaxValue(),
|
||||
.controls = {
|
||||
.arrows = this->isArrowsEnabled(),
|
||||
.bigArrows = this->isBigArrowsEnabled(),
|
||||
.arrowStep = this->getArrowStepSize(),
|
||||
.bigArrowStep = this->getBigArrowStepSize(),
|
||||
.slider = this->isSliderEnabled(),
|
||||
.sliderStep = this->getSliderSnap(),
|
||||
.input = this->isInputEnabled(),
|
||||
},
|
||||
}));
|
||||
}
|
||||
std::optional<std::unique_ptr<SettingValue>> FloatSettingV3::convertToLegacyValue() const {
|
||||
return std::make_unique<FloatSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
|
||||
}
|
||||
|
||||
class StringSettingV3::Impl final {
|
||||
public:
|
||||
std::string value;
|
||||
std::string defaultValue;
|
||||
std::optional<std::string> match;
|
||||
std::optional<std::string> filter;
|
||||
std::optional<std::vector<std::string>> oneOf;
|
||||
};
|
||||
|
||||
std::string StringSettingV3::getValue() const {
|
||||
return m_impl->value;
|
||||
}
|
||||
Result<> StringSettingV3::setValue(std::string_view value) {
|
||||
m_impl->value = value;
|
||||
}
|
||||
std::string StringSettingV3::getDefaultValue() const {
|
||||
return m_impl->defaultValue;
|
||||
}
|
||||
|
||||
std::optional<std::string> StringSettingV3::getRegexValidator() const {
|
||||
return m_impl->match;
|
||||
}
|
||||
std::optional<std::string> StringSettingV3::getAllowedCharacters() const {
|
||||
return m_impl->filter;
|
||||
}
|
||||
std::optional<std::vector<std::string>> StringSettingV3::getEnumOptions() const {
|
||||
return m_impl->oneOf;
|
||||
}
|
||||
|
||||
Result<> StringSettingV3::parse(std::string const& modID, matjson::Value const& json) {
|
||||
auto root = checkJson(json, "StringSettingV3");
|
||||
|
||||
GEODE_UNWRAP(this->parseShared(root));
|
||||
root.needs("default").into(m_impl->defaultValue);
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
|
||||
root.has("match").into(m_impl->match);
|
||||
root.has("filter").into(m_impl->filter);
|
||||
root.has("one-of").into(m_impl->oneOf);
|
||||
|
||||
root.checkUnknownKeys();
|
||||
return root.ok();
|
||||
}
|
||||
bool StringSettingV3::load(matjson::Value const& json) {
|
||||
if (json.is_string()) {
|
||||
m_impl->value = json.as_string();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool StringSettingV3::save(matjson::Value& json) const {
|
||||
json = m_impl->value;
|
||||
return true;
|
||||
}
|
||||
SettingNodeV3* StringSettingV3::createNode(float width) {
|
||||
// todo
|
||||
}
|
||||
|
||||
bool StringSettingV3::isDefaultValue() const {
|
||||
return m_impl->value == m_impl->defaultValue;
|
||||
}
|
||||
void StringSettingV3::reset() {
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
}
|
||||
|
||||
std::optional<Setting> StringSettingV3::convertToLegacy() const {
|
||||
auto setting = StringSetting();
|
||||
setting.name = this->getName();
|
||||
setting.description = this->getDescription();
|
||||
setting.defaultValue = this->getDefaultValue();
|
||||
setting.controls->filter = this->getAllowedCharacters();
|
||||
setting.controls->match = this->getRegexValidator();
|
||||
setting.controls->options = this->getEnumOptions();
|
||||
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
|
||||
}
|
||||
std::optional<std::unique_ptr<SettingValue>> StringSettingV3::convertToLegacyValue() const {
|
||||
return std::make_unique<StringSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
|
||||
}
|
||||
|
||||
class FileSettingV3::Impl final {
|
||||
public:
|
||||
std::filesystem::path value;
|
||||
std::filesystem::path defaultValue;
|
||||
std::optional<std::vector<utils::file::FilePickOptions::Filter>> filters;
|
||||
};
|
||||
|
||||
std::filesystem::path FileSettingV3::getDefaultValue() const {
|
||||
return m_impl->defaultValue;
|
||||
}
|
||||
std::filesystem::path FileSettingV3::getValue() const {
|
||||
return m_impl->value;
|
||||
}
|
||||
std::optional<std::vector<utils::file::FilePickOptions::Filter>> FileSettingV3::getFilters() const {
|
||||
return m_impl->filters;
|
||||
}
|
||||
|
||||
Result<> FileSettingV3::parse(std::string const& modID, matjson::Value const& json) {
|
||||
auto root = checkJson(json, "FileSettingV3");
|
||||
|
||||
GEODE_UNWRAP(this->parseShared(root));
|
||||
|
||||
root.needs("default").into(m_impl->defaultValue);
|
||||
|
||||
// Replace known paths like `{gd-save-dir}/`
|
||||
try {
|
||||
m_impl->defaultValue = fmt::format(
|
||||
fmt::runtime(m_impl->defaultValue.string()),
|
||||
fmt::arg("gd-save-dir", dirs::getSaveDir()),
|
||||
fmt::arg("gd-game-dir", dirs::getGameDir()),
|
||||
fmt::arg("mod-config-dir", dirs::getModConfigDir() / modID),
|
||||
fmt::arg("mod-save-dir", dirs::getModsSaveDir() / modID),
|
||||
fmt::arg("temp-dir", dirs::getTempDir())
|
||||
);
|
||||
}
|
||||
catch(fmt::format_error const&) {
|
||||
return Err("Invalid format string for file setting path");
|
||||
}
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
|
||||
if (auto controls = root.has("control")) {
|
||||
auto filters = std::vector<file::FilePickOptions::Filter>();
|
||||
for (auto& item : controls.has("filters").items()) {
|
||||
utils::file::FilePickOptions::Filter filter;
|
||||
item.has("description").into(filter.description);
|
||||
item.has("files").into(filter.files);
|
||||
filters.push_back(filter);
|
||||
}
|
||||
if (!filters.empty()) {
|
||||
m_impl->filters.emplace(filters);
|
||||
}
|
||||
}
|
||||
|
||||
root.checkUnknownKeys();
|
||||
return root.ok();
|
||||
}
|
||||
bool FileSettingV3::load(matjson::Value const& json) {
|
||||
if (json.is_string()) {
|
||||
m_impl->value = json.as_string();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool FileSettingV3::save(matjson::Value& json) const {
|
||||
json = m_impl->value;
|
||||
return true;
|
||||
}
|
||||
SettingNodeV3* createNode(float width) {
|
||||
// todo
|
||||
}
|
||||
|
||||
bool FileSettingV3::isDefaultValue() const {
|
||||
return m_impl->value == m_impl->defaultValue;
|
||||
}
|
||||
void FileSettingV3::reset() {
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
}
|
||||
|
||||
std::optional<Setting> FileSettingV3::convertToLegacy() const {
|
||||
auto setting = FileSetting();
|
||||
setting.name = this->getName();
|
||||
setting.description = this->getDescription();
|
||||
setting.defaultValue = this->getDefaultValue();
|
||||
setting.controls.filters = this->getFilters().value_or(std::vector<utils::file::FilePickOptions::Filter>());
|
||||
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
|
||||
}
|
||||
std::optional<std::unique_ptr<SettingValue>> FileSettingV3::convertToLegacyValue() const {
|
||||
return std::make_unique<FileSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
|
||||
}
|
||||
|
||||
class Color3BSettingV3::Impl final {
|
||||
public:
|
||||
ccColor3B value;
|
||||
ccColor3B defaultValue;
|
||||
};
|
||||
|
||||
ccColor3B Color3BSettingV3::getDefaultValue() const {
|
||||
return m_impl->defaultValue;
|
||||
}
|
||||
ccColor3B Color3BSettingV3::getValue() const {
|
||||
return m_impl->value;
|
||||
}
|
||||
|
||||
Result<> Color3BSettingV3::parse(std::string const& modID, matjson::Value const& json) {
|
||||
auto root = checkJson(json, "Color3BSettingV3");
|
||||
|
||||
GEODE_UNWRAP(this->parseShared(root));
|
||||
root.needs("default").into(m_impl->defaultValue);
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
|
||||
root.checkUnknownKeys();
|
||||
return root.ok();
|
||||
}
|
||||
bool Color3BSettingV3::load(matjson::Value const& json) {
|
||||
if (json.template is<ccColor3B>()) {
|
||||
m_impl->value = json.template as<ccColor3B>();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool Color3BSettingV3::save(matjson::Value& json) const {
|
||||
json = m_impl->value;
|
||||
return true;
|
||||
}
|
||||
SettingNodeV3* Color3BSettingV3::createNode(float width) {
|
||||
// todo
|
||||
}
|
||||
|
||||
bool Color3BSettingV3::isDefaultValue() const {
|
||||
return m_impl->value == m_impl->defaultValue;
|
||||
|
||||
}
|
||||
void Color3BSettingV3::reset() {
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
}
|
||||
|
||||
std::optional<Setting> Color3BSettingV3::convertToLegacy() const {
|
||||
auto setting = ColorSetting();
|
||||
setting.name = this->getName();
|
||||
setting.description = this->getDescription();
|
||||
setting.defaultValue = this->getDefaultValue();
|
||||
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
|
||||
}
|
||||
std::optional<std::unique_ptr<SettingValue>> Color3BSettingV3::convertToLegacyValue() const {
|
||||
return std::make_unique<ColorSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
|
||||
}
|
||||
|
||||
class Color4BSettingV3::Impl final {
|
||||
public:
|
||||
ccColor4B value;
|
||||
ccColor4B defaultValue;
|
||||
};
|
||||
|
||||
ccColor4B Color4BSettingV3::getDefaultValue() const {
|
||||
return m_impl->defaultValue;
|
||||
}
|
||||
ccColor4B Color4BSettingV3::getValue() const {
|
||||
return m_impl->value;
|
||||
}
|
||||
|
||||
Result<> Color4BSettingV3::parse(std::string const& modID, matjson::Value const& json) {
|
||||
auto root = checkJson(json, "Color4BSettingV3");
|
||||
|
||||
GEODE_UNWRAP(this->parseShared(root));
|
||||
root.needs("default").into(m_impl->defaultValue);
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
|
||||
root.checkUnknownKeys();
|
||||
return root.ok();
|
||||
}
|
||||
bool Color4BSettingV3::load(matjson::Value const& json) {
|
||||
if (json.template is<ccColor4B>()) {
|
||||
m_impl->value = json.template as<ccColor4B>();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool Color4BSettingV3::save(matjson::Value& json) const {
|
||||
json = m_impl->value;
|
||||
return true;
|
||||
}
|
||||
SettingNodeV3* Color4BSettingV3::createNode(float width) {
|
||||
// todo
|
||||
}
|
||||
|
||||
bool Color4BSettingV3::isDefaultValue() const {
|
||||
return m_impl->value == m_impl->defaultValue;
|
||||
|
||||
}
|
||||
void Color4BSettingV3::reset() {
|
||||
m_impl->value = m_impl->defaultValue;
|
||||
}
|
||||
|
||||
std::optional<Setting> Color4BSettingV3::convertToLegacy() const {
|
||||
auto setting = ColorAlphaSetting();
|
||||
setting.name = this->getName();
|
||||
setting.description = this->getDescription();
|
||||
setting.defaultValue = this->getDefaultValue();
|
||||
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
|
||||
}
|
||||
std::optional<std::unique_ptr<SettingValue>> Color4BSettingV3::convertToLegacyValue() const {
|
||||
return std::make_unique<ColorAlphaSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
|
||||
}
|
|
@ -2,201 +2,50 @@
|
|||
|
||||
using namespace geode::prelude;
|
||||
|
||||
|
||||
matjson::Value& JsonMaybeSomething::json() {
|
||||
return m_json;
|
||||
}
|
||||
|
||||
|
||||
JsonMaybeSomething::JsonMaybeSomething(
|
||||
JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue
|
||||
) :
|
||||
m_checker(checker),
|
||||
m_json(json), m_hierarchy(hierarchy), m_hasValue(hasValue) {}
|
||||
|
||||
|
||||
bool JsonMaybeSomething::isError() const {
|
||||
return m_checker.isError() || !m_hasValue;
|
||||
}
|
||||
|
||||
|
||||
std::string JsonMaybeSomething::getError() const {
|
||||
return m_checker.getError();
|
||||
}
|
||||
|
||||
|
||||
JsonMaybeSomething::operator bool() const {
|
||||
return !isError();
|
||||
}
|
||||
|
||||
|
||||
void JsonMaybeSomething::setError(std::string const& error) {
|
||||
m_checker.m_result = error;
|
||||
}
|
||||
|
||||
|
||||
JsonMaybeValue::JsonMaybeValue(
|
||||
JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue
|
||||
) :
|
||||
JsonMaybeSomething(checker, json, hierarchy, hasValue) {}
|
||||
|
||||
) : JsonMaybeSomething(checker, json, hierarchy, hasValue) {}
|
||||
|
||||
JsonMaybeSomething& JsonMaybeValue::self() {
|
||||
return *static_cast<JsonMaybeSomething*>(this);
|
||||
}
|
||||
|
||||
// template<class Json>
|
||||
// template<nlohmann::detail::value_t T>
|
||||
// JsonMaybeValue& JsonMaybeValue::as() {
|
||||
// if (this->isError()) return *this;
|
||||
// if (!jsonConvertibleTo(self().m_json.type(), T)) {
|
||||
// this->setError(
|
||||
// self().m_hierarchy + ": Invalid type \"" +
|
||||
// self().m_json.type_name() + "\", expected \"" +
|
||||
// jsonValueTypeToString(T) + "\""
|
||||
// );
|
||||
// }
|
||||
// m_inferType = false;
|
||||
// return *this;
|
||||
// }
|
||||
|
||||
|
||||
JsonMaybeValue& JsonMaybeValue::array() {
|
||||
this->as<value_t::Array>();
|
||||
return *this;
|
||||
}
|
||||
|
||||
// template<class Json>
|
||||
// template<nlohmann::detail::value_t... T>
|
||||
// JsonMaybeValue JsonMaybeValue::asOneOf() {
|
||||
// if (this->isError()) return *this;
|
||||
// bool isOneOf = (... || jsonConvertibleTo(self().m_json.type(), T));
|
||||
// if (!isOneOf) {
|
||||
// this->setError(
|
||||
// self().m_hierarchy + ": Invalid type \"" +
|
||||
// self().m_json.type_name() + "\", expected one of \"" +
|
||||
// (jsonValueTypeToString(T), ...) + "\""
|
||||
// );
|
||||
// }
|
||||
// m_inferType = false;
|
||||
// return *this;
|
||||
// }
|
||||
|
||||
// template<class Json>
|
||||
// template<nlohmann::detail::value_t T>
|
||||
// JsonMaybeValue JsonMaybeValue::is() {
|
||||
// if (this->isError()) return *this;
|
||||
// self().m_hasValue = jsonConvertibleTo(self().m_json.type(), T);
|
||||
// m_inferType = false;
|
||||
// return *this;
|
||||
// }
|
||||
|
||||
// template<class Json>
|
||||
// template<class T>
|
||||
// JsonMaybeValue JsonMaybeValue::validate(JsonValueValidator<T> validator) {
|
||||
// if (this->isError()) return *this;
|
||||
// try {
|
||||
// if (!validator(self().m_json.template get<T>())) {
|
||||
// this->setError(self().m_hierarchy + ": Invalid value format");
|
||||
// }
|
||||
// } catch(...) {
|
||||
// this->setError(
|
||||
// self().m_hierarchy + ": Invalid type \"" +
|
||||
// std::string(self().m_json.type_name()) + "\""
|
||||
// );
|
||||
// }
|
||||
// return *this;
|
||||
// }
|
||||
|
||||
// template<class Json>
|
||||
// template<class T>
|
||||
// JsonMaybeValue JsonMaybeValue::inferType() {
|
||||
// if (this->isError() || !m_inferType) return *this;
|
||||
// return this->as<getJsonType<T>()>();
|
||||
// }
|
||||
|
||||
// template<class Json>
|
||||
// template<class T>
|
||||
// JsonMaybeValue JsonMaybeValue::intoRaw(T& target) {
|
||||
// if (this->isError()) return *this;
|
||||
// target = self().m_json;
|
||||
// return *this;
|
||||
// }
|
||||
|
||||
// template<class Json>
|
||||
// template<class T>
|
||||
// JsonMaybeValue JsonMaybeValue::into(T& target) {
|
||||
// return this->intoAs<T, T>(target);
|
||||
// }
|
||||
|
||||
// template<class Json>
|
||||
// template<class T>
|
||||
// JsonMaybeValue JsonMaybeValue::into(std::optional<T>& target) {
|
||||
// return this->intoAs<T, std::optional<T>>(target);
|
||||
// }
|
||||
|
||||
// template<class Json>
|
||||
// template<class A, class T>
|
||||
// JsonMaybeValue JsonMaybeValue::intoAs(T& target) {
|
||||
// this->inferType<A>();
|
||||
// if (this->isError()) return *this;
|
||||
// try {
|
||||
// target = self().m_json.template get<A>();
|
||||
// } catch(...) {
|
||||
// this->setError(
|
||||
// self().m_hierarchy + ": Invalid type \"" +
|
||||
// std::string(self().m_json.type_name()) + "\""
|
||||
// );
|
||||
// }
|
||||
// return *this;
|
||||
// }
|
||||
|
||||
// template<class Json>
|
||||
// template<class T>
|
||||
// T JsonMaybeValue::get() {
|
||||
// this->inferType<T>();
|
||||
// if (this->isError()) return T();
|
||||
// try {
|
||||
// return self().m_json.template get<T>();
|
||||
// } catch(...) {
|
||||
// this->setError(
|
||||
// self().m_hierarchy + ": Invalid type to get \"" +
|
||||
// std::string(self().m_json.type_name()) + "\""
|
||||
// );
|
||||
// }
|
||||
// return T();
|
||||
// }
|
||||
|
||||
|
||||
JsonMaybeObject JsonMaybeValue::obj() {
|
||||
this->as<value_t::Object>();
|
||||
return JsonMaybeObject(self().m_checker, self().m_json, self().m_hierarchy, self().m_hasValue);
|
||||
}
|
||||
|
||||
// template<class Json>
|
||||
// template<class T>
|
||||
// struct JsonMaybeValue::Iterator {
|
||||
// std::vector<T> m_values;
|
||||
|
||||
// using iterator = typename std::vector<T>::iterator;
|
||||
// using const_iterator = typename std::vector<T>::const_iterator;
|
||||
|
||||
// iterator begin() {
|
||||
// return m_values.begin();
|
||||
// }
|
||||
// iterator end() {
|
||||
// return m_values.end();
|
||||
// }
|
||||
|
||||
// const_iterator begin() const {
|
||||
// return m_values.begin();
|
||||
// }
|
||||
// const_iterator end() const {
|
||||
// return m_values.end();
|
||||
// }
|
||||
// };
|
||||
|
||||
|
||||
JsonMaybeValue JsonMaybeValue::at(size_t i) {
|
||||
this->as<value_t::Array>();
|
||||
if (this->isError()) return *this;
|
||||
|
@ -216,7 +65,6 @@ JsonMaybeValue JsonMaybeValue::at(size_t i) {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
typename JsonMaybeValue::template Iterator<JsonMaybeValue> JsonMaybeValue::iterate() {
|
||||
this->as<value_t::Array>();
|
||||
Iterator<JsonMaybeValue> iter;
|
||||
|
@ -232,7 +80,6 @@ typename JsonMaybeValue::template Iterator<JsonMaybeValue> JsonMaybeValue::itera
|
|||
return iter;
|
||||
}
|
||||
|
||||
|
||||
typename JsonMaybeValue::template Iterator<std::pair<std::string, JsonMaybeValue>> JsonMaybeValue::items() {
|
||||
this->as<value_t::Object>();
|
||||
Iterator<std::pair<std::string, JsonMaybeValue>> iter;
|
||||
|
@ -248,33 +95,26 @@ typename JsonMaybeValue::template Iterator<std::pair<std::string, JsonMaybeValue
|
|||
return iter;
|
||||
}
|
||||
|
||||
|
||||
JsonMaybeObject::JsonMaybeObject(
|
||||
JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue
|
||||
) :
|
||||
JsonMaybeSomething(checker, json, hierarchy, hasValue) {}
|
||||
|
||||
) : JsonMaybeSomething(checker, json, hierarchy, hasValue) {}
|
||||
|
||||
JsonMaybeSomething& JsonMaybeObject::self() {
|
||||
return *static_cast<JsonMaybeSomething*>(this);
|
||||
}
|
||||
|
||||
|
||||
void JsonMaybeObject::addKnownKey(std::string const& key) {
|
||||
m_knownKeys.insert(key);
|
||||
}
|
||||
|
||||
|
||||
matjson::Value& JsonMaybeObject::json() {
|
||||
return self().m_json;
|
||||
}
|
||||
|
||||
|
||||
JsonMaybeValue JsonMaybeObject::emptyValue() {
|
||||
return JsonMaybeValue(self().m_checker, self().m_json, "", false);
|
||||
}
|
||||
|
||||
|
||||
JsonMaybeValue JsonMaybeObject::has(std::string const& key) {
|
||||
this->addKnownKey(key);
|
||||
if (this->isError()) return emptyValue();
|
||||
|
@ -284,7 +124,6 @@ JsonMaybeValue JsonMaybeObject::has(std::string const& key) {
|
|||
return JsonMaybeValue(self().m_checker, self().m_json[key], key, true);
|
||||
}
|
||||
|
||||
|
||||
JsonMaybeValue JsonMaybeObject::needs(std::string const& key) {
|
||||
this->addKnownKey(key);
|
||||
if (this->isError()) return emptyValue();
|
||||
|
@ -295,7 +134,6 @@ JsonMaybeValue JsonMaybeObject::needs(std::string const& key) {
|
|||
return JsonMaybeValue(self().m_checker, self().m_json[key], key, true);
|
||||
}
|
||||
|
||||
|
||||
// TODO: gross hack :3 (ctrl+f this comment to find the other part)
|
||||
extern bool s_jsonCheckerShouldCheckUnknownKeys;
|
||||
bool s_jsonCheckerShouldCheckUnknownKeys = true;
|
||||
|
@ -309,20 +147,270 @@ void JsonMaybeObject::checkUnknownKeys() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
JsonChecker::JsonChecker(matjson::Value& json) : m_json(json), m_result(std::monostate()) {}
|
||||
|
||||
|
||||
bool JsonChecker::isError() const {
|
||||
return std::holds_alternative<std::string>(m_result);
|
||||
}
|
||||
|
||||
|
||||
std::string JsonChecker::getError() const {
|
||||
return std::get<std::string>(m_result);
|
||||
}
|
||||
|
||||
|
||||
JsonMaybeValue JsonChecker::root(std::string const& hierarchy) {
|
||||
return JsonMaybeValue(*this, m_json, hierarchy, true);
|
||||
}
|
||||
|
||||
static const char* matJsonTypeToString(matjson::Type ty) {
|
||||
switch (ty) {
|
||||
case matjson::Type::Null: return "null";
|
||||
case matjson::Type::Bool: return "bool";
|
||||
case matjson::Type::Number: return "number";
|
||||
case matjson::Type::String: return "string";
|
||||
case matjson::Type::Array: return "array";
|
||||
case matjson::Type::Object: return "object";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// This is used for null JsonExpectedValues (for example when doing
|
||||
// `json.has("key")` where "key" doesn't exist)
|
||||
static matjson::Value NULL_SCOPED_VALUE = nullptr;
|
||||
|
||||
class JsonExpectedValue::Impl final {
|
||||
public:
|
||||
// Values shared between JsonExpectedValues related to the same JSON
|
||||
struct Shared final {
|
||||
matjson::Value originalJson;
|
||||
std::optional<std::string> error;
|
||||
std::string rootScopeName;
|
||||
|
||||
Shared(matjson::Value const& json, std::string_view rootScopeName)
|
||||
: originalJson(json), rootScopeName(rootScopeName) {}
|
||||
};
|
||||
|
||||
// this may be null if the JsonExpectedValue is a "null" value
|
||||
std::shared_ptr<Shared> shared;
|
||||
matjson::Value& scope;
|
||||
std::string scopeName;
|
||||
std::string key;
|
||||
std::unordered_set<std::string> knownKeys;
|
||||
|
||||
Impl()
|
||||
: shared(nullptr),
|
||||
scope(NULL_SCOPED_VALUE)
|
||||
{}
|
||||
|
||||
// Create a root Impl
|
||||
Impl(std::shared_ptr<Shared> shared)
|
||||
: shared(shared),
|
||||
scope(shared->originalJson),
|
||||
scopeName(shared->rootScopeName)
|
||||
{}
|
||||
|
||||
// Create a derived Impl
|
||||
Impl(Impl* from, matjson::Value& scope, std::string_view key)
|
||||
: shared(from->shared),
|
||||
scope(scope),
|
||||
scopeName(fmt::format("{}.{}", from->scopeName, key)),
|
||||
key(key)
|
||||
{}
|
||||
};
|
||||
|
||||
JsonExpectedValue::JsonExpectedValue()
|
||||
: m_impl(std::make_unique<Impl>())
|
||||
{}
|
||||
JsonExpectedValue::JsonExpectedValue(Impl* from, matjson::Value& scope, std::string_view key)
|
||||
: m_impl(std::make_unique<Impl>(from, scope, key))
|
||||
{}
|
||||
JsonExpectedValue::JsonExpectedValue(matjson::Value const& json, std::string_view rootScopeName)
|
||||
: m_impl(std::make_unique<Impl>(std::make_shared<matjson::Value>(json, rootScopeName)))
|
||||
{}
|
||||
JsonExpectedValue::~JsonExpectedValue() {}
|
||||
|
||||
JsonExpectedValue::JsonExpectedValue(JsonExpectedValue&&) = default;
|
||||
JsonExpectedValue& JsonExpectedValue::operator=(JsonExpectedValue&&) = default;
|
||||
|
||||
matjson::Value const& JsonExpectedValue::getJSONRef() const {
|
||||
return m_impl->scope;
|
||||
}
|
||||
matjson::Value JsonExpectedValue::json() const {
|
||||
return m_impl->scope;
|
||||
}
|
||||
std::string JsonExpectedValue::key() const {
|
||||
return m_impl->key;
|
||||
}
|
||||
|
||||
bool JsonExpectedValue::hasError() const {
|
||||
return !m_impl->shared || m_impl->shared->error.has_value();
|
||||
}
|
||||
void JsonExpectedValue::setError(std::string_view error) {
|
||||
m_impl->shared->error.emplace(fmt::format("[{}]: {}", m_impl->scopeName, error));
|
||||
}
|
||||
|
||||
bool JsonExpectedValue::is(matjson::Type type) const {
|
||||
if (this->hasError()) return false;
|
||||
return m_impl->scope.type() == type;
|
||||
}
|
||||
bool JsonExpectedValue::isNull() const {
|
||||
return this->is(matjson::Type::Null);
|
||||
}
|
||||
bool JsonExpectedValue::isBool() const {
|
||||
return this->is(matjson::Type::Bool);
|
||||
}
|
||||
bool JsonExpectedValue::isNumber() const {
|
||||
return this->is(matjson::Type::Number);
|
||||
}
|
||||
bool JsonExpectedValue::isString() const {
|
||||
return this->is(matjson::Type::String);
|
||||
}
|
||||
bool JsonExpectedValue::isArray() const {
|
||||
return this->is(matjson::Type::Array);
|
||||
}
|
||||
bool JsonExpectedValue::isObject() const {
|
||||
return this->is(matjson::Type::Object);
|
||||
}
|
||||
JsonExpectedValue& JsonExpectedValue::assertIs(matjson::Type type) {
|
||||
if (this->hasError()) return *this;
|
||||
if (m_impl->scope.type() != type) {
|
||||
this->setError(
|
||||
"invalid type {}, expected {}",
|
||||
matJsonTypeToString(m_impl->scope.type()),
|
||||
matJsonTypeToString(type)
|
||||
);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
JsonExpectedValue& JsonExpectedValue::assertIsNull() {
|
||||
return this->assertIs(matjson::Type::Null);
|
||||
}
|
||||
JsonExpectedValue& JsonExpectedValue::assertIsBool() {
|
||||
return this->assertIs(matjson::Type::Bool);
|
||||
}
|
||||
JsonExpectedValue& JsonExpectedValue::assertIsNumber() {
|
||||
return this->assertIs(matjson::Type::Number);
|
||||
}
|
||||
JsonExpectedValue& JsonExpectedValue::assertIsString() {
|
||||
return this->assertIs(matjson::Type::String);
|
||||
}
|
||||
JsonExpectedValue& JsonExpectedValue::assertIsArray() {
|
||||
return this->assertIs(matjson::Type::Array);
|
||||
}
|
||||
JsonExpectedValue& JsonExpectedValue::assertIsObject() {
|
||||
return this->assertIs(matjson::Type::Object);
|
||||
}
|
||||
JsonExpectedValue& JsonExpectedValue::assertIs(std::initializer_list<matjson::Type> types) {
|
||||
if (this->hasError()) return *this;
|
||||
if (!std::any_of(types.begin(), types.end(), [this](matjson::Type t) { return t == m_impl->scope.type(); })) {
|
||||
this->setError(
|
||||
"invalid type {}, expected either {}",
|
||||
matJsonTypeToString(m_impl->scope.type()),
|
||||
ranges::join(ranges::map<std::vector<std::string>>(types, [](matjson::Type t) {
|
||||
return matJsonTypeToString(t);
|
||||
}), " or ")
|
||||
);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonExpectedValue JsonExpectedValue::has(std::string_view key) {
|
||||
if (this->hasError()) {
|
||||
return JsonExpectedValue();
|
||||
}
|
||||
if (!this->assertIs(matjson::Type::Object)) {
|
||||
return JsonExpectedValue();
|
||||
}
|
||||
if (!m_impl->scope.contains(key)) {
|
||||
return JsonExpectedValue();
|
||||
}
|
||||
return JsonExpectedValue(m_impl.get(), m_impl->scope[key], key);
|
||||
}
|
||||
JsonExpectedValue JsonExpectedValue::needs(std::string_view key) {
|
||||
if (this->hasError()) {
|
||||
return JsonExpectedValue();
|
||||
}
|
||||
if (!this->assertIs(matjson::Type::Object)) {
|
||||
return JsonExpectedValue();
|
||||
}
|
||||
if (!m_impl->scope.contains(key)) {
|
||||
this->setError("missing required key {}", key);
|
||||
return JsonExpectedValue();
|
||||
}
|
||||
return JsonExpectedValue(m_impl.get(), m_impl->scope[key], key);
|
||||
}
|
||||
std::vector<std::pair<std::string, JsonExpectedValue>> JsonExpectedValue::properties() {
|
||||
if (this->hasError()) {
|
||||
return std::vector<std::pair<std::string, JsonExpectedValue>>();
|
||||
}
|
||||
if (!this->assertIs(matjson::Type::Object)) {
|
||||
return std::vector<std::pair<std::string, JsonExpectedValue>>();
|
||||
}
|
||||
std::vector<std::pair<std::string, JsonExpectedValue>> res;
|
||||
for (auto& [k, v] : m_impl->scope.as_object()) {
|
||||
res.push_back(std::make_pair(k, JsonExpectedValue(m_impl.get(), v, k)));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
void JsonExpectedValue::checkUnknownKeys() {
|
||||
for (auto& [key, _] : this->properties()) {
|
||||
if (!m_impl->knownKeys.count(key)) {
|
||||
log::warn("{} contains unknown key \"{}\"", m_impl->scopeName, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t JsonExpectedValue::length() {
|
||||
if (this->hasError()) {
|
||||
return 0;
|
||||
}
|
||||
if (!this->assertIs(matjson::Type::Array)) {
|
||||
return 0;
|
||||
}
|
||||
return m_impl->scope.as_array().size();
|
||||
}
|
||||
JsonExpectedValue JsonExpectedValue::at(size_t index) {
|
||||
if (this->hasError()) {
|
||||
return JsonExpectedValue();
|
||||
}
|
||||
if (!this->assertIs(matjson::Type::Array)) {
|
||||
return JsonExpectedValue();
|
||||
}
|
||||
auto& arr = m_impl->scope.as_array();
|
||||
if (arr.size() <= index) {
|
||||
this->setError(
|
||||
"array expected to have at least size {}, but its size was only {}",
|
||||
index + 1, arr.size()
|
||||
);
|
||||
return JsonExpectedValue();
|
||||
}
|
||||
return JsonExpectedValue(m_impl.get(), arr.at(index), std::to_string(index));
|
||||
}
|
||||
std::vector<JsonExpectedValue> JsonExpectedValue::items() {
|
||||
if (this->hasError()) {
|
||||
return std::vector<JsonExpectedValue>();
|
||||
}
|
||||
if (!this->assertIs(matjson::Type::Object)) {
|
||||
return std::vector<JsonExpectedValue>();
|
||||
}
|
||||
std::vector<JsonExpectedValue> res;
|
||||
size_t i = 0;
|
||||
for (auto& v : m_impl->scope.as_array()) {
|
||||
res.push_back(JsonExpectedValue(m_impl.get(), v, std::to_string(i++)));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
JsonExpectedValue::operator bool() const {
|
||||
return !this->hasError();
|
||||
}
|
||||
|
||||
Result<> JsonExpectedValue::ok() {
|
||||
if (m_impl->shared && m_impl->shared->error) {
|
||||
return Err(*m_impl->shared->error);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
JsonExpectedValue geode::checkJson(matjson::Value const& json, std::string_view rootScopeName) {
|
||||
return JsonExpectedValue(json, rootScopeName);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue