mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 17:02:51 +08:00
Parse quoted strings correctly
When parsing quoted strings, space was not handled properly in remaining arguments. Fixes #186
This commit is contained in:
parent
241423dd16
commit
79742ce9e3
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,3 @@
|
|||||||
|
# Raw
|
||||||
|
/c
|
||||||
|
set && pause
|
@ -11,7 +11,8 @@ namespace Spectre.Console.Tests.Unit.Cli
|
|||||||
{
|
{
|
||||||
public sealed partial class CommandAppTests
|
public sealed partial class CommandAppTests
|
||||||
{
|
{
|
||||||
public static class Parsing
|
[UsesVerify]
|
||||||
|
public sealed class Parsing
|
||||||
{
|
{
|
||||||
[UsesVerify]
|
[UsesVerify]
|
||||||
public sealed class UnknownCommand
|
public sealed class UnknownCommand
|
||||||
@ -575,6 +576,23 @@ namespace Spectre.Console.Tests.Unit.Cli
|
|||||||
result.ShouldBe("Error: Command 'dog' is missing required argument 'AGE'.");
|
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
|
internal sealed class Fixture
|
||||||
|
@ -720,7 +720,7 @@ namespace Spectre.Console.Tests.Unit.Cli
|
|||||||
ctx.Remaining.Raw[0].ShouldBe("--foo");
|
ctx.Remaining.Raw[0].ShouldBe("--foo");
|
||||||
ctx.Remaining.Raw[1].ShouldBe("bar");
|
ctx.Remaining.Raw[1].ShouldBe("bar");
|
||||||
ctx.Remaining.Raw[2].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");
|
ctx.Remaining.Raw[4].ShouldBe("qux");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<SolutionConfiguration>
|
<SolutionConfiguration>
|
||||||
<Settings>
|
<Settings>
|
||||||
<AllowParallelTestExecution>True</AllowParallelTestExecution>
|
<AllowParallelTestExecution>True</AllowParallelTestExecution>
|
||||||
|
<InstrumentationMode>Optimised</InstrumentationMode>
|
||||||
<SolutionConfigured>True</SolutionConfigured>
|
<SolutionConfigured>True</SolutionConfigured>
|
||||||
</Settings>
|
</Settings>
|
||||||
</SolutionConfiguration>
|
</SolutionConfiguration>
|
@ -24,6 +24,7 @@ namespace Spectre.Console.Cli.Internal
|
|||||||
}
|
}
|
||||||
|
|
||||||
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
|
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
|
||||||
|
_registrar.RegisterInstance(typeof(IAnsiConsole), configuration.Settings.Console.GetConsole());
|
||||||
|
|
||||||
// Create the command model.
|
// Create the command model.
|
||||||
var model = CommandModelBuilder.Build(configuration);
|
var model = CommandModelBuilder.Build(configuration);
|
||||||
|
@ -116,34 +116,41 @@ namespace Spectre.Console.Cli.Internal
|
|||||||
{
|
{
|
||||||
var position = reader.Position;
|
var position = reader.Position;
|
||||||
|
|
||||||
|
context.FlushRemaining();
|
||||||
reader.Consume('\"');
|
reader.Consume('\"');
|
||||||
context.AddRemaining('\"');
|
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
var terminated = false;
|
||||||
while (!reader.ReachedEnd)
|
while (!reader.ReachedEnd)
|
||||||
{
|
{
|
||||||
var character = reader.Peek();
|
var character = reader.Peek();
|
||||||
if (character == '\"')
|
if (character == '\"')
|
||||||
{
|
{
|
||||||
|
terminated = true;
|
||||||
|
reader.Read();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.AddRemaining(character);
|
|
||||||
builder.Append(reader.Read());
|
builder.Append(reader.Read());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader.Peek() != '\"')
|
if (!terminated)
|
||||||
{
|
{
|
||||||
var unterminatedQuote = builder.ToString();
|
var unterminatedQuote = builder.ToString();
|
||||||
var token = new CommandTreeToken(CommandTreeToken.Kind.String, position, unterminatedQuote, $"\"{unterminatedQuote}");
|
var token = new CommandTreeToken(CommandTreeToken.Kind.String, position, unterminatedQuote, $"\"{unterminatedQuote}");
|
||||||
throw CommandParseException.UnterminatedQuote(reader.Original, token);
|
throw CommandParseException.UnterminatedQuote(reader.Original, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.Read();
|
var quotedString = builder.ToString();
|
||||||
context.AddRemaining('\"');
|
|
||||||
|
|
||||||
var value = builder.ToString();
|
// Add to the context
|
||||||
return new CommandTreeToken(CommandTreeToken.Kind.String, position, value, $"\"{value}\"");
|
context.AddRemaining(quotedString);
|
||||||
|
context.FlushRemaining();
|
||||||
|
|
||||||
|
return new CommandTreeToken(
|
||||||
|
CommandTreeToken.Kind.String,
|
||||||
|
position, quotedString,
|
||||||
|
quotedString);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<CommandTreeToken> ScanOptions(CommandTreeTokenizerContext context, TextBuffer reader)
|
private static IEnumerable<CommandTreeToken> ScanOptions(CommandTreeTokenizerContext context, TextBuffer reader)
|
||||||
|
@ -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()
|
public void FlushRemaining()
|
||||||
{
|
{
|
||||||
if (Mode == CommandTreeTokenizer.Mode.Remaining)
|
if (Mode == CommandTreeTokenizer.Mode.Remaining)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user