From 05430941dcb952c44a1603e61f8a23952a35e2b3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 21 Jul 2026 23:36:32 +0200 Subject: [PATCH] Refactor `CommandLine` parsing and processing Get rid of fixed list of command arguments and use flexible access to pre-parsed options. Use command line parsing algorithm from `Flax.Build` that has been battle tested. Simplify API and expose it to scripting (incl. plugins). --- Source/Engine/Core/Types/StringView.cpp | 27 ++ Source/Engine/Core/Types/StringView.h | 5 + Source/Engine/Engine/CommandLine.cpp | 416 +++++++++--------- Source/Engine/Engine/CommandLine.h | 48 +- Source/Engine/Engine/Engine.cpp | 3 +- .../Engine/Platform/Base/StringUtilsBase.cpp | 22 + Source/Engine/Platform/StringUtils.h | 4 + Source/Engine/Tests/TestCommandLine.cpp | 82 ---- Source/Tools/Flax.Build/CommandLine.cs | 2 + 9 files changed, 306 insertions(+), 303 deletions(-) delete mode 100644 Source/Engine/Tests/TestCommandLine.cpp diff --git a/Source/Engine/Core/Types/StringView.cpp b/Source/Engine/Core/Types/StringView.cpp index a9b40e6a3..8667d0c58 100644 --- a/Source/Engine/Core/Types/StringView.cpp +++ b/Source/Engine/Core/Types/StringView.cpp @@ -50,6 +50,33 @@ StringView StringView::Substring(int32 startIndex, int32 count) const return StringView(Get() + startIndex, count); } +StringView StringView::Trim() const +{ + if (IsEmpty()) + return *this; + + int32 start = 0; + int32 end = Length() - 1; + + while (start <= end) + { + if (!StringUtils::IsWhitespace((*this)[start])) + break; + start++; + } + while (end >= 0) + { + if (!StringUtils::IsWhitespace((*this)[end])) + break; + end--; + } + + const int32 count = end - start + 1; + if (start >= 0 && start + count <= Length() && count >= 0) + return StringView(_data + start, count); + return Empty; +} + String StringView::ToString() const { return String(_data, _length); diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h index 40a763bee..89b4dde2a 100644 --- a/Source/Engine/Core/Types/StringView.h +++ b/Source/Engine/Core/Types/StringView.h @@ -382,6 +382,11 @@ public: /// The substring created from String data. StringView Substring(int32 startIndex, int32 count) const; + /// + /// Removes trailing whitespace characters from end and begin of the string. + /// + StringView Trim() const; + public: String ToString() const; StringAnsi ToStringAnsi() const; diff --git a/Source/Engine/Engine/CommandLine.cpp b/Source/Engine/Engine/CommandLine.cpp index d8bde1864..d2b5fa374 100644 --- a/Source/Engine/Engine/CommandLine.cpp +++ b/Source/Engine/Engine/CommandLine.cpp @@ -1,245 +1,243 @@ // Copyright (c) Wojciech Figat. All rights reserved. #include "CommandLine.h" -#include "Engine/Core/Collections/Array.h" #include "Engine/Core/Utilities.h" -#include "Engine/Core/Types/StringView.h" -#include +#include "Engine/Core/Collections/Array.h" +static Array Args; CommandLine::OptionsData CommandLine::Options; -bool ParseArg(Char* ptr, Char*& start, Char*& end) +bool InitCommandLine(const Char* cmdLine) { - // Skip leading whitespaces - while (*ptr && StringUtils::IsWhitespace(*ptr)) - ptr++; - - bool isInComma = false; - bool isUglyQuote = false; - start = ptr; - while (*ptr) - { - Char c = *ptr; - if (StringUtils::IsWhitespace(c) && !isInComma) - { - // End - end = ptr; - return false; - } - else if (c == '\"' || c == '\'') - { - if (isInComma) - { - // End comma - end = ptr; - if (isUglyQuote) - { - ptr += 2; - end -= 2; - } - return false; - } - else - { - // Special case (eg. Visual Studio Code adds soo many quotes to the args with spaces) - isUglyQuote = StringUtils::Compare(ptr, TEXT("\"\\\\\""), 4) == 0; - if (isUglyQuote) - { - ptr += 3; - } - - // Start comma - isInComma = true; - start = ptr + 1; - } - } - - ptr++; - } - return true; -} - -bool CommandLine::Parse(const Char* cmdLine) -{ - Options.CmdLine = cmdLine; + auto& options = CommandLine::Options; #if FLAX_TESTS // Configure engine for test running environment - Options.Headless = true; - Options.Null = true; - Options.Mute = true; - Options.Std = true; + options.Headless = true; + options.Null = true; + options.Mute = true; + options.Std = true; #endif + // Parse command line int32 length = StringUtils::Length(cmdLine); if (length == 0) return false; - Array buffer; - buffer.Resize(length + 2); - Platform::MemoryCopy(buffer.Get(), cmdLine, sizeof(Char) * length); - buffer[length++] = ' '; - buffer[length++] = 0; - Char* end = buffer.Get() + length; - - Char* pos; - Char* argStart; - Char* argEnd; - int32 len; - (void)argStart; - (void)argEnd; + if (CommandLine::Parse(StringView(cmdLine, length), Args)) + return true; + // Parse common switches + bool valueBool; + StringView valueString; +#define PARSE_BOOL_SWITCH(arg, field) if (CommandLine::Get(TEXT(arg), valueBool)) options.field = valueBool +#define PARSE_ARG_SWITCH(arg, field) if (CommandLine::Get(TEXT(arg), valueString)) options.field = valueString + PARSE_BOOL_SWITCH("windowed", Windowed); + PARSE_BOOL_SWITCH("fullscreen", Fullscreen); + PARSE_BOOL_SWITCH("vsync", VSync); + PARSE_BOOL_SWITCH("novsync", NoVSync); + PARSE_BOOL_SWITCH("nolog", NoLog); + PARSE_BOOL_SWITCH("std", Std); #if PLATFORM_HAS_HEADLESS_MODE -#define PARSE_ERROR(msg) std::cout << msg << std::endl -#else -#define PARSE_ERROR(msg) // Ignore + PARSE_BOOL_SWITCH("headless", Headless); #endif - -#define PARSE_BOOL_SWITCH(text, field) \ - pos = (Char*)StringUtils::FindIgnoreCase(buffer.Get(), TEXT(text)); \ - if (pos) \ - { \ - len = ARRAY_COUNT(text) - 1; \ - Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \ - *(end - len) = 0; \ - end -= len; \ - Options.field = true; \ - } -#define PARSE_ARG_SWITCH(text, field) \ - pos = (Char*)StringUtils::FindIgnoreCase(buffer.Get(), TEXT(text)); \ - if (pos) \ - { \ - len = ARRAY_COUNT(text) - 1; \ - if (ParseArg(pos + len, argStart, argEnd)) \ - { \ - PARSE_ERROR("Failed to parse argument."); \ - return true; \ - } \ - Options.field = String(argStart, static_cast(argEnd - argStart)); \ - len = static_cast((argEnd - pos) + 1); \ - Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \ - *(end - len) = 0; \ - end -= len; \ - } - -#define PARSE_ARG_OPT_SWITCH(text, field) \ - pos = (Char*)StringUtils::FindIgnoreCase(buffer.Get(), TEXT(text)); \ - if (pos) \ - { \ - len = ARRAY_COUNT(text) - 1; \ - if (ParseArg(pos + len, argStart, argEnd)) \ - Options.field = String::Empty; \ - else \ - { \ - Options.field = String(argStart, static_cast(argEnd - argStart)); \ - len = static_cast((argEnd - pos) + 1); \ - Utilities::UnsafeMemoryCopy(pos, pos + len, (end - pos - len) * 2); \ - *(end - len) = 0; \ - end -= len; \ - } \ - } - - PARSE_BOOL_SWITCH("-windowed ", Windowed); - PARSE_BOOL_SWITCH("-fullscreen ", Fullscreen); - PARSE_BOOL_SWITCH("-vsync ", VSync); - PARSE_BOOL_SWITCH("-novsync ", NoVSync); - PARSE_BOOL_SWITCH("-nolog ", NoLog); - PARSE_BOOL_SWITCH("-std ", Std); -#if PLATFORM_HAS_HEADLESS_MODE - PARSE_BOOL_SWITCH("-headless ", Headless); -#endif - PARSE_BOOL_SWITCH("-d3d12 ", D3D12); - PARSE_BOOL_SWITCH("-d3d11 ", D3D11); - PARSE_BOOL_SWITCH("-d3d10 ", D3D10); - PARSE_BOOL_SWITCH("-null ", Null); - PARSE_BOOL_SWITCH("-vulkan ", Vulkan); - PARSE_BOOL_SWITCH("-nvidia ", NVIDIA); - PARSE_BOOL_SWITCH("-amd ", AMD); - PARSE_BOOL_SWITCH("-intel ", Intel); - PARSE_BOOL_SWITCH("-mute ", Mute); - PARSE_BOOL_SWITCH("-lowdpi ", LowDPI); - + PARSE_BOOL_SWITCH("d3d12", D3D12); + PARSE_BOOL_SWITCH("d3d11", D3D11); + PARSE_BOOL_SWITCH("d3d10", D3D10); + PARSE_BOOL_SWITCH("null", Null); + PARSE_BOOL_SWITCH("vulkan", Vulkan); + PARSE_BOOL_SWITCH("nvidia", NVIDIA); + PARSE_BOOL_SWITCH("amd", AMD); + PARSE_BOOL_SWITCH("intel", Intel); + PARSE_BOOL_SWITCH("mute", Mute); + PARSE_BOOL_SWITCH("lowdpi", LowDPI); #if PLATFORM_LINUX && PLATFORM_SDL - PARSE_BOOL_SWITCH("-wayland ", Wayland); - PARSE_BOOL_SWITCH("-x11 ", X11); + PARSE_BOOL_SWITCH("wayland", Wayland); + PARSE_BOOL_SWITCH("x11", X11); #endif - #if USE_EDITOR - PARSE_BOOL_SWITCH("-clearcache ", ClearCache); - PARSE_BOOL_SWITCH("-clearcooker ", ClearCookerCache); - PARSE_ARG_SWITCH("-project ", Project); - PARSE_BOOL_SWITCH("-lastproject ", LastProject); - PARSE_BOOL_SWITCH("-new ", NewProject); - PARSE_BOOL_SWITCH("-genprojectfiles ", GenProjectFiles); - PARSE_ARG_SWITCH("-build ", Build); - PARSE_BOOL_SWITCH("-skipcompile ", SkipCompile); - PARSE_BOOL_SWITCH("-shaderdebug ", ShaderDebug); - PARSE_BOOL_SWITCH("-exit ", Exit); - PARSE_ARG_OPT_SWITCH("-play ", Play); + PARSE_BOOL_SWITCH("clearcache", ClearCache); + PARSE_BOOL_SWITCH("clearcooker", ClearCookerCache); + PARSE_ARG_SWITCH("project", Project); + PARSE_BOOL_SWITCH("lastproject", LastProject); + PARSE_BOOL_SWITCH("new", NewProject); + PARSE_BOOL_SWITCH("genprojectfiles", GenProjectFiles); + PARSE_ARG_SWITCH("build", Build); + PARSE_BOOL_SWITCH("skipcompile", SkipCompile); + PARSE_BOOL_SWITCH("shaderdebug", ShaderDebug); + PARSE_BOOL_SWITCH("exit", Exit); + PARSE_ARG_SWITCH("play", Play); #endif #if USE_EDITOR || !BUILD_RELEASE - PARSE_BOOL_SWITCH("-shaderprofile ", ShaderProfile); - PARSE_BOOL_SWITCH("-gpudebug ", GPUDebug); + PARSE_BOOL_SWITCH("shaderprofile", ShaderProfile); + PARSE_BOOL_SWITCH("gpudebug", GPUDebug); #endif +#undef PARSE_BOOL_SWITCH +#undef PARSE_ARG_SWITCH return false; } -bool CommandLine::ParseArguments(const StringView& cmdLine, Array& arguments) +bool CommandLine::Get(const StringView& arg) { - 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; - } + StringView value; + return Get(arg, value); +} - int32 count = i - quotesStart + offset; - if (count > 0) - arguments.Add(StringAnsi(cmdLine.Substring(quotesStart, count))); - start = i + 1; - } - quotesStart = -1; - } - else - { - quotesStart = i; - } +bool CommandLine::Get(const StringView& arg, StringView& value) +{ + for (const Arg& a : Args) + { + if (a.Name.Length() == arg.Length() && StringUtils::CompareIgnoreCase(*a.Name, *arg, arg.Length()) == 0) + { + value = a.Value; + return true; } } - 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; +} + +bool CommandLine::Get(const StringView& arg, bool& value) +{ + StringView str; + if (Get(arg, str)) + { + value = true; // Assume true when arg is set + if (str.HasChars()) + StringUtils::Parse(str.Get(), str.Length(), &value); + return true; + } + return false; +} + +bool CommandLine::Get(const StringView& arg, int32& value) +{ + StringView str; + if (Get(arg, str)) + { + value = 0; + if (str.HasChars()) + StringUtils::Parse(str.Get(), str.Length(), &value); + return true; + } + return false; +} + +bool CommandLine::Parse(const StringView& commandLine, Array& args) +{ + int32 length = commandLine.Length(); + bool addedEmptyArg = false; + for (int32 i = 0; i < length;) + { + // Skip white space + while (i < length && StringUtils::IsWhitespace(commandLine[i])) + i++; + + // Read option prefix + if (i == length) + break; + bool wholeQuote = commandLine[i] == '\"'; + if (wholeQuote) + i++; + if (i == length) + break; + if (commandLine[i] == '-') + i++; + else if (commandLine[i] == '/') + i++; + + // Skip white space + while (i < length && StringUtils::IsWhitespace(commandLine[i])) + i++; + + // Read option name + int32 nameStart = i; + while (i < length && commandLine[i] != '-' && commandLine[i] != '=' && !StringUtils::IsWhitespace(commandLine[i])) + i++; + if (wholeQuote) + i--; + int32 nameEnd = i; + StringView name = commandLine.Substring(nameStart, nameEnd - nameStart); + + // If previous argument was empty and this one is a quote then assume it's a value for the previous argument (without '=') + if (wholeQuote && addedEmptyArg) + { + args.Last().Value = name; + i++; + continue; + } + + // Skip white space + while (i < length && StringUtils::IsWhitespace(commandLine[i])) + i++; + + // Check if has no value + if (i >= length - 1 || commandLine[i] != '=') + { + args.Add({ name, StringView::Empty }); + addedEmptyArg = true; + if (wholeQuote) + i++; + if (i < length && commandLine[i] != '\"') + i++; + continue; + } + addedEmptyArg = false; + + // Read value + i++; + int32 valueStart, valueEnd; + if (length > i + 1 && commandLine[i] == '\\' && commandLine[i + 1] == '\"') + { + valueStart = i + 2; + i++; + while (i + 1 < length && commandLine[i] != '\\' && commandLine[i + 1] != '\"') + i++; + valueEnd = i; + i += 2; + if (wholeQuote) + { + while (i < length && commandLine[i] != '\"') + i++; + i++; + } + } + else if (commandLine[i] == '\"' || commandLine[i] == '\'') + { + Char quoteChar = commandLine[i]; + valueStart = i + 1; + i++; + while (i < length && commandLine[i] != quoteChar) + i++; + valueEnd = i; + i++; + if (wholeQuote) + { + while (i < length && commandLine[i] != '\"') + i++; + i++; + } + } + else if (wholeQuote) + { + valueStart = i; + while (i < length && commandLine[i] != '\"') + i++; + valueEnd = i; + i++; + } + else + { + valueStart = i; + while (i < length && commandLine[i] != ' ') + i++; + valueEnd = i; + } + StringView value = commandLine.Substring(valueStart, valueEnd - valueStart); + value = value.Trim(); + if (value.StartsWith(TEXT("\\\"")) && value.EndsWith(TEXT("\\\""))) + value = value.Substring(2, value.Length() - 4); + args.Add({ name, value }); + } return false; } diff --git a/Source/Engine/Engine/CommandLine.h b/Source/Engine/Engine/CommandLine.h index 55ecfc4e5..79c9c30ae 100644 --- a/Source/Engine/Engine/CommandLine.h +++ b/Source/Engine/Engine/CommandLine.h @@ -3,16 +3,15 @@ #pragma once #include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/Nullable.h" -#include "Engine/Core/Collections/Array.h" /// /// Command line options helper. /// -class CommandLine +class FLAXENGINE_API CommandLine { public: - struct OptionsData { /// @@ -199,19 +198,46 @@ public: static OptionsData Options; public: + /// + /// The command line option data. + /// + struct FLAXENGINE_API Arg + { + // The name. + StringView Name; + // The value. + StringView Value; + }; /// - /// Parses the input command line. + /// Gets the specific command line option value. Returns true if given argument has been set. /// - /// The command line. - /// True if failed, otherwise false. - static bool Parse(const Char* cmdLine); + /// True if given argument has been provided in program command line. + static bool Get(const StringView& arg); /// - /// Parses the command line arguments string into string list of arguments. + /// Gets the specific command line option value. Returns true if given argument has been set and provides it's value (as string). /// - /// The command line. - /// The parsed arguments + /// True if given argument has been provided in program command line. + static bool Get(const StringView& arg, StringView& value); + + /// + /// Gets the specific command line option value. Returns true if given argument has been set and provides it's value (as boolean). Returns ture if argument doesn't have specific value. + /// + /// True if given argument has been provided in program command line. + static bool Get(const StringView& arg, bool& value); + + /// + /// Gets the specific command line option value. Returns true if given argument has been set and provides it's value (as integer). + /// + /// True if given argument has been provided in program command line. + static bool Get(const StringView& arg, int32& value); + + /// + /// Parses the specified command line. + /// + /// The input command line. + /// The parsed arguments /// True if failed, otherwise false. - static bool ParseArguments(const StringView& cmdLine, Array& arguments); + static bool Parse(const StringView& cmdLine, Array& args); }; diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index afaa8824c..5259cc564 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -87,7 +87,8 @@ int32 Engine::OnInit(const Char* cmdLine) EngineService::Sort(); - if (CommandLine::Parse(cmdLine)) + extern bool InitCommandLine(const Char* cmdLine); + if (InitCommandLine(cmdLine)) { Platform::Fatal(TEXT("Invalid command line.")); return -1; diff --git a/Source/Engine/Platform/Base/StringUtilsBase.cpp b/Source/Engine/Platform/Base/StringUtilsBase.cpp index 96f0fd2dc..350d43272 100644 --- a/Source/Engine/Platform/Base/StringUtilsBase.cpp +++ b/Source/Engine/Platform/Base/StringUtilsBase.cpp @@ -499,6 +499,28 @@ bool StringUtils::Parse(const char* str, float* result) #endif } +bool StringUtils::Parse(const Char* str, int32 length, bool* result) +{ + if ((length == 1 && CompareIgnoreCase(str, TEXT("1"), length) == 0) || + (length == 4 && CompareIgnoreCase(str, TEXT("true"), length) == 0)) + { + *result = true; + return false; + } + if ((length == 1 && CompareIgnoreCase(str, TEXT("0"), length) == 0) || + (length == 5 && CompareIgnoreCase(str, TEXT("false"), length) == 0)) + { + *result = false; + return false; + } + return true; +} + +String StringUtils::ToString(bool value) +{ + return value ? TEXT("true") : TEXT("false"); +} + String StringUtils::ToString(int32 value) { return String::Format(TEXT("{}"), value); diff --git a/Source/Engine/Platform/StringUtils.h b/Source/Engine/Platform/StringUtils.h index faa8127f8..be877fc6a 100644 --- a/Source/Engine/Platform/StringUtils.h +++ b/Source/Engine/Platform/StringUtils.h @@ -336,7 +336,11 @@ public: // Parses text to the scalar value. Returns true if failed to convert the value. static bool Parse(const char* str, float* result); + // Parses text to the boolean value. Returns true if failed to convert the value. + static bool Parse(const Char* str, int32 length, bool* result); + public: + static String ToString(bool value); static String ToString(int32 value); static String ToString(int64 value); static String ToString(uint32 value); diff --git a/Source/Engine/Tests/TestCommandLine.cpp b/Source/Engine/Tests/TestCommandLine.cpp deleted file mode 100644 index 8aaaa2aa8..000000000 --- a/Source/Engine/Tests/TestCommandLine.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// 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); - } - } -} diff --git a/Source/Tools/Flax.Build/CommandLine.cs b/Source/Tools/Flax.Build/CommandLine.cs index bc54675f5..523134618 100644 --- a/Source/Tools/Flax.Build/CommandLine.cs +++ b/Source/Tools/Flax.Build/CommandLine.cs @@ -266,6 +266,8 @@ namespace Flax.Build int nameStart = i; while (i < length && commandLine[i] != '-' && commandLine[i] != '=' && !char.IsWhiteSpace(commandLine[i])) i++; + if (wholeQuote) + i--; int nameEnd = i; string name = commandLine.Substring(nameStart, nameEnd - nameStart);