diff --git a/Cesium.Sdk.Tests/ArgumentUtilTests.cs b/Cesium.Sdk.Tests/ArgumentUtilTests.cs
new file mode 100644
index 00000000..7df90d99
--- /dev/null
+++ b/Cesium.Sdk.Tests/ArgumentUtilTests.cs
@@ -0,0 +1,17 @@
+namespace Cesium.Sdk.Tests;
+
+public class ArgumentUtilTests
+{
+ [Fact]
+ public void PerformTests()
+ {
+ Assert.Equal("\"\"", ArgumentUtil.ToCommandLineString([""]));
+ Assert.Equal("a b c", ArgumentUtil.ToCommandLineString(["a", "b", "c"]));
+ Assert.Equal("\"a b\" c", ArgumentUtil.ToCommandLineString(["a b", "c"]));
+ Assert.Equal("a\\b c", ArgumentUtil.ToCommandLineString([@"a\b", "c"]));
+ Assert.Equal("\"\\\"\"", ArgumentUtil.ToCommandLineString(["\""]));
+ Assert.Equal("\"a \\\"b\\\"\"", ArgumentUtil.ToCommandLineString(["a \"b\""]));
+ Assert.Equal("\"\\\\\\\"\"", ArgumentUtil.ToCommandLineString(["\\\""]));
+ Assert.Equal("\"a\\ \\\\\\\"b\\\"\"", ArgumentUtil.ToCommandLineString(["a\\ \\\"b\""]));
+ }
+}
diff --git a/Cesium.Sdk.Tests/Cesium.Sdk.Tests.csproj b/Cesium.Sdk.Tests/Cesium.Sdk.Tests.csproj
index 2d715572..bc129dc3 100644
--- a/Cesium.Sdk.Tests/Cesium.Sdk.Tests.csproj
+++ b/Cesium.Sdk.Tests/Cesium.Sdk.Tests.csproj
@@ -31,6 +31,7 @@
+
diff --git a/Cesium.Sdk/ArgumentUtil.cs b/Cesium.Sdk/ArgumentUtil.cs
new file mode 100644
index 00000000..8df5dd0c
--- /dev/null
+++ b/Cesium.Sdk/ArgumentUtil.cs
@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Cesium.Sdk;
+
+public static class ArgumentUtil
+{
+ public static string ToCommandLineString(IEnumerable args)
+ {
+ var result = new StringBuilder();
+ var first = true;
+ foreach (var a in args)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ result.Append(' ');
+ }
+ if (a.Length == 0 || a.Any(c => char.IsWhiteSpace(c) || c == '"'))
+ {
+ result.Append(Quoted(a));
+ }
+ else
+ {
+ result.Append(a);
+ }
+ }
+ return result.ToString();
+ }
+
+ private static string Quoted(string arg)
+ {
+ // The simplified rules:
+ // 1. Every quote should be escaped by \
+ // 2. Slashes preceding the quotes should be escaped by \
+ //
+ // Meaning any amount of slashes following a quote should be converted to twice as many slashes + slash + quote.
+ // The final quote (not part of the argument per se) also counts as quote for this purpose.
+ var result = new StringBuilder(arg.Length + 2);
+ result.Append('"');
+ var slashes = 0;
+ foreach (var c in arg)
+ {
+ switch (c)
+ {
+ case '\\':
+ slashes++;
+ break;
+ case '"':
+ result.Append('\\', slashes * 2);
+ slashes = 0;
+
+ result.Append("\\\"");
+ break;
+ default:
+ result.Append('\\', slashes);
+ slashes = 0;
+
+ result.Append(c);
+ break;
+ }
+ }
+
+ result.Append('\\', slashes * 2);
+ result.Append('"');
+
+ return result.ToString();
+ }
+}
diff --git a/Cesium.Sdk/CesiumCompile.cs b/Cesium.Sdk/CesiumCompile.cs
index 7201cee3..cc756379 100644
--- a/Cesium.Sdk/CesiumCompile.cs
+++ b/Cesium.Sdk/CesiumCompile.cs
@@ -187,67 +187,67 @@ private bool TryValidate(out ValidatedOptions? options)
private string CollectCommandLineArguments(ValidatedOptions options)
{
- var builder = new CommandArgumentsBuilder();
+ var args = new List();
- builder.RawArgument("--nologo");
+ args.Add("--nologo");
if (options.Framework is { } framework)
{
- builder.RawArgument("--framework");
- builder.RawArgument(framework.ToString());
+ args.Add("--framework");
+ args.Add(framework.ToString());
}
if (options.Architecture is { } arch)
{
- builder.RawArgument("--arch");
- builder.RawArgument(arch.ToString());
+ args.Add("--arch");
+ args.Add(arch.ToString());
}
if (options.ModuleKind is { } moduleKind)
{
- builder.RawArgument("--modulekind");
- builder.RawArgument(moduleKind.ToString());
+ args.Add("--modulekind");
+ args.Add(moduleKind.ToString());
}
if (!string.IsNullOrWhiteSpace(options.Namespace))
{
- builder.RawArgument("--namespace");
- builder.RawArgument(options.Namespace!);
+ args.Add("--namespace");
+ args.Add(options.Namespace!);
}
foreach (var import in options.ImportItems)
{
- builder.RawArgument("--import");
- builder.Argument(import);
+ args.Add("--import");
+ args.Add(import);
}
if (!string.IsNullOrWhiteSpace(options.CoreLibPath))
{
- builder.RawArgument("--corelib");
- builder.Argument(options.CoreLibPath!);
+ args.Add("--corelib");
+ args.Add(options.CoreLibPath!);
}
if (!string.IsNullOrWhiteSpace(options.RuntimePath))
{
- builder.RawArgument("--runtime");
- builder.Argument(options.RuntimePath!);
+ args.Add("--runtime");
+ args.Add(options.RuntimePath!);
}
foreach (var item in options.PreprocessorItems)
{
- builder.RawArgument("-D");
- builder.Argument(item);
+ args.Add("-D");
+ args.Add(item);
}
- builder.RawArgument("--out");
- builder.Argument(options.OutputFile);
+ args.Add("--out");
+ args.Add(options.OutputFile);
foreach (var input in options.InputItems)
{
- builder.Argument(input);
+ args.Add(input);
}
- return builder.Build();
+ return ArgumentUtil.ToCommandLineString(args);
}
private void ReportValidationError(string code, string message) =>
diff --git a/Cesium.Sdk/CommandArgumentsBuilder.cs b/Cesium.Sdk/CommandArgumentsBuilder.cs
deleted file mode 100644
index ffcc4077..00000000
--- a/Cesium.Sdk/CommandArgumentsBuilder.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System.Linq;
-using System.Text;
-
-namespace Cesium.Sdk;
-
-// Implementation reference:
-// https://github.com/Tyrrrz/CliWrap/blob/417aaffe171b9897799ed2a28a6467c11d69c296/CliWrap/Builders/ArgumentsBuilder.cs
-// MIT License, Oleksii Holub
-public class CommandArgumentsBuilder
-{
- private StringBuilder _builder = new StringBuilder();
-
- public CommandArgumentsBuilder RawArgument(string value)
- {
- _builder.Append(' ');
- _builder.Append(value);
-
- return this;
- }
-
- public CommandArgumentsBuilder Argument(string argument)
- {
- _builder.Append(' ');
- if (NeedsEscaping(argument))
- argument = Escape(argument);
- _builder.Append(argument);
-
- return this;
- }
-
- public string Build() => _builder.ToString();
-
- private bool NeedsEscaping(string argument) =>
- argument.Length > 0 && argument.Any(c => char.IsWhiteSpace(c) || c != '"');
-
- private static string Escape(string argument)
- {
- var buffer = new StringBuilder();
-
- buffer.Append('"');
-
- for (var i = 0; i < argument.Length;)
- {
- var c = argument[i++];
-
- if (c == '\\')
- {
- var backslashCount = 1;
- while (i < argument.Length && argument[i] == '\\')
- {
- backslashCount++;
- i++;
- }
-
- if (i == argument.Length)
- {
- buffer.Append('\\', backslashCount * 2);
- }
- else if (argument[i] == '"')
- {
- buffer
- .Append('\\', backslashCount * 2 + 1)
- .Append('"');
-
- i++;
- }
- else
- {
- buffer.Append('\\', backslashCount);
- }
- }
- else if (c == '"')
- {
- buffer.Append('\\').Append('"');
- }
- else
- {
- buffer.Append(c);
- }
- }
-
- buffer.Append('"');
-
- return buffer.ToString();
- }
-}