From ef89501111706bb8e5a11cbb3da445b05fea1ad8 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Thu, 18 Dec 2025 21:01:42 +0200 Subject: [PATCH] Add function for parsing command-line arguments into argument list --- Source/Engine/Engine/CommandLine.cpp | 61 ++++++++++++++++++ Source/Engine/Engine/CommandLine.h | 9 +++ Source/Engine/Tests/TestCommandLine.cpp | 82 +++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 Source/Engine/Tests/TestCommandLine.cpp diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp index a717a45a2..ad62d8946 100644 --- a/Source/Engine/Engine/CommandLine.cpp +++ b/Source/Engine/Engine/CommandLine.cpp @@ -3,6 +3,7 @@ #include "CommandLine.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Utilities.h" +#include "Engine/Core/Types/StringView.h" #include CommandLine::OptionsData CommandLine::Options; @@ -170,3 +171,63 @@ bool CommandLine::Parse(const Char* cmdLine) return false; } + +bool CommandLine::ParseArguments(const StringView& cmdLine, Array& arguments) +{ + int32 start = 0; + int32 quotesStart = -1; + int32 length = cmdLine.Length(); + for (int32 i = 0; i < length; i++) + { + if (cmdLine[i] == ' ' && quotesStart == -1) + { + int32 count = i - start; + if (count > 0) + arguments.Add(StringAnsi(cmdLine.Substring(start, count))); + start = i + 1; + } + else if (cmdLine[i] == '\"') + { + if (quotesStart >= 0) + { + if (i + 1 < length && cmdLine[i + 1] != ' ') + { + // End quotes are in the middle of the current word, + // continue until the end of the current word. + } + else + { + int32 offset = 1; + if (quotesStart == start && cmdLine[start] == '\"') + { + // Word starts and ends with quotes, only include the quoted content. + quotesStart++; + offset--; + } + else if (quotesStart != start) + { + // Start quotes in the middle of the word, include the whole word. + quotesStart = start; + } + + int32 count = i - quotesStart + offset; + if (count > 0) + arguments.Add(StringAnsi(cmdLine.Substring(quotesStart, count))); + start = i + 1; + } + quotesStart = -1; + } + else + { + quotesStart = i; + } + } + } + const int32 count = length - start; + if (count > 0) + arguments.Add(StringAnsi(cmdLine.Substring(start, count))); + if (quotesStart >= 0) + return true; // Missing last closing quote + + return false; +} diff --git a/Source/Engine/Engine/CommandLine.h b/Source/Engine/Engine/CommandLine.h index 77a573a0a..e60772bc1 100644 --- a/Source/Engine/Engine/CommandLine.h +++ b/Source/Engine/Engine/CommandLine.h @@ -4,6 +4,7 @@ #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/Nullable.h" +#include "Engine/Core/Collections/Array.h" /// /// Command line options helper. @@ -219,4 +220,12 @@ public: /// The command line. /// True if failed, otherwise false. static bool Parse(const Char* cmdLine); + + /// + /// Parses the command line arguments string into string list of arguments. + /// + /// The command line. + /// The parsed arguments + /// True if failed, otherwise false. + static bool ParseArguments(const StringView& cmdLine, Array& arguments); }; diff --git a/Source/Engine/Tests/TestCommandLine.cpp b/Source/Engine/Tests/TestCommandLine.cpp new file mode 100644 index 000000000..8aaaa2aa8 --- /dev/null +++ b/Source/Engine/Tests/TestCommandLine.cpp @@ -0,0 +1,82 @@ +// Copyright (c) Wojciech Figat. All rights reserved. + +#include "Engine/Engine/CommandLine.h" +#include "Engine/Core/Types/StringView.h" +#include "Engine/Core/Collections/Array.h" +#include + +TEST_CASE("CommandLine") +{ + SECTION("Test Argument Parser") + { + SECTION("Single quoted word") + { + String input("\"word\""); + Array arguments; + CHECK(!CommandLine::ParseArguments(input, arguments)); + CHECK(arguments.Count() == 1); + CHECK(arguments[0].Compare(StringAnsi("word")) == 0); + } + SECTION("Quotes at the beginning of the word") + { + String input("start\"word\""); + Array arguments; + CHECK(!CommandLine::ParseArguments(input, arguments)); + CHECK(arguments.Count() == 1); + CHECK(arguments[0].Compare(StringAnsi("start\"word\"")) == 0); + } + SECTION("Quotes in the middle of the word") + { + String input("start\"word\"end"); + Array arguments; + CHECK(!CommandLine::ParseArguments(input, arguments)); + CHECK(arguments.Count() == 1); + CHECK(arguments[0].Compare(StringAnsi("start\"word\"end")) == 0); + } + SECTION("Quotes at the end of the word") + { + String input("\"word\"end"); + Array arguments; + CHECK(!CommandLine::ParseArguments(input, arguments)); + CHECK(arguments.Count() == 1); + CHECK(arguments[0].Compare(StringAnsi("\"word\"end")) == 0); + } + SECTION("Multiple words") + { + String input("The quick brown fox"); + Array arguments; + CHECK(!CommandLine::ParseArguments(input, arguments)); + CHECK(arguments.Count() == 4); + CHECK(arguments[0].Compare(StringAnsi("The")) == 0); + CHECK(arguments[1].Compare(StringAnsi("quick")) == 0); + CHECK(arguments[2].Compare(StringAnsi("brown")) == 0); + CHECK(arguments[3].Compare(StringAnsi("fox")) == 0); + } + SECTION("Multiple words with quotes") + { + String input("The \"quick brown fox\" jumps over the \"lazy\" dog"); + Array arguments; + CHECK(!CommandLine::ParseArguments(input, arguments)); + CHECK(arguments.Count() == 7); + CHECK(arguments[0].Compare(StringAnsi("The")) == 0); + CHECK(arguments[1].Compare(StringAnsi("quick brown fox")) == 0); + CHECK(arguments[2].Compare(StringAnsi("jumps")) == 0); + CHECK(arguments[3].Compare(StringAnsi("over")) == 0); + CHECK(arguments[4].Compare(StringAnsi("the")) == 0); + CHECK(arguments[5].Compare(StringAnsi("lazy")) == 0); + CHECK(arguments[6].Compare(StringAnsi("dog")) == 0); + } + SECTION("Flax.Build sample parameters") + { + String input("-log -mutex -workspace=\"C:\\path with spaces/to/FlaxEngine/\" -configuration=Debug -hotreload=\".HotReload.1\""); + Array arguments; + CHECK(!CommandLine::ParseArguments(input, arguments)); + CHECK(arguments.Count() == 5); + CHECK(arguments[0].Compare(StringAnsi("-log")) == 0); + CHECK(arguments[1].Compare(StringAnsi("-mutex")) == 0); + CHECK(arguments[2].Compare(StringAnsi("-workspace=\"C:\\path with spaces/to/FlaxEngine/\"")) == 0); + CHECK(arguments[3].Compare(StringAnsi("-configuration=Debug")) == 0); + CHECK(arguments[4].Compare(StringAnsi("-hotreload=\".HotReload.1\"")) == 0); + } + } +}