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/zenserver/config/luaconfig.cpp | |
| 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/zenserver/config/luaconfig.cpp')
| -rw-r--r-- | src/zenserver/config/luaconfig.cpp | 461 |
1 files changed, 461 insertions, 0 deletions
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 |