-
Notifications
You must be signed in to change notification settings - Fork 543
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement IResourceWithArgs on ProjectResource (#3559)
Also fix the command line arg parsing to be the same as .NET Process's: * double quotes around single arguments * consecutive double quotes inside a quoted argument means one double quote Fix #3306 Co-authored-by: Eric Erhardt <[email protected]>
- Loading branch information
1 parent
9a507d0
commit f1b9c55
Showing
7 changed files
with
238 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Text; | ||
|
||
namespace Aspire.Hosting.Utils; | ||
|
||
internal static class CommandLineArgsParser | ||
{ | ||
/// <summary>Parses a command-line argument string into a list of arguments.</summary> | ||
public static List<string> Parse(string arguments) | ||
{ | ||
var result = new List<string>(); | ||
ParseArgumentsIntoList(arguments, result); | ||
return result; | ||
} | ||
|
||
/// <summary>Parses a command-line argument string into a list of arguments.</summary> | ||
/// <param name="arguments">The argument string.</param> | ||
/// <param name="results">The list into which the component arguments should be stored.</param> | ||
/// <remarks> | ||
/// This follows the rules outlined in "Parsing C++ Command-Line Arguments" at | ||
/// https://msdn.microsoft.com/en-us/library/17w5ykft.aspx. | ||
/// </remarks> | ||
// copied from https://github.com/dotnet/runtime/blob/404b286b23093cd93a985791934756f64a33483e/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Unix.cs#L846-L945 | ||
private static void ParseArgumentsIntoList(string arguments, List<string> results) | ||
{ | ||
// Iterate through all of the characters in the argument string. | ||
for (int i = 0; i < arguments.Length; i++) | ||
{ | ||
while (i < arguments.Length && (arguments[i] == ' ' || arguments[i] == '\t')) | ||
{ | ||
i++; | ||
} | ||
|
||
if (i == arguments.Length) | ||
{ | ||
break; | ||
} | ||
|
||
results.Add(GetNextArgument(arguments, ref i)); | ||
} | ||
} | ||
|
||
private static string GetNextArgument(string arguments, ref int i) | ||
{ | ||
var currentArgument = new StringBuilder(); | ||
bool inQuotes = false; | ||
|
||
while (i < arguments.Length) | ||
{ | ||
// From the current position, iterate through contiguous backslashes. | ||
int backslashCount = 0; | ||
while (i < arguments.Length && arguments[i] == '\\') | ||
{ | ||
i++; | ||
backslashCount++; | ||
} | ||
|
||
if (backslashCount > 0) | ||
{ | ||
if (i >= arguments.Length || arguments[i] != '"') | ||
{ | ||
// Backslashes not followed by a double quote: | ||
// they should all be treated as literal backslashes. | ||
currentArgument.Append('\\', backslashCount); | ||
} | ||
else | ||
{ | ||
// Backslashes followed by a double quote: | ||
// - Output a literal slash for each complete pair of slashes | ||
// - If one remains, use it to make the subsequent quote a literal. | ||
currentArgument.Append('\\', backslashCount / 2); | ||
if (backslashCount % 2 != 0) | ||
{ | ||
currentArgument.Append('"'); | ||
i++; | ||
} | ||
} | ||
|
||
continue; | ||
} | ||
|
||
char c = arguments[i]; | ||
|
||
// If this is a double quote, track whether we're inside of quotes or not. | ||
// Anything within quotes will be treated as a single argument, even if | ||
// it contains spaces. | ||
if (c == '"') | ||
{ | ||
if (inQuotes && i < arguments.Length - 1 && arguments[i + 1] == '"') | ||
{ | ||
// Two consecutive double quotes inside an inQuotes region should result in a literal double quote | ||
// (the parser is left in the inQuotes region). | ||
// This behavior is not part of the spec of code:ParseArgumentsIntoList, but is compatible with CRT | ||
// and .NET Framework. | ||
currentArgument.Append('"'); | ||
i++; | ||
} | ||
else | ||
{ | ||
inQuotes = !inQuotes; | ||
} | ||
|
||
i++; | ||
continue; | ||
} | ||
|
||
// If this is a space/tab and we're not in quotes, we're done with the current | ||
// argument, it should be added to the results and then reset for the next one. | ||
if ((c == ' ' || c == '\t') && !inQuotes) | ||
{ | ||
break; | ||
} | ||
|
||
// Nothing special; add the character to the current argument. | ||
currentArgument.Append(c); | ||
i++; | ||
} | ||
|
||
return currentArgument.ToString(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
tests/Aspire.Hosting.Tests/Utils/CommandLineArgsParserTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using Xunit; | ||
using static Aspire.Hosting.Utils.CommandLineArgsParser; | ||
|
||
namespace Aspire.Hosting.Tests.Utils; | ||
|
||
public class CommandLineArgsParserTests | ||
{ | ||
[Theory] | ||
[InlineData("", new string[] { })] | ||
[InlineData("single", new[] { "single" })] | ||
[InlineData("hello world", new[] { "hello", "world" })] | ||
[InlineData("foo bar baz", new[] { "foo", "bar", "baz" })] | ||
[InlineData("foo\tbar\tbaz", new[] { "foo", "bar", "baz" })] | ||
[InlineData("\"quoted string\"", new[] { "quoted string" })] | ||
[InlineData("\"quoted\tstring\"", new[] { "quoted\tstring" })] | ||
[InlineData("\"quoted \"\" string\"", new[] { "quoted \" string" })] | ||
// Single quotes are not treated as string delimiters | ||
[InlineData("\"hello 'world'\"", new[] { "hello 'world'" })] | ||
[InlineData("'single quoted'", new[] { "'single", "quoted'" })] | ||
[InlineData("'foo \"bar\" baz'", new[] { "'foo", "bar", "baz'" })] | ||
public void TestParse(string commandLine, string[] expectedParsed) | ||
{ | ||
var actualParsed = Parse(commandLine); | ||
|
||
Assert.Equal(expectedParsed, actualParsed.ToArray()); | ||
} | ||
} |