From 79742ce9e34e1a81780bd183cd7a5b7de0cea3da Mon Sep 17 00:00:00 2001 From: Patrik Svensson Date: Wed, 30 Dec 2020 18:29:20 +0100 Subject: [PATCH] Parse quoted strings correctly When parsing quoted strings, space was not handled properly in remaining arguments. Fixes #186 --- .../Data/Commands/DumpRemainingCommand.cs | 39 +++++++++++++++++++ .../Data/Commands/InterceptingCommand.cs | 22 ----------- ...arse_Quoted_Strings_Correctly.verified.txt | 3 ++ .../Unit/Cli/CommandAppTests.Parsing.cs | 20 +++++++++- .../Unit/Cli/CommandAppTests.cs | 2 +- src/Spectre.Console.v3.ncrunchsolution | 1 + .../Cli/Internal/CommandExecutor.cs | 1 + .../Internal/Parsing/CommandTreeTokenizer.cs | 21 ++++++---- .../Parsing/CommandTreeTokenizerContext.cs | 8 ++++ 9 files changed, 86 insertions(+), 31 deletions(-) create mode 100644 src/Spectre.Console.Tests/Data/Commands/DumpRemainingCommand.cs delete mode 100644 src/Spectre.Console.Tests/Data/Commands/InterceptingCommand.cs create mode 100644 src/Spectre.Console.Tests/Expectations/CommandAppTests.Parsing.Parsing.Should_Parse_Quoted_Strings_Correctly.verified.txt diff --git a/src/Spectre.Console.Tests/Data/Commands/DumpRemainingCommand.cs b/src/Spectre.Console.Tests/Data/Commands/DumpRemainingCommand.cs new file mode 100644 index 0000000..8c3e078 --- /dev/null +++ b/src/Spectre.Console.Tests/Data/Commands/DumpRemainingCommand.cs @@ -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 + { + 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; + } + } +} diff --git a/src/Spectre.Console.Tests/Data/Commands/InterceptingCommand.cs b/src/Spectre.Console.Tests/Data/Commands/InterceptingCommand.cs deleted file mode 100644 index 6e71ca9..0000000 --- a/src/Spectre.Console.Tests/Data/Commands/InterceptingCommand.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Spectre.Console.Cli; - -namespace Spectre.Console.Tests.Data -{ - public sealed class InterceptingCommand : Command - where TSettings : CommandSettings - { - private readonly Action _action; - - public InterceptingCommand(Action action) - { - _action = action ?? throw new ArgumentNullException(nameof(action)); - } - - public override int Execute(CommandContext context, TSettings settings) - { - _action(context, settings); - return 0; - } - } -} diff --git a/src/Spectre.Console.Tests/Expectations/CommandAppTests.Parsing.Parsing.Should_Parse_Quoted_Strings_Correctly.verified.txt b/src/Spectre.Console.Tests/Expectations/CommandAppTests.Parsing.Parsing.Should_Parse_Quoted_Strings_Correctly.verified.txt new file mode 100644 index 0000000..1c05483 --- /dev/null +++ b/src/Spectre.Console.Tests/Expectations/CommandAppTests.Parsing.Parsing.Should_Parse_Quoted_Strings_Correctly.verified.txt @@ -0,0 +1,3 @@ +# Raw +/c +set && pause \ No newline at end of file diff --git a/src/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Parsing.cs b/src/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Parsing.cs index 8820ff5..3247f8e 100644 --- a/src/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Parsing.cs +++ b/src/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Parsing.cs @@ -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("foo"); + }); + + // When + var result = fixture.Run("foo", "--", "/c", "\"set && pause\""); + + // Then + return Verifier.Verify(result); + } } internal sealed class Fixture diff --git a/src/Spectre.Console.Tests/Unit/Cli/CommandAppTests.cs b/src/Spectre.Console.Tests/Unit/Cli/CommandAppTests.cs index d366b0f..d877c72 100644 --- a/src/Spectre.Console.Tests/Unit/Cli/CommandAppTests.cs +++ b/src/Spectre.Console.Tests/Unit/Cli/CommandAppTests.cs @@ -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"); } } diff --git a/src/Spectre.Console.v3.ncrunchsolution b/src/Spectre.Console.v3.ncrunchsolution index 10420ac..c0d9e77 100644 --- a/src/Spectre.Console.v3.ncrunchsolution +++ b/src/Spectre.Console.v3.ncrunchsolution @@ -1,6 +1,7 @@  True + Optimised True \ No newline at end of file diff --git a/src/Spectre.Console/Cli/Internal/CommandExecutor.cs b/src/Spectre.Console/Cli/Internal/CommandExecutor.cs index 0d3c314..7a51dbe 100644 --- a/src/Spectre.Console/Cli/Internal/CommandExecutor.cs +++ b/src/Spectre.Console/Cli/Internal/CommandExecutor.cs @@ -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); diff --git a/src/Spectre.Console/Cli/Internal/Parsing/CommandTreeTokenizer.cs b/src/Spectre.Console/Cli/Internal/Parsing/CommandTreeTokenizer.cs index bce85c8..8314618 100644 --- a/src/Spectre.Console/Cli/Internal/Parsing/CommandTreeTokenizer.cs +++ b/src/Spectre.Console/Cli/Internal/Parsing/CommandTreeTokenizer.cs @@ -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 ScanOptions(CommandTreeTokenizerContext context, TextBuffer reader) diff --git a/src/Spectre.Console/Cli/Internal/Parsing/CommandTreeTokenizerContext.cs b/src/Spectre.Console/Cli/Internal/Parsing/CommandTreeTokenizerContext.cs index 8210f59..4d8a2cb 100644 --- a/src/Spectre.Console/Cli/Internal/Parsing/CommandTreeTokenizerContext.cs +++ b/src/Spectre.Console/Cli/Internal/Parsing/CommandTreeTokenizerContext.cs @@ -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)