Skip to content

Commit

Permalink
Support rsp in MTP
Browse files Browse the repository at this point in the history
  • Loading branch information
Youssef1313 committed Nov 17, 2024
1 parent b769496 commit 41d38e8
Show file tree
Hide file tree
Showing 18 changed files with 318 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@

namespace Microsoft.Testing.Platform.CommandLine;

internal sealed class CommandLineParseResult(string? toolName, IReadOnlyList<OptionRecord> options, IReadOnlyList<string> errors, IReadOnlyList<string> originalArguments) : IEquatable<CommandLineParseResult>
internal sealed class CommandLineParseResult(string? toolName, IReadOnlyList<OptionRecord> options, IReadOnlyList<string> errors) : IEquatable<CommandLineParseResult>
{
public const char OptionPrefix = '-';

public static CommandLineParseResult Empty => new(null, [], [], []);
public static CommandLineParseResult Empty => new(null, [], []);

public string? ToolName { get; } = toolName;

public IReadOnlyList<OptionRecord> Options { get; } = options;

public IReadOnlyList<string> Errors { get; } = errors;

public IReadOnlyList<string> OriginalArguments { get; } = originalArguments;

public bool HasError => Errors.Count > 0;

public bool HasTool => ToolName is not null;
Expand Down
13 changes: 11 additions & 2 deletions src/Platform/Microsoft.Testing.Platform/CommandLine/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ internal static class CommandLineParser
/// * A POSIX convention lets you omit the delimiter when you are specifying a single-character option alias, i.e. myapp -vquiet.
/// </summary>
public static CommandLineParseResult Parse(string[] args, IEnvironment environment)
=> Parse(args.ToList(), environment);

private static CommandLineParseResult Parse(List<string> args, IEnvironment environment)
{
List<OptionRecord> options = [];
List<string> errors = [];
Expand All @@ -41,8 +44,14 @@ public static CommandLineParseResult Parse(string[] args, IEnvironment environme
string? currentArg = null;
string? toolName = null;
List<string> currentOptionArguments = [];
for (int i = 0; i < args.Length; i++)
for (int i = 0; i < args.Count; i++)
{
if (args[i].StartsWith('@') && ResponseFileHelper.TryReadResponseFile(args[i].Substring(1), errors, out string[]? newArguments))
{
args.InsertRange(i + 1, newArguments);
continue;
}

bool argumentHandled = false;
currentArg = args[i];

Expand Down Expand Up @@ -118,7 +127,7 @@ public static CommandLineParseResult Parse(string[] args, IEnvironment environme
options.Add(new(currentOption, currentOptionArguments.ToArray()));
}

return new CommandLineParseResult(toolName, options, errors, args);
return new CommandLineParseResult(toolName, options, errors);

static void ParseOptionAndSeparators(string arg, out string? currentOption, out string? currentArg)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics.CodeAnalysis;
using System.Globalization;

using Microsoft.Testing.Platform.Resources;

// Most of the core logic is from https://github.com/dotnet/command-line-api/blob/feb61c7f328a2401d74f4317b39d02126cfdfe24/src/System.CommandLine/Parsing/CliParser.cs#L49
internal static class ResponseFileHelper
{
internal static bool TryReadResponseFile(string rspFilePath, List<string> errors, [NotNullWhen(true)] out string[]? newArguments)
{
try
{
newArguments = ExpandResponseFile(rspFilePath).ToArray();
return true;
}
catch (FileNotFoundException)
{
errors.Add(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineParserResponseFileNotFound, rspFilePath));
}
catch (IOException e)
{
errors.Add(string.Format(CultureInfo.InvariantCulture, PlatformResources.CommandLineParserFailedToReadResponseFile, rspFilePath, e.Message));
}

newArguments = null;
return false;

static IEnumerable<string> ExpandResponseFile(string filePath)
{
string[] lines = File.ReadAllLines(filePath);

for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];

foreach (string p in SplitLine(line))
{
yield return p;
}
}
}

static IEnumerable<string> SplitLine(string line)
{
string arg = line.Trim();

if (arg.Length == 0 || arg[0] == '#')
{
yield break;
}

foreach (string word in SplitCommandLine(arg))
{
yield return word;
}
}
}

private enum Boundary
{
TokenStart,
WordEnd,
QuoteStart,
QuoteEnd,
}

public static IEnumerable<string> SplitCommandLine(string commandLine)
{
int startTokenIndex = 0;

int pos = 0;

Boundary seeking = Boundary.TokenStart;
Boundary seekingQuote = Boundary.QuoteStart;

while (pos < commandLine.Length)
{
char c = commandLine[pos];

if (char.IsWhiteSpace(c))
{
if (seekingQuote == Boundary.QuoteStart)
{
switch (seeking)
{
case Boundary.WordEnd:
yield return CurrentToken();
startTokenIndex = pos;
seeking = Boundary.TokenStart;
break;

case Boundary.TokenStart:
startTokenIndex = pos;
break;
}
}
}
else if (c == '\"')
{
if (seeking == Boundary.TokenStart)
{
switch (seekingQuote)
{
case Boundary.QuoteEnd:
yield return CurrentToken();
startTokenIndex = pos;
seekingQuote = Boundary.QuoteStart;
break;

case Boundary.QuoteStart:
startTokenIndex = pos + 1;
seekingQuote = Boundary.QuoteEnd;
break;
}
}
else
{
switch (seekingQuote)
{
case Boundary.QuoteEnd:
seekingQuote = Boundary.QuoteStart;
break;

case Boundary.QuoteStart:
seekingQuote = Boundary.QuoteEnd;
break;
}
}
}
else if (seeking == Boundary.TokenStart && seekingQuote == Boundary.QuoteStart)
{
seeking = Boundary.WordEnd;
startTokenIndex = pos;
}

Advance();

if (IsAtEndOfInput())
{
switch (seeking)
{
case Boundary.TokenStart:
break;
default:
yield return CurrentToken();
break;
}
}
}

void Advance() => pos++;

string CurrentToken() => commandLine.Substring(startTokenIndex, IndexOfEndOfToken()).ToString().Replace("\"", string.Empty);

int IndexOfEndOfToken() => pos - startTokenIndex;

bool IsAtEndOfInput() => pos == commandLine.Length;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -655,4 +655,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
<data name="ExitCode" xml:space="preserve">
<value>Exit code</value>
</data>
<data name="CommandLineParserResponseFileNotFound" xml:space="preserve">
<value>The response file '{0}' was not found</value>
</data>
<data name="CommandLineParserFailedToReadResponseFile" xml:space="preserve">
<value>Failed to read response file '{0}'. {1}.</value>
<comment>{1} is the exception</comment>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">Rozhraní ICommandLineOptions ještě není sestavené.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">Neočekávaný argument {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">ICommandLineOptions wurde noch nicht erstellt.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">Unerwartetes Argument {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">ICommandLineOptions aún no se ha compilado.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">Argumento inesperado {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">ICommandLineOptions n’a pas encore été généré.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">Arguments inattendue {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">ICommandLineOptions non è stato ancora compilato.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">Argomento imprevisto {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">ICommandLineOptions はまだ構築されていません。</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">予期しない引数 {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">ICommandLineOptions가 아직 빌드되지 않았습니다.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">예기치 않은 인수 {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">Obiekt ICommandLineOptions nie został jeszcze skompilowany.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">Nieoczekiwany argument {0}</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@
<target state="translated">O ICommandLineOptions ainda não foi criado.</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserFailedToReadResponseFile">
<source>Failed to read response file '{0}'. {1}.</source>
<target state="new">Failed to read response file '{0}'. {1}.</target>
<note>{1} is the exception</note>
</trans-unit>
<trans-unit id="CommandLineParserResponseFileNotFound">
<source>The response file '{0}' was not found</source>
<target state="new">The response file '{0}' was not found</target>
<note />
</trans-unit>
<trans-unit id="CommandLineParserUnexpectedArgument">
<source>Unexpected argument {0}</source>
<target state="translated">Argumento inesperado {0}</target>
Expand Down
Loading

0 comments on commit 41d38e8

Please sign in to comment.