// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #if ZEN_WITH_TESTS # include #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 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 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 StripCommandlineQuotes(std::vector& InOutArgs) { std::vector 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(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(RawArgs.size()); argv = RawArgs.data(); } #if ZEN_WITH_TESTS void commandlineoptions_forcelink() { } TEST_CASE("CommandLine") { std::vector 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 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 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 v3 = ParseCommandLine( "--tracehost \"127.0.0.1\" builds download --url=\"https://jupiter.devtools.epicgames.com\" --build-part-name=\"win64\""); std::vector 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")); } #endif } // namespace zen