Parse quoted strings correctly

When parsing quoted strings, space was not handled
properly in remaining arguments.

Fixes #186
This commit is contained in:
Patrik Svensson 2020-12-30 18:29:20 +01:00 committed by Patrik Svensson
parent 241423dd16
commit 79742ce9e3
9 changed files with 86 additions and 31 deletions

View File

@ -0,0 +1,39 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Spectre.Console.Cli;
namespace Spectre.Console.Tests.Data
{
public sealed class DumpRemainingCommand : Command<EmptyCommandSettings>
{
private readonly IAnsiConsole _console;
public DumpRemainingCommand(IAnsiConsole console)
{
_console = console;
}
public override int Execute([NotNull] CommandContext context, [NotNull] EmptyCommandSettings settings)
{
if (context.Remaining.Raw.Count > 0)
{
_console.WriteLine("# Raw");
foreach (var item in context.Remaining.Raw)
{
_console.WriteLine(item);
}
}
if (context.Remaining.Parsed.Count > 0)
{
_console.WriteLine("# Parsed");
foreach (var item in context.Remaining.Parsed)
{
_console.WriteLine(string.Format("{0}={1}", item.Key, string.Join(",", item.Select(x => x))));
}
}
return 0;
}
}
}

View File

@ -1,22 +0,0 @@
using System;
using Spectre.Console.Cli;
namespace Spectre.Console.Tests.Data
{
public sealed class InterceptingCommand<TSettings> : Command<TSettings>
where TSettings : CommandSettings
{
private readonly Action<CommandContext, TSettings> _action;
public InterceptingCommand(Action<CommandContext, TSettings> action)
{
_action = action ?? throw new ArgumentNullException(nameof(action));
}
public override int Execute(CommandContext context, TSettings settings)
{
_action(context, settings);
return 0;
}
}
}

View File

@ -11,7 +11,8 @@ namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public static class Parsing
[UsesVerify]
public sealed class Parsing
{
[UsesVerify]
public sealed class UnknownCommand
@ -575,6 +576,23 @@ namespace Spectre.Console.Tests.Unit.Cli
result.ShouldBe("Error: Command 'dog' is missing required argument 'AGE'.");
}
}
[Fact]
public Task Should_Parse_Quoted_Strings_Correctly()
{
// Given
var fixture = new Fixture();
fixture.Configure(configurator =>
{
configurator.AddCommand<DumpRemainingCommand>("foo");
});
// When
var result = fixture.Run("foo", "--", "/c", "\"set && pause\"");
// Then
return Verifier.Verify(result);
}
}
internal sealed class Fixture

View File

@ -720,7 +720,7 @@ namespace Spectre.Console.Tests.Unit.Cli
ctx.Remaining.Raw[0].ShouldBe("--foo");
ctx.Remaining.Raw[1].ShouldBe("bar");
ctx.Remaining.Raw[2].ShouldBe("-bar");
ctx.Remaining.Raw[3].ShouldBe("\"baz\"");
ctx.Remaining.Raw[3].ShouldBe("baz");
ctx.Remaining.Raw[4].ShouldBe("qux");
}
}

View File

@ -1,6 +1,7 @@
<SolutionConfiguration>
<Settings>
<AllowParallelTestExecution>True</AllowParallelTestExecution>
<InstrumentationMode>Optimised</InstrumentationMode>
<SolutionConfigured>True</SolutionConfigured>
</Settings>
</SolutionConfiguration>

View File

@ -24,6 +24,7 @@ namespace Spectre.Console.Cli.Internal
}
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
_registrar.RegisterInstance(typeof(IAnsiConsole), configuration.Settings.Console.GetConsole());
// Create the command model.
var model = CommandModelBuilder.Build(configuration);

View File

@ -116,34 +116,41 @@ namespace Spectre.Console.Cli.Internal
{
var position = reader.Position;
context.FlushRemaining();
reader.Consume('\"');
context.AddRemaining('\"');
var builder = new StringBuilder();
var terminated = false;
while (!reader.ReachedEnd)
{
var character = reader.Peek();
if (character == '\"')
{
terminated = true;
reader.Read();
break;
}
context.AddRemaining(character);
builder.Append(reader.Read());
}
if (reader.Peek() != '\"')
if (!terminated)
{
var unterminatedQuote = builder.ToString();
var token = new CommandTreeToken(CommandTreeToken.Kind.String, position, unterminatedQuote, $"\"{unterminatedQuote}");
throw CommandParseException.UnterminatedQuote(reader.Original, token);
}
reader.Read();
context.AddRemaining('\"');
var quotedString = builder.ToString();
var value = builder.ToString();
return new CommandTreeToken(CommandTreeToken.Kind.String, position, value, $"\"{value}\"");
// Add to the context
context.AddRemaining(quotedString);
context.FlushRemaining();
return new CommandTreeToken(
CommandTreeToken.Kind.String,
position, quotedString,
quotedString);
}
private static IEnumerable<CommandTreeToken> ScanOptions(CommandTreeTokenizerContext context, TextBuffer reader)

View File

@ -32,6 +32,14 @@ namespace Spectre.Console.Cli.Internal
}
}
public void AddRemaining(string text)
{
if (Mode == CommandTreeTokenizer.Mode.Remaining)
{
_builder.Append(text);
}
}
public void FlushRemaining()
{
if (Mode == CommandTreeTokenizer.Mode.Remaining)