diff options
| author | Stefan Boberg <[email protected]> | 2023-11-28 14:19:55 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-11-28 14:19:55 +0100 |
| commit | 76e0fe5d3bdb06691ee534954acdbaf56da0577d (patch) | |
| tree | 200d3dcdfe9b0f2dd14c2fb8c8650235fee890a0 /src | |
| parent | tracing for gcv2 (#574) (diff) | |
| download | zen-76e0fe5d3bdb06691ee534954acdbaf56da0577d.tar.xz zen-76e0fe5d3bdb06691ee534954acdbaf56da0577d.zip | |
moved LuaConfig code so it can be used outside of config.cpp (#575)
Diffstat (limited to 'src')
| -rw-r--r-- | src/zenserver/config.cpp | 645 | ||||
| -rw-r--r-- | src/zenserver/config/luaconfig.cpp | 461 | ||||
| -rw-r--r-- | src/zenserver/config/luaconfig.h | 139 |
3 files changed, 705 insertions, 540 deletions
diff --git a/src/zenserver/config.cpp b/src/zenserver/config.cpp index 92d6c7f7d..fe92613f4 100644 --- a/src/zenserver/config.cpp +++ b/src/zenserver/config.cpp @@ -2,6 +2,7 @@ #include "config.h" +#include "config/luaconfig.h" #include "diag/logging.h" #include <zencore/crypto.h> @@ -175,592 +176,156 @@ MakeSafePath(const std::string_view Path) #endif }; -namespace LuaConfig { - - void EscapeBackslash(std::string& InOutString) - { - std::size_t BackslashPos = InOutString.find('\\'); - if (BackslashPos != std::string::npos) - { - std::size_t Offset = 0; - zen::ExtendableStringBuilder<512> PathBuilder; - while (BackslashPos != std::string::npos) - { - PathBuilder.Append(InOutString.substr(Offset, BackslashPos + 1 - Offset)); - PathBuilder.Append('\\'); - Offset = BackslashPos + 1; - BackslashPos = InOutString.find('\\', Offset); - } - PathBuilder.Append(InOutString.substr(Offset, BackslashPos)); - InOutString = PathBuilder.ToString(); +class CachePolicyOption : public LuaConfig::OptionValue +{ +public: + CachePolicyOption(UpstreamCachePolicy& Value) : Value(Value) {} + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override + { + switch (Value) + { + case UpstreamCachePolicy::Read: + StringBuilder.Append("readonly"); + break; + case UpstreamCachePolicy::Write: + StringBuilder.Append("writeonly"); + break; + case UpstreamCachePolicy::Disabled: + StringBuilder.Append("disabled"); + break; + case UpstreamCachePolicy::ReadWrite: + StringBuilder.Append("readwrite"); + break; + default: + ZEN_ASSERT(false); } } - - class OptionValue - { - public: - virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) = 0; - virtual void Parse(sol::object Object) = 0; - - virtual ~OptionValue() {} - }; - - typedef std::shared_ptr<OptionValue> TOptionValue; - - class StringOption : public OptionValue + virtual void Parse(sol::object Object) override { - public: - StringOption(std::string& Value) : Value(Value) {} - virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override + std::string PolicyString = Object.as<std::string>(); + if (PolicyString == "readonly") { - StringBuilder.Append(fmt::format("\"{}\"", Value)); + Value = UpstreamCachePolicy::Read; } - virtual void Parse(sol::object Object) override { Value = Object.as<std::string>(); } - std::string& Value; - }; - - class FilePathOption : public OptionValue - { - public: - FilePathOption(std::filesystem::path& Value) : Value(Value) {} - virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override + else if (PolicyString == "writeonly") { - std::string Path = Value.string(); - EscapeBackslash(Path); - StringBuilder.Append(fmt::format("\"{}\"", Path)); + Value = UpstreamCachePolicy::Write; } - virtual void Parse(sol::object Object) override + else if (PolicyString == "disabled") { - std::string Str = Object.as<std::string>(); - if (!Str.empty()) - { - Value = MakeSafePath(Str); - } + Value = UpstreamCachePolicy::Disabled; } - std::filesystem::path& Value; - }; - - class BoolOption : public OptionValue - { - public: - BoolOption(bool& Value) : Value(Value) {} - virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override + else if (PolicyString == "readwrite") { - StringBuilder.Append(Value ? "true" : "false"); + Value = UpstreamCachePolicy::ReadWrite; } - virtual void Parse(sol::object Object) override { Value = Object.as<bool>(); } - bool& Value; - }; - - class CachePolicyOption : public OptionValue - { - public: - CachePolicyOption(UpstreamCachePolicy& Value) : Value(Value) {} - virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override - { - switch (Value) - { - case UpstreamCachePolicy::Read: - StringBuilder.Append("readonly"); - break; - case UpstreamCachePolicy::Write: - StringBuilder.Append("writeonly"); - break; - case UpstreamCachePolicy::Disabled: - StringBuilder.Append("disabled"); - break; - case UpstreamCachePolicy::ReadWrite: - StringBuilder.Append("readwrite"); - break; - default: - ZEN_ASSERT(false); - } - } - virtual void Parse(sol::object Object) override - { - std::string PolicyString = Object.as<std::string>(); - if (PolicyString == "readonly") - { - Value = UpstreamCachePolicy::Read; - } - else if (PolicyString == "writeonly") - { - Value = UpstreamCachePolicy::Write; - } - else if (PolicyString == "disabled") - { - Value = UpstreamCachePolicy::Disabled; - } - else if (PolicyString == "readwrite") - { - Value = UpstreamCachePolicy::ReadWrite; - } - } - UpstreamCachePolicy& Value; - }; - - template<Integral T> - class NumberOption : public OptionValue - { - public: - NumberOption(T& Value) : Value(Value) {} - virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override - { - StringBuilder.Append(fmt::format("{}", Value)); - } - virtual void Parse(sol::object Object) override { Value = Object.as<T>(); } - T& Value; - }; + } + UpstreamCachePolicy& Value; +}; - class LuaContainerWriter +class ZenAuthConfigOption : public LuaConfig::OptionValue +{ +public: + ZenAuthConfigOption(ZenAuthConfig& Value) : Value(Value) {} + virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override { - public: - LuaContainerWriter(zen::StringBuilderBase& StringBuilder, std::string_view Indent) - : StringBuilder(StringBuilder) - , InitialIndent(Indent.length()) - , LocalIndent(Indent) - { - StringBuilder.Append("{\n"); - LocalIndent.push_back('\t'); - } - ~LuaContainerWriter() + if (Value.OpenIdProviders.empty()) { - LocalIndent.pop_back(); - StringBuilder.Append(LocalIndent); - StringBuilder.Append("}"); + StringBuilder.Append("{}"); + return; } - - void BeginContainer(std::string_view Name) + LuaConfig::LuaContainerWriter Writer(StringBuilder, Indent); + for (const ZenOpenIdProviderConfig& Config : Value.OpenIdProviders) { - StringBuilder.Append(LocalIndent); - if (!Name.empty()) - { - StringBuilder.Append(Name); - StringBuilder.Append(" = {\n"); - } - else + Writer.BeginContainer(""); { - StringBuilder.Append("{\n"); + Writer.WriteValue("name", Config.Name); + Writer.WriteValue("url", Config.Url); + Writer.WriteValue("clientid", Config.ClientId); } - LocalIndent.push_back('\t'); + Writer.EndContainer(); } - void WriteValue(std::string_view Name, std::string_view Value) - { - if (Name.empty()) - { - StringBuilder.Append(fmt::format("{}\"{}\",\n", LocalIndent, Value)); - } - else - { - StringBuilder.Append(fmt::format("{}{} = \"{}\",\n", LocalIndent, Name, Value)); - } - } - void EndContainer() - { - LocalIndent.pop_back(); - StringBuilder.Append(LocalIndent); - StringBuilder.Append("}"); - StringBuilder.Append(",\n"); - } - - private: - zen::StringBuilderBase& StringBuilder; - const std::size_t InitialIndent; - std::string LocalIndent; - }; - - class StringArrayOption : public OptionValue + } + virtual void Parse(sol::object Object) override { - public: - StringArrayOption(std::vector<std::string>& Value) : Value(Value) {} - virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override + if (sol::optional<sol::table> OpenIdProviders = Object.as<sol::table>()) { - if (Value.empty()) - { - StringBuilder.Append("{}"); - } - if (Value.size() == 1) - { - StringBuilder.Append(fmt::format("\"{}\"", Value[0])); - } - else + for (const auto& Kv : OpenIdProviders.value()) { - LuaContainerWriter Writer(StringBuilder, Indent); - for (std::string String : Value) + if (sol::optional<sol::table> OpenIdProvider = Kv.second.as<sol::table>()) { - Writer.WriteValue("", String); - } - } - } - virtual void Parse(sol::object Object) override - { - if (Object.get_type() == sol::type::string) - { - Value.push_back(Object.as<std::string>()); - } - else if (Object.get_type() == sol::type::table) - { - for (const auto& Kv : Object.as<sol::table>()) - { - Value.push_back(Kv.second.as<std::string>()); - } - } - } - - private: - std::vector<std::string>& Value; - }; + std::string Name = OpenIdProvider.value().get_or("name", std::string("Default")); + std::string Url = OpenIdProvider.value().get_or("url", std::string()); + std::string ClientId = OpenIdProvider.value().get_or("clientid", std::string()); - class ZenAuthConfigOption : public OptionValue - { - public: - ZenAuthConfigOption(ZenAuthConfig& Value) : Value(Value) {} - virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override - { - if (Value.OpenIdProviders.empty()) - { - StringBuilder.Append("{}"); - return; - } - LuaContainerWriter Writer(StringBuilder, Indent); - for (const ZenOpenIdProviderConfig& Config : Value.OpenIdProviders) - { - Writer.BeginContainer(""); - { - Writer.WriteValue("name", Config.Name); - Writer.WriteValue("url", Config.Url); - Writer.WriteValue("clientid", Config.ClientId); + Value.OpenIdProviders.push_back({.Name = std::move(Name), .Url = std::move(Url), .ClientId = std::move(ClientId)}); } - Writer.EndContainer(); } } - virtual void Parse(sol::object Object) override - { - if (sol::optional<sol::table> OpenIdProviders = Object.as<sol::table>()) - { - for (const auto& Kv : OpenIdProviders.value()) - { - if (sol::optional<sol::table> OpenIdProvider = Kv.second.as<sol::table>()) - { - std::string Name = OpenIdProvider.value().get_or("name", std::string("Default")); - std::string Url = OpenIdProvider.value().get_or("url", std::string()); - std::string ClientId = OpenIdProvider.value().get_or("clientid", std::string()); - - Value.OpenIdProviders.push_back({.Name = std::move(Name), .Url = std::move(Url), .ClientId = std::move(ClientId)}); - } - } - } - } - ZenAuthConfig& Value; - }; + } + ZenAuthConfig& Value; +}; - class ZenObjectStoreConfigOption : public OptionValue +class ZenObjectStoreConfigOption : public LuaConfig::OptionValue +{ +public: + ZenObjectStoreConfigOption(ZenObjectStoreConfig& Value) : Value(Value) {} + virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override { - public: - ZenObjectStoreConfigOption(ZenObjectStoreConfig& Value) : Value(Value) {} - virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override + if (Value.Buckets.empty()) { - if (Value.Buckets.empty()) - { - StringBuilder.Append("{}"); - return; - } - LuaContainerWriter Writer(StringBuilder, Indent); - for (const ZenObjectStoreConfig::BucketConfig& Config : Value.Buckets) - { - Writer.BeginContainer(""); - { - Writer.WriteValue("name", Config.Name); - std::string Directory = Config.Directory.string(); - EscapeBackslash(Directory); - Writer.WriteValue("directory", Directory); - } - Writer.EndContainer(); - } + StringBuilder.Append("{}"); + return; } - virtual void Parse(sol::object Object) override + LuaConfig::LuaContainerWriter Writer(StringBuilder, Indent); + for (const ZenObjectStoreConfig::BucketConfig& Config : Value.Buckets) { - if (sol::optional<sol::table> Buckets = Object.as<sol::table>()) + Writer.BeginContainer(""); { - for (const auto& Kv : Buckets.value()) - { - if (sol::optional<sol::table> Bucket = Kv.second.as<sol::table>()) - { - std::string Name = Bucket.value().get_or("name", std::string("Default")); - std::string Directory = Bucket.value().get_or("directory", std::string()); - - Value.Buckets.push_back({.Name = std::move(Name), .Directory = MakeSafePath(Directory)}); - } - } + Writer.WriteValue("name", Config.Name); + std::string Directory = Config.Directory.string(); + LuaConfig::EscapeBackslash(Directory); + Writer.WriteValue("directory", Directory); } + Writer.EndContainer(); } - ZenObjectStoreConfig& Value; - }; - - std::shared_ptr<OptionValue> MakeOption(std::string& Value) { return std::make_shared<StringOption>(Value); }; - - std::shared_ptr<OptionValue> MakeOption(std::filesystem::path& Value) { return std::make_shared<FilePathOption>(Value); }; - - template<Integral T> - std::shared_ptr<OptionValue> MakeOption(T& Value) - { - return std::make_shared<NumberOption<T>>(Value); - }; - - std::shared_ptr<OptionValue> MakeOption(bool& Value) { return std::make_shared<BoolOption>(Value); }; - - std::shared_ptr<OptionValue> MakeOption(UpstreamCachePolicy& Value) { return std::make_shared<CachePolicyOption>(Value); }; - - std::shared_ptr<OptionValue> MakeOption(std::vector<std::string>& Value) { return std::make_shared<StringArrayOption>(Value); }; - - std::shared_ptr<OptionValue> MakeOption(ZenAuthConfig& Value) { return std::make_shared<ZenAuthConfigOption>(Value); }; - - std::shared_ptr<OptionValue> MakeOption(ZenObjectStoreConfig& Value) { return std::make_shared<ZenObjectStoreConfigOption>(Value); }; - - struct Option - { - std::string CommandLineOptionName; - TOptionValue Value; - }; - - struct Options + } + virtual void Parse(sol::object Object) override { - public: - template<typename T> - void AddOption(std::string_view Key, T& Value, std::string_view CommandLineOptionName = "") - { - OptionMap.insert_or_assign(std::string(Key), - Option{.CommandLineOptionName = std::string(CommandLineOptionName), .Value = MakeOption(Value)}); - }; - - void Parse(const std::filesystem::path& Path, const cxxopts::ParseResult& CmdLineResult) + if (sol::optional<sol::table> Buckets = Object.as<sol::table>()) { - zen::IoBuffer LuaScript = zen::IoBufferBuilder::MakeFromFile(Path); - - if (LuaScript) - { - sol::state lua; - - lua.open_libraries(sol::lib::base); - - lua.set_function("getenv", [&](const std::string env) -> sol::object { -#if ZEN_PLATFORM_WINDOWS - std::wstring EnvVarValue; - size_t RequiredSize = 0; - std::wstring EnvWide = zen::Utf8ToWide(env); - _wgetenv_s(&RequiredSize, nullptr, 0, EnvWide.c_str()); - - if (RequiredSize == 0) - return sol::make_object(lua, sol::lua_nil); - - EnvVarValue.resize(RequiredSize); - _wgetenv_s(&RequiredSize, EnvVarValue.data(), RequiredSize, EnvWide.c_str()); - return sol::make_object(lua, zen::WideToUtf8(EnvVarValue.c_str())); -#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC - char* EnvVariable = getenv(env.c_str()); - if (EnvVariable == nullptr) + for (const auto& Kv : Buckets.value()) { - return sol::make_object(lua, sol::lua_nil); - } - return sol::make_object(lua, EnvVariable); -#else - ZEN_UNUSED(env); - return sol::make_object(lua, sol::lua_nil); -#endif - }); - - try + if (sol::optional<sol::table> Bucket = Kv.second.as<sol::table>()) { - sol::load_result config = lua.load(std::string_view((const char*)LuaScript.Data(), LuaScript.Size()), "zen_cfg"); - - if (!config.valid()) - { - sol::error err = config; - - std::string ErrorString = sol::to_string(config.status()); - - throw std::runtime_error(fmt::format("{} error: {}", ErrorString, err.what())); - } + std::string Name = Bucket.value().get_or("name", std::string("Default")); + std::string Directory = Bucket.value().get_or("directory", std::string()); - config(); + Value.Buckets.push_back({.Name = std::move(Name), .Directory = LuaConfig::MakeSafePath(Directory)}); } - catch (std::exception& e) - { - throw std::runtime_error(fmt::format("failed to load config script ('{}'): {}", Path, e.what()).c_str()); - } - - Parse(lua, CmdLineResult); - } - } - - void Parse(const sol::state& LuaState, const cxxopts::ParseResult& CmdLineResult) - { - for (auto It : LuaState) - { - sol::object Key = It.first; - sol::type KeyType = Key.get_type(); - if (KeyType == sol::type::string) - { - sol::type ValueType = It.second.get_type(); - switch (ValueType) - { - case sol::type::table: - { - std::string Name = Key.as<std::string>(); - if (Name.starts_with("_")) - { - continue; - } - if (Name == "base") - { - continue; - } - Traverse(It.second.as<sol::table>(), Name, CmdLineResult); - } - break; - default: - break; - } - } - } - } - - void Touch(std::string_view Key) { UsedKeys.insert(std::string(Key)); } - - void Print(zen::StringBuilderBase& SB, const cxxopts::ParseResult& CmdLineResult) - { - for (auto It : OptionMap) - { - if (CmdLineResult.count(It.second.CommandLineOptionName) != 0) - { - UsedKeys.insert(It.first); - } - } - - std::vector<std::string> SortedKeys(UsedKeys.begin(), UsedKeys.end()); - std::sort(SortedKeys.begin(), SortedKeys.end()); - auto GetTablePath = [](const std::string& Key) -> std::vector<std::string> { - std::vector<std::string> Path; - zen::ForEachStrTok(Key, '.', [&Path](std::string_view Part) { - Path.push_back(std::string(Part)); - return true; - }); - return Path; - }; - std::vector<std::string> CurrentTablePath; - std::string Indent; - auto It = SortedKeys.begin(); - for (const std::string& Key : SortedKeys) - { - std::vector<std::string> KeyPath = GetTablePath(Key); - std::string Name = KeyPath.back(); - KeyPath.pop_back(); - if (CurrentTablePath != KeyPath) - { - size_t EqualCount = 0; - while (EqualCount < CurrentTablePath.size() && EqualCount < KeyPath.size() && - CurrentTablePath[EqualCount] == KeyPath[EqualCount]) - { - EqualCount++; - } - while (CurrentTablePath.size() > EqualCount) - { - CurrentTablePath.pop_back(); - Indent.pop_back(); - SB.Append(Indent); - SB.Append("}"); - if (CurrentTablePath.size() == EqualCount && !Indent.empty() && KeyPath.size() >= EqualCount) - { - SB.Append(","); - } - SB.Append("\n"); - if (Indent.empty()) - { - SB.Append("\n"); - } - } - while (EqualCount < KeyPath.size()) - { - SB.Append(Indent); - SB.Append(KeyPath[EqualCount]); - SB.Append(" = {\n"); - Indent.push_back('\t'); - CurrentTablePath.push_back(KeyPath[EqualCount]); - EqualCount++; - } - } - - SB.Append(Indent); - SB.Append(Name); - SB.Append(" = "); - OptionMap[Key].Value->Print(Indent, SB); - SB.Append(",\n"); - } - while (!CurrentTablePath.empty()) - { - Indent.pop_back(); - SB.Append(Indent); - SB.Append("}\n"); - CurrentTablePath.pop_back(); } } + } + ZenObjectStoreConfig& Value; +}; - private: - void Traverse(sol::table Table, std::string_view PathPrefix, const cxxopts::ParseResult& CmdLineResult) - { - for (auto It : Table) - { - sol::object Key = It.first; - sol::type KeyType = Key.get_type(); - if (KeyType == sol::type::string || KeyType == sol::type::number) - { - sol::type ValueType = It.second.get_type(); - switch (ValueType) - { - case sol::type::table: - case sol::type::string: - case sol::type::number: - case sol::type::boolean: - { - std::string Name = Key.as<std::string>(); - if (Name.starts_with("_")) - { - continue; - } - Name = std::string(PathPrefix) + "." + Key.as<std::string>(); - auto OptionIt = OptionMap.find(Name); - if (OptionIt != OptionMap.end()) - { - UsedKeys.insert(Name); - if (CmdLineResult.count(OptionIt->second.CommandLineOptionName) != 0) - { - continue; - } - OptionIt->second.Value->Parse(It.second); - continue; - } - if (ValueType == sol::type::table) - { - if (Name == "base") - { - continue; - } - Traverse(It.second.as<sol::table>(), Name, CmdLineResult); - } - } - break; - default: - break; - } - } - } - } +std::shared_ptr<LuaConfig::OptionValue> +MakeOption(zen::UpstreamCachePolicy& Value) +{ + return std::make_shared<CachePolicyOption>(Value); +}; - std::unordered_map<std::string, Option> OptionMap; - std::unordered_set<std::string> UsedKeys; - }; +std::shared_ptr<LuaConfig::OptionValue> +MakeOption(zen::ZenAuthConfig& Value) +{ + return std::make_shared<ZenAuthConfigOption>(Value); +}; -} // namespace LuaConfig +std::shared_ptr<LuaConfig::OptionValue> +MakeOption(zen::ZenObjectStoreConfig& Value) +{ + return std::make_shared<ZenObjectStoreConfigOption>(Value); +}; void ParseConfigFile(const std::filesystem::path& Path, diff --git a/src/zenserver/config/luaconfig.cpp b/src/zenserver/config/luaconfig.cpp new file mode 100644 index 000000000..cdc808cf6 --- /dev/null +++ b/src/zenserver/config/luaconfig.cpp @@ -0,0 +1,461 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "luaconfig.h" + +namespace zen::LuaConfig { + +std::string +MakeSafePath(const std::string_view Path) +{ +#if ZEN_PLATFORM_WINDOWS + if (Path.empty()) + { + return std::string(Path); + } + + std::string FixedPath(Path); + std::replace(FixedPath.begin(), FixedPath.end(), '/', '\\'); + if (!FixedPath.starts_with("\\\\?\\")) + { + FixedPath.insert(0, "\\\\?\\"); + } + return FixedPath; +#else + return std::string(Path); +#endif +}; + +void +EscapeBackslash(std::string& InOutString) +{ + std::size_t BackslashPos = InOutString.find('\\'); + if (BackslashPos != std::string::npos) + { + std::size_t Offset = 0; + zen::ExtendableStringBuilder<512> PathBuilder; + while (BackslashPos != std::string::npos) + { + PathBuilder.Append(InOutString.substr(Offset, BackslashPos + 1 - Offset)); + PathBuilder.Append('\\'); + Offset = BackslashPos + 1; + BackslashPos = InOutString.find('\\', Offset); + } + PathBuilder.Append(InOutString.substr(Offset, BackslashPos)); + InOutString = PathBuilder.ToString(); + } +} + +////////////////////////////////////////////////////////////////////////// + +BoolOption::BoolOption(bool& Value) : Value(Value) +{ +} + +void +BoolOption::Print(std::string_view, zen::StringBuilderBase& StringBuilder) +{ + StringBuilder.Append(Value ? "true" : "false"); +} + +void +BoolOption::Parse(sol::object Object) +{ + Value = Object.as<bool>(); +} + +////////////////////////////////////////////////////////////////////////// + +StringOption::StringOption(std::string& Value) : Value(Value) +{ +} + +void +StringOption::Print(std::string_view, zen::StringBuilderBase& StringBuilder) +{ + StringBuilder.Append(fmt::format("\"{}\"", Value)); +} + +void +StringOption::Parse(sol::object Object) +{ + Value = Object.as<std::string>(); +} + +////////////////////////////////////////////////////////////////////////// + +FilePathOption::FilePathOption(std::filesystem::path& Value) : Value(Value) +{ +} + +void +FilePathOption::Print(std::string_view, zen::StringBuilderBase& StringBuilder) +{ + std::string Path = Value.string(); + EscapeBackslash(Path); + StringBuilder.Append(fmt::format("\"{}\"", Path)); +} + +void +FilePathOption::Parse(sol::object Object) +{ + std::string Str = Object.as<std::string>(); + if (!Str.empty()) + { + Value = MakeSafePath(Str); + } +} + +////////////////////////////////////////////////////////////////////////// + +LuaContainerWriter::LuaContainerWriter(zen::StringBuilderBase& StringBuilder, std::string_view Indent) +: StringBuilder(StringBuilder) +, InitialIndent(Indent.length()) +, LocalIndent(Indent) +{ + StringBuilder.Append("{\n"); + LocalIndent.push_back('\t'); +} + +LuaContainerWriter::~LuaContainerWriter() +{ + LocalIndent.pop_back(); + StringBuilder.Append(LocalIndent); + StringBuilder.Append("}"); +} + +void +LuaContainerWriter::BeginContainer(std::string_view Name) +{ + StringBuilder.Append(LocalIndent); + if (!Name.empty()) + { + StringBuilder.Append(Name); + StringBuilder.Append(" = {\n"); + } + else + { + StringBuilder.Append("{\n"); + } + LocalIndent.push_back('\t'); +} + +void +LuaContainerWriter::WriteValue(std::string_view Name, std::string_view Value) +{ + if (Name.empty()) + { + StringBuilder.Append(fmt::format("{}\"{}\",\n", LocalIndent, Value)); + } + else + { + StringBuilder.Append(fmt::format("{}{} = \"{}\",\n", LocalIndent, Name, Value)); + } +} + +void +LuaContainerWriter::EndContainer() +{ + LocalIndent.pop_back(); + StringBuilder.Append(LocalIndent); + StringBuilder.Append("}"); + StringBuilder.Append(",\n"); +} + +////////////////////////////////////////////////////////////////////////// + +StringArrayOption::StringArrayOption(std::vector<std::string>& Value) : Value(Value) +{ +} + +void +StringArrayOption::Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) +{ + if (Value.empty()) + { + StringBuilder.Append("{}"); + } + if (Value.size() == 1) + { + StringBuilder.Append(fmt::format("\"{}\"", Value[0])); + } + else + { + LuaContainerWriter Writer(StringBuilder, Indent); + for (std::string String : Value) + { + Writer.WriteValue("", String); + } + } +} + +void +StringArrayOption::Parse(sol::object Object) +{ + if (Object.get_type() == sol::type::string) + { + Value.push_back(Object.as<std::string>()); + } + else if (Object.get_type() == sol::type::table) + { + for (const auto& Kv : Object.as<sol::table>()) + { + Value.push_back(Kv.second.as<std::string>()); + } + } +} + +std::shared_ptr<OptionValue> +MakeOption(std::string& Value) +{ + return std::make_shared<StringOption>(Value); +} + +std::shared_ptr<OptionValue> +MakeOption(std::filesystem::path& Value) +{ + return std::make_shared<FilePathOption>(Value); +} + +std::shared_ptr<OptionValue> +MakeOption(bool& Value) +{ + return std::make_shared<BoolOption>(Value); +} + +std::shared_ptr<OptionValue> +MakeOption(std::vector<std::string>& Value) +{ + return std::make_shared<StringArrayOption>(Value); +} + +void +Options::Parse(const std::filesystem::path& Path, const cxxopts::ParseResult& CmdLineResult) +{ + zen::IoBuffer LuaScript = zen::IoBufferBuilder::MakeFromFile(Path); + + if (LuaScript) + { + sol::state lua; + + lua.open_libraries(sol::lib::base); + + lua.set_function("getenv", [&](const std::string env) -> sol::object { +#if ZEN_PLATFORM_WINDOWS + std::wstring EnvVarValue; + size_t RequiredSize = 0; + std::wstring EnvWide = zen::Utf8ToWide(env); + _wgetenv_s(&RequiredSize, nullptr, 0, EnvWide.c_str()); + + if (RequiredSize == 0) + return sol::make_object(lua, sol::lua_nil); + + EnvVarValue.resize(RequiredSize); + _wgetenv_s(&RequiredSize, EnvVarValue.data(), RequiredSize, EnvWide.c_str()); + return sol::make_object(lua, zen::WideToUtf8(EnvVarValue.c_str())); +#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC + char* EnvVariable = getenv(env.c_str()); + if (EnvVariable == nullptr) + { + return sol::make_object(lua, sol::lua_nil); + } + return sol::make_object(lua, EnvVariable); +#else + ZEN_UNUSED(env); + return sol::make_object(lua, sol::lua_nil); +#endif + }); + + try + { + sol::load_result config = lua.load(std::string_view((const char*)LuaScript.Data(), LuaScript.Size()), "zen_cfg"); + + if (!config.valid()) + { + sol::error err = config; + + std::string ErrorString = sol::to_string(config.status()); + + throw std::runtime_error(fmt::format("{} error: {}", ErrorString, err.what())); + } + + config(); + } + catch (std::exception& e) + { + throw std::runtime_error(fmt::format("failed to load config script ('{}'): {}", Path, e.what()).c_str()); + } + + Parse(lua, CmdLineResult); + } +} + +void +Options::Parse(const sol::state& LuaState, const cxxopts::ParseResult& CmdLineResult) +{ + for (auto It : LuaState) + { + sol::object Key = It.first; + sol::type KeyType = Key.get_type(); + if (KeyType == sol::type::string) + { + sol::type ValueType = It.second.get_type(); + switch (ValueType) + { + case sol::type::table: + { + std::string Name = Key.as<std::string>(); + if (Name.starts_with("_")) + { + continue; + } + if (Name == "base") + { + continue; + } + Traverse(It.second.as<sol::table>(), Name, CmdLineResult); + } + break; + default: + break; + } + } + } +} + +void +Options::Touch(std::string_view Key) +{ + UsedKeys.insert(std::string(Key)); +} + +void +Options::Print(zen::StringBuilderBase& SB, const cxxopts::ParseResult& CmdLineResult) +{ + for (auto It : OptionMap) + { + if (CmdLineResult.count(It.second.CommandLineOptionName) != 0) + { + UsedKeys.insert(It.first); + } + } + + std::vector<std::string> SortedKeys(UsedKeys.begin(), UsedKeys.end()); + std::sort(SortedKeys.begin(), SortedKeys.end()); + auto GetTablePath = [](const std::string& Key) -> std::vector<std::string> { + std::vector<std::string> Path; + zen::ForEachStrTok(Key, '.', [&Path](std::string_view Part) { + Path.push_back(std::string(Part)); + return true; + }); + return Path; + }; + std::vector<std::string> CurrentTablePath; + std::string Indent; + auto It = SortedKeys.begin(); + for (const std::string& Key : SortedKeys) + { + std::vector<std::string> KeyPath = GetTablePath(Key); + std::string Name = KeyPath.back(); + KeyPath.pop_back(); + if (CurrentTablePath != KeyPath) + { + size_t EqualCount = 0; + while (EqualCount < CurrentTablePath.size() && EqualCount < KeyPath.size() && + CurrentTablePath[EqualCount] == KeyPath[EqualCount]) + { + EqualCount++; + } + while (CurrentTablePath.size() > EqualCount) + { + CurrentTablePath.pop_back(); + Indent.pop_back(); + SB.Append(Indent); + SB.Append("}"); + if (CurrentTablePath.size() == EqualCount && !Indent.empty() && KeyPath.size() >= EqualCount) + { + SB.Append(","); + } + SB.Append("\n"); + if (Indent.empty()) + { + SB.Append("\n"); + } + } + while (EqualCount < KeyPath.size()) + { + SB.Append(Indent); + SB.Append(KeyPath[EqualCount]); + SB.Append(" = {\n"); + Indent.push_back('\t'); + CurrentTablePath.push_back(KeyPath[EqualCount]); + EqualCount++; + } + } + + SB.Append(Indent); + SB.Append(Name); + SB.Append(" = "); + OptionMap[Key].Value->Print(Indent, SB); + SB.Append(",\n"); + } + while (!CurrentTablePath.empty()) + { + Indent.pop_back(); + SB.Append(Indent); + SB.Append("}\n"); + CurrentTablePath.pop_back(); + } +} + +void +Options::Traverse(sol::table Table, std::string_view PathPrefix, const cxxopts::ParseResult& CmdLineResult) +{ + for (auto It : Table) + { + sol::object Key = It.first; + sol::type KeyType = Key.get_type(); + if (KeyType == sol::type::string || KeyType == sol::type::number) + { + sol::type ValueType = It.second.get_type(); + switch (ValueType) + { + case sol::type::table: + case sol::type::string: + case sol::type::number: + case sol::type::boolean: + { + std::string Name = Key.as<std::string>(); + if (Name.starts_with("_")) + { + continue; + } + Name = std::string(PathPrefix) + "." + Key.as<std::string>(); + auto OptionIt = OptionMap.find(Name); + if (OptionIt != OptionMap.end()) + { + UsedKeys.insert(Name); + if (CmdLineResult.count(OptionIt->second.CommandLineOptionName) != 0) + { + continue; + } + OptionIt->second.Value->Parse(It.second); + continue; + } + if (ValueType == sol::type::table) + { + if (Name == "base") + { + continue; + } + Traverse(It.second.as<sol::table>(), Name, CmdLineResult); + } + } + break; + default: + break; + } + } + } +} + +} // namespace zen::LuaConfig diff --git a/src/zenserver/config/luaconfig.h b/src/zenserver/config/luaconfig.h new file mode 100644 index 000000000..76b3088a3 --- /dev/null +++ b/src/zenserver/config/luaconfig.h @@ -0,0 +1,139 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include <zenbase/concepts.h> +#include <zencore/fmtutils.h> + +ZEN_THIRD_PARTY_INCLUDES_START +#include <fmt/format.h> +#include <cxxopts.hpp> +#include <sol/sol.hpp> +ZEN_THIRD_PARTY_INCLUDES_END + +#include <filesystem> +#include <memory> +#include <string> +#include <string_view> +#include <unordered_map> +#include <unordered_set> + +namespace zen::LuaConfig { + +std::string MakeSafePath(const std::string_view Path); +void EscapeBackslash(std::string& InOutString); + +class OptionValue +{ +public: + virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) = 0; + virtual void Parse(sol::object Object) = 0; + + virtual ~OptionValue() {} +}; + +class StringOption : public OptionValue +{ +public: + explicit StringOption(std::string& Value); + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override; + virtual void Parse(sol::object Object) override; + std::string& Value; +}; + +class FilePathOption : public OptionValue +{ +public: + explicit FilePathOption(std::filesystem::path& Value); + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override; + virtual void Parse(sol::object Object) override; + std::filesystem::path& Value; +}; + +class BoolOption : public OptionValue +{ +public: + explicit BoolOption(bool& Value); + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder); + virtual void Parse(sol::object Object); + bool& Value; +}; + +template<Integral T> +class NumberOption : public OptionValue +{ +public: + explicit NumberOption(T& Value) : Value(Value) {} + virtual void Print(std::string_view, zen::StringBuilderBase& StringBuilder) override { StringBuilder.Append(fmt::format("{}", Value)); } + virtual void Parse(sol::object Object) override { Value = Object.as<T>(); } + T& Value; +}; + +class LuaContainerWriter +{ +public: + LuaContainerWriter(zen::StringBuilderBase& StringBuilder, std::string_view Indent); + ~LuaContainerWriter(); + void BeginContainer(std::string_view Name); + void WriteValue(std::string_view Name, std::string_view Value); + void EndContainer(); + +private: + zen::StringBuilderBase& StringBuilder; + const std::size_t InitialIndent; + std::string LocalIndent; +}; + +class StringArrayOption : public OptionValue +{ +public: + explicit StringArrayOption(std::vector<std::string>& Value); + virtual void Print(std::string_view Indent, zen::StringBuilderBase& StringBuilder) override; + virtual void Parse(sol::object Object) override; + +private: + std::vector<std::string>& Value; +}; + +std::shared_ptr<OptionValue> MakeOption(std::string& Value); +std::shared_ptr<OptionValue> MakeOption(std::filesystem::path& Value); + +template<Integral T> +std::shared_ptr<OptionValue> +MakeOption(T& Value) +{ + return std::make_shared<NumberOption<T>>(Value); +}; + +std::shared_ptr<OptionValue> MakeOption(bool& Value); +std::shared_ptr<OptionValue> MakeOption(std::vector<std::string>& Value); + +struct Option +{ + std::string CommandLineOptionName; + std::shared_ptr<OptionValue> Value; +}; + +struct Options +{ +public: + template<typename T> + void AddOption(std::string_view Key, T& Value, std::string_view CommandLineOptionName = "") + { + OptionMap.insert_or_assign(std::string(Key), + Option{.CommandLineOptionName = std::string(CommandLineOptionName), .Value = MakeOption(Value)}); + }; + + void Parse(const std::filesystem::path& Path, const cxxopts::ParseResult& CmdLineResult); + void Parse(const sol::state& LuaState, const cxxopts::ParseResult& CmdLineResult); + void Touch(std::string_view Key); + void Print(zen::StringBuilderBase& SB, const cxxopts::ParseResult& CmdLineResult); + +private: + void Traverse(sol::table Table, std::string_view PathPrefix, const cxxopts::ParseResult& CmdLineResult); + + std::unordered_map<std::string, Option> OptionMap; + std::unordered_set<std::string> UsedKeys; +}; + +} // namespace zen::LuaConfig |