aboutsummaryrefslogtreecommitdiff
path: root/src/zenutil/config
diff options
context:
space:
mode:
authorLiam Mitchell <[email protected]>2026-03-09 19:06:36 -0700
committerLiam Mitchell <[email protected]>2026-03-09 19:06:36 -0700
commitd1abc50ee9d4fb72efc646e17decafea741caa34 (patch)
treee4288e00f2f7ca0391b83d986efcb69d3ba66a83 /src/zenutil/config
parentAllow requests with invalid content-types unless specified in command line or... (diff)
parentupdated chunk–block analyser (#818) (diff)
downloadzen-d1abc50ee9d4fb72efc646e17decafea741caa34.tar.xz
zen-d1abc50ee9d4fb72efc646e17decafea741caa34.zip
Merge branch 'main' into lm/restrict-content-type
Diffstat (limited to 'src/zenutil/config')
-rw-r--r--src/zenutil/config/commandlineoptions.cpp244
-rw-r--r--src/zenutil/config/environmentoptions.cpp84
-rw-r--r--src/zenutil/config/loggingconfig.cpp77
3 files changed, 405 insertions, 0 deletions
diff --git a/src/zenutil/config/commandlineoptions.cpp b/src/zenutil/config/commandlineoptions.cpp
new file mode 100644
index 000000000..25f5522d8
--- /dev/null
+++ b/src/zenutil/config/commandlineoptions.cpp
@@ -0,0 +1,244 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zenutil/config/commandlineoptions.h>
+
+#include <zencore/filesystem.h>
+#include <zencore/string.h>
+#include <filesystem>
+
+#include <zencore/windows.h>
+
+#if ZEN_WITH_TESTS
+# include <zencore/testing.h>
+#endif // ZEN_WITH_TESTS
+
+#ifndef CXXOPTS_HAS_FILESYSTEM
+void
+cxxopts::values::parse_value(const std::string& text, std::filesystem::path& value)
+{
+ value = zen::StringToPath(text);
+}
+#endif
+
+namespace zen {
+
+std::vector<std::string>
+ParseCommandLine(std::string_view CommandLine)
+{
+ auto IsWhitespaceOrEnd = [](std::string_view CommandLine, std::string::size_type Pos) {
+ if (Pos == CommandLine.length())
+ {
+ return true;
+ }
+ if (CommandLine[Pos] == ' ')
+ {
+ return true;
+ }
+ return false;
+ };
+
+ bool IsParsingArg = false;
+ bool IsInQuote = false;
+
+ std::string::size_type Pos = 0;
+ std::string::size_type ArgStart = 0;
+ std::vector<std::string> Args;
+ while (Pos < CommandLine.length())
+ {
+ if (IsInQuote)
+ {
+ if (CommandLine[Pos] == '"' && IsWhitespaceOrEnd(CommandLine, Pos + 1))
+ {
+ Args.push_back(std::string(CommandLine.substr(ArgStart, Pos - ArgStart + 1)));
+ Pos++;
+ IsInQuote = false;
+ IsParsingArg = false;
+ }
+ else
+ {
+ Pos++;
+ }
+ }
+ else if (IsParsingArg)
+ {
+ ZEN_ASSERT(Pos > ArgStart);
+ if (CommandLine[Pos] == ' ')
+ {
+ Args.push_back(std::string(CommandLine.substr(ArgStart, Pos - ArgStart)));
+ Pos++;
+ IsParsingArg = false;
+ }
+ else if (CommandLine[Pos] == '"')
+ {
+ IsInQuote = true;
+ Pos++;
+ }
+ else
+ {
+ Pos++;
+ }
+ }
+ else if (CommandLine[Pos] == '"')
+ {
+ IsInQuote = true;
+ IsParsingArg = true;
+ ArgStart = Pos;
+ Pos++;
+ }
+ else if (CommandLine[Pos] != ' ')
+ {
+ IsParsingArg = true;
+ ArgStart = Pos;
+ Pos++;
+ }
+ else
+ {
+ Pos++;
+ }
+ }
+ if (IsParsingArg)
+ {
+ ZEN_ASSERT(Pos > ArgStart);
+ Args.push_back(std::string(CommandLine.substr(ArgStart)));
+ }
+
+ return Args;
+}
+
+std::vector<char*>
+StripCommandlineQuotes(std::vector<std::string>& InOutArgs)
+{
+ std::vector<char*> RawArgs;
+ RawArgs.reserve(InOutArgs.size());
+ for (std::string& Arg : InOutArgs)
+ {
+ std::string::size_type EscapedQuotePos = Arg.find("\\\"", 1);
+ while (EscapedQuotePos != std::string::npos && Arg.rfind('\"', EscapedQuotePos - 1) != std::string::npos)
+ {
+ Arg.erase(EscapedQuotePos, 1);
+ EscapedQuotePos = Arg.find("\\\"", EscapedQuotePos);
+ }
+
+ if (Arg.starts_with("\""))
+ {
+ if (Arg.find('"', 1) == Arg.length() - 1)
+ {
+ Arg = Arg.substr(1, Arg.length() - 2);
+ }
+ }
+ else if (Arg.ends_with("\""))
+ {
+ std::string::size_type EqualSign = Arg.find("=", 1);
+ if (EqualSign != std::string::npos && Arg[EqualSign + 1] == '\"')
+ {
+ Arg = Arg.substr(0, EqualSign + 1) + Arg.substr(EqualSign + 2, Arg.length() - (EqualSign + 2) - 1);
+ }
+ }
+ RawArgs.push_back(const_cast<char*>(Arg.c_str()));
+ }
+ return RawArgs;
+}
+
+std::filesystem::path
+StringToPath(const std::string_view& Path)
+{
+ std::string_view UnquotedPath = RemoveQuotes(Path);
+
+ if (UnquotedPath.ends_with('/') || UnquotedPath.ends_with('\\') || UnquotedPath.ends_with(std::filesystem::path::preferred_separator))
+ {
+ UnquotedPath = UnquotedPath.substr(0, UnquotedPath.length() - 1);
+ }
+
+ return std::filesystem::path(UnquotedPath).make_preferred();
+}
+
+std::string_view
+RemoveQuotes(const std::string_view& Arg)
+{
+ if (Arg.length() > 2)
+ {
+ if (Arg[0] == '"' && Arg[Arg.length() - 1] == '"')
+ {
+ return Arg.substr(1, Arg.length() - 2);
+ }
+ }
+ return Arg;
+}
+
+CommandLineConverter::CommandLineConverter(int& argc, char**& argv)
+{
+#if ZEN_PLATFORM_WINDOWS
+ LPWSTR RawCommandLine = GetCommandLineW();
+ std::string CommandLine = WideToUtf8(RawCommandLine);
+ Args = ParseCommandLine(CommandLine);
+#else
+ Args.reserve(argc);
+ for (int I = 0; I < argc; I++)
+ {
+ std::string Arg(argv[I]);
+ if ((!Arg.empty()) && (Arg != " "))
+ {
+ Args.emplace_back(std::move(Arg));
+ }
+ }
+#endif
+ RawArgs = StripCommandlineQuotes(Args);
+
+ argc = static_cast<int>(RawArgs.size());
+ argv = RawArgs.data();
+}
+
+#if ZEN_WITH_TESTS
+
+void
+commandlineoptions_forcelink()
+{
+}
+
+TEST_SUITE_BEGIN("util.commandlineoptions");
+
+TEST_CASE("CommandLine")
+{
+ std::vector<std::string> v1 = ParseCommandLine("c:\\my\\exe.exe \"quoted arg\" \"one\",two,\"three\\\"");
+ CHECK_EQ(v1[0], "c:\\my\\exe.exe");
+ CHECK_EQ(v1[1], "\"quoted arg\"");
+ CHECK_EQ(v1[2], "\"one\",two,\"three\\\"");
+
+ std::vector<std::string> v2 = ParseCommandLine(
+ "--tracehost 127.0.0.1 builds download --url=https://jupiter.devtools.epicgames.com --namespace=ue.oplog "
+ "--bucket=citysample.packaged-build.fortnite-main.windows \"c:\\just\\a\\path\" "
+ "--access-token-path=\"C:\\Users\\dan.engelbrecht\\jupiter-token.json\" \"D:\\Dev\\Spaced Folder\\Target\\\" "
+ "--alt-path=\"D:\\Dev\\Spaced Folder2\\Target\\\" 07dn23ifiwesnvoasjncasab --build-part-name win64,linux,ps5");
+
+ std::vector<char*> v2Stripped = StripCommandlineQuotes(v2);
+ CHECK_EQ(v2Stripped[0], std::string("--tracehost"));
+ CHECK_EQ(v2Stripped[1], std::string("127.0.0.1"));
+ CHECK_EQ(v2Stripped[2], std::string("builds"));
+ CHECK_EQ(v2Stripped[3], std::string("download"));
+ CHECK_EQ(v2Stripped[4], std::string("--url=https://jupiter.devtools.epicgames.com"));
+ CHECK_EQ(v2Stripped[5], std::string("--namespace=ue.oplog"));
+ CHECK_EQ(v2Stripped[6], std::string("--bucket=citysample.packaged-build.fortnite-main.windows"));
+ CHECK_EQ(v2Stripped[7], std::string("c:\\just\\a\\path"));
+ CHECK_EQ(v2Stripped[8], std::string("--access-token-path=C:\\Users\\dan.engelbrecht\\jupiter-token.json"));
+ CHECK_EQ(v2Stripped[9], std::string("D:\\Dev\\Spaced Folder\\Target"));
+ CHECK_EQ(v2Stripped[10], std::string("--alt-path=D:\\Dev\\Spaced Folder2\\Target"));
+ CHECK_EQ(v2Stripped[11], std::string("07dn23ifiwesnvoasjncasab"));
+ CHECK_EQ(v2Stripped[12], std::string("--build-part-name"));
+ CHECK_EQ(v2Stripped[13], std::string("win64,linux,ps5"));
+
+ std::vector<std::string> v3 = ParseCommandLine(
+ "--tracehost \"127.0.0.1\" builds download --url=\"https://jupiter.devtools.epicgames.com\" --build-part-name=\"win64\"");
+ std::vector<char*> v3Stripped = StripCommandlineQuotes(v3);
+
+ CHECK_EQ(v3Stripped[0], std::string("--tracehost"));
+ CHECK_EQ(v3Stripped[1], std::string("127.0.0.1"));
+ CHECK_EQ(v3Stripped[2], std::string("builds"));
+ CHECK_EQ(v3Stripped[3], std::string("download"));
+ CHECK_EQ(v3Stripped[4], std::string("--url=https://jupiter.devtools.epicgames.com"));
+ CHECK_EQ(v3Stripped[5], std::string("--build-part-name=win64"));
+}
+
+TEST_SUITE_END();
+
+#endif
+} // namespace zen
diff --git a/src/zenutil/config/environmentoptions.cpp b/src/zenutil/config/environmentoptions.cpp
new file mode 100644
index 000000000..fb7f71706
--- /dev/null
+++ b/src/zenutil/config/environmentoptions.cpp
@@ -0,0 +1,84 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zenutil/config/environmentoptions.h>
+
+#include <zencore/filesystem.h>
+
+namespace zen {
+
+EnvironmentOptions::StringOption::StringOption(std::string& Value) : RefValue(Value)
+{
+}
+void
+EnvironmentOptions::StringOption::Parse(std::string_view Value)
+{
+ RefValue = std::string(Value);
+}
+
+EnvironmentOptions::FilePathOption::FilePathOption(std::filesystem::path& Value) : RefValue(Value)
+{
+}
+void
+EnvironmentOptions::FilePathOption::Parse(std::string_view Value)
+{
+ RefValue = MakeSafeAbsolutePath(Value);
+}
+
+EnvironmentOptions::BoolOption::BoolOption(bool& Value) : RefValue(Value)
+{
+}
+void
+EnvironmentOptions::BoolOption::Parse(std::string_view Value)
+{
+ const std::string Lower = ToLower(Value);
+ if (Lower == "true" || Lower == "y" || Lower == "yes")
+ {
+ RefValue = true;
+ }
+ else if (Lower == "false" || Lower == "n" || Lower == "no")
+ {
+ RefValue = false;
+ }
+}
+
+std::shared_ptr<EnvironmentOptions::OptionValue>
+EnvironmentOptions::MakeOption(std::string& Value)
+{
+ return std::make_shared<StringOption>(Value);
+}
+
+std::shared_ptr<EnvironmentOptions::OptionValue>
+EnvironmentOptions::MakeOption(std::filesystem::path& Value)
+{
+ return std::make_shared<FilePathOption>(Value);
+}
+
+std::shared_ptr<EnvironmentOptions::OptionValue>
+EnvironmentOptions::MakeOption(bool& Value)
+{
+ return std::make_shared<BoolOption>(Value);
+}
+
+EnvironmentOptions::EnvironmentOptions()
+{
+}
+
+void
+EnvironmentOptions::Parse(const cxxopts::ParseResult& CmdLineResult)
+{
+ for (auto& It : OptionMap)
+ {
+ std::string_view EnvName = It.first;
+ const Option& Opt = It.second;
+ if (CmdLineResult.count(Opt.CommandLineOptionName) == 0)
+ {
+ std::string EnvValue = GetEnvVariable(EnvName);
+ if (!EnvValue.empty())
+ {
+ Opt.Value->Parse(EnvValue);
+ }
+ }
+ }
+}
+
+} // namespace zen
diff --git a/src/zenutil/config/loggingconfig.cpp b/src/zenutil/config/loggingconfig.cpp
new file mode 100644
index 000000000..5092c60aa
--- /dev/null
+++ b/src/zenutil/config/loggingconfig.cpp
@@ -0,0 +1,77 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "zenutil/config/loggingconfig.h"
+
+#include <zenbase/zenbase.h>
+#include <zencore/filesystem.h>
+#include <zencore/logging.h>
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <cxxopts.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+
+void
+ZenLoggingCmdLineOptions::AddCliOptions(cxxopts::Options& options, ZenLoggingConfig& LoggingConfig)
+{
+ // clang-format off
+ options.add_options("logging")
+ ("abslog", "Path to log file", cxxopts::value<std::string>(m_AbsLogFile))
+ ("log-id", "Specify id for adding context to log output", cxxopts::value<std::string>(LoggingConfig.LogId))
+ ("quiet", "Configure console logger output to level WARN", cxxopts::value<bool>(LoggingConfig.QuietConsole)->default_value("false"))
+ ("noconsole", "Disable console logging", cxxopts::value<bool>(LoggingConfig.NoConsoleOutput)->default_value("false"))
+ ("log-trace", "Change selected loggers to level TRACE", cxxopts::value<std::string>(LoggingConfig.Loggers[logging::Trace]))
+ ("log-debug", "Change selected loggers to level DEBUG", cxxopts::value<std::string>(LoggingConfig.Loggers[logging::Debug]))
+ ("log-info", "Change selected loggers to level INFO", cxxopts::value<std::string>(LoggingConfig.Loggers[logging::Info]))
+ ("log-warn", "Change selected loggers to level WARN", cxxopts::value<std::string>(LoggingConfig.Loggers[logging::Warn]))
+ ("log-error", "Change selected loggers to level ERROR", cxxopts::value<std::string>(LoggingConfig.Loggers[logging::Err]))
+ ("log-critical", "Change selected loggers to level CRITICAL", cxxopts::value<std::string>(LoggingConfig.Loggers[logging::Critical]))
+ ("log-off", "Change selected loggers to level OFF", cxxopts::value<std::string>(LoggingConfig.Loggers[logging::Off]))
+ ("otlp-endpoint", "OpenTelemetry endpoint URI (e.g http://localhost:4318)", cxxopts::value<std::string>(LoggingConfig.OtelEndpointUri))
+ ;
+ // clang-format on
+}
+
+void
+ZenLoggingCmdLineOptions::ApplyOptions(ZenLoggingConfig& LoggingConfig)
+{
+ LoggingConfig.AbsLogFile = MakeSafeAbsolutePath(m_AbsLogFile);
+}
+
+void
+ApplyLoggingOptions(cxxopts::Options& options, ZenLoggingConfig& LoggingConfig)
+{
+ ZEN_UNUSED(options);
+
+ if (LoggingConfig.QuietConsole)
+ {
+ bool HasExplicitConsoleLevel = false;
+ for (int i = 0; i < logging::LogLevelCount; ++i)
+ {
+ if (LoggingConfig.Loggers[i].find("console") != std::string::npos)
+ {
+ HasExplicitConsoleLevel = true;
+ break;
+ }
+ }
+
+ if (!HasExplicitConsoleLevel)
+ {
+ std::string& WarnLoggers = LoggingConfig.Loggers[logging::Warn];
+ if (!WarnLoggers.empty())
+ {
+ WarnLoggers += ",";
+ }
+ WarnLoggers += "console";
+ }
+ }
+
+ for (int i = 0; i < logging::LogLevelCount; ++i)
+ {
+ logging::ConfigureLogLevels(logging::LogLevel(i), LoggingConfig.Loggers[i]);
+ }
+ logging::RefreshLogLevels();
+}
+
+} // namespace zen