Expose raw arguments on the command context

This commit is contained in:
Patrik Svensson 2024-04-20 14:11:03 +02:00 committed by Patrik Svensson
parent de04619f88
commit 95bff47b85
6 changed files with 62 additions and 11 deletions

View File

@ -13,6 +13,11 @@ public sealed class CommandContext
/// </value>
public IRemainingArguments Remaining { get; }
/// <summary>
/// Gets all the arguments that were passed to the applicaton.
/// </summary>
public IReadOnlyList<string> Arguments { get; }
/// <summary>
/// Gets the name of the command.
/// </summary>
@ -32,11 +37,17 @@ public sealed class CommandContext
/// <summary>
/// Initializes a new instance of the <see cref="CommandContext"/> class.
/// </summary>
/// <param name="arguments">All arguments that were passed to the application.</param>
/// <param name="remaining">The remaining arguments.</param>
/// <param name="name">The command name.</param>
/// <param name="data">The command data.</param>
public CommandContext(IRemainingArguments remaining, string name, object? data)
public CommandContext(
IEnumerable<string> arguments,
IRemainingArguments remaining,
string name,
object? data)
{
Arguments = arguments.ToSafeReadOnlyList();
Remaining = remaining ?? throw new System.ArgumentNullException(nameof(remaining));
Name = name ?? throw new System.ArgumentNullException(nameof(name));
Data = data;

View File

@ -12,6 +12,7 @@ public interface IRemainingArguments
/// <summary>
/// Gets the raw, non-parsed remaining arguments.
/// This is normally everything after the `--` delimiter.
/// </summary>
IReadOnlyList<string> Raw { get; }
}

View File

@ -17,7 +17,7 @@ internal sealed class CommandExecutor
throw new ArgumentNullException(nameof(configuration));
}
args ??= new List<string>();
var arguments = args.ToSafeReadOnlyList();
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
@ -31,7 +31,7 @@ internal sealed class CommandExecutor
if (model.DefaultCommand == null)
{
// Got at least one argument?
var firstArgument = args.FirstOrDefault();
var firstArgument = arguments.FirstOrDefault();
if (firstArgument != null)
{
// Asking for version? Kind of a hack, but it's alright.
@ -47,7 +47,7 @@ internal sealed class CommandExecutor
}
// Parse and map the model against the arguments.
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, arguments);
// Register the arguments with the container.
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
@ -79,7 +79,7 @@ internal sealed class CommandExecutor
}
// Is this the default and is it called without arguments when there are required arguments?
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
if (leaf.Command.IsDefaultCommand && arguments.Count == 0 && leaf.Command.Parameters.Any(p => p.Required))
{
// Display help for default command.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
@ -87,15 +87,18 @@ internal sealed class CommandExecutor
}
// Create the content.
var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data);
var context = new CommandContext(
arguments,
parsedResult.Remaining,
leaf.Command.Name,
leaf.Command.Data);
// Execute the command tree.
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
}
}
#pragma warning disable CS8603 // Possible null reference return.
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IReadOnlyList<string> args)
{
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);
@ -103,7 +106,7 @@ internal sealed class CommandExecutor
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
var parsedResult = parser.Parse(parserContext, tokenizerResult);
var lastParsedLeaf = parsedResult?.Tree?.GetLeafCommand();
var lastParsedLeaf = parsedResult.Tree?.GetLeafCommand();
var lastParsedCommand = lastParsedLeaf?.Command;
if (lastParsedLeaf != null && lastParsedCommand != null &&
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
@ -122,7 +125,6 @@ internal sealed class CommandExecutor
return parsedResult;
}
#pragma warning restore CS8603 // Possible null reference return.
private static string ResolveApplicationVersion(IConfiguration configuration)
{

View File

@ -0,0 +1,14 @@
namespace Spectre.Console.Cli;
internal static class EnumerableExtensions
{
public static IReadOnlyList<T> ToSafeReadOnlyList<T>(this IEnumerable<T> source)
{
return source switch
{
null => new List<T>(),
IReadOnlyList<T> list => list,
_ => source.ToList(),
};
}
}

View File

@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,23 @@
namespace Spectre.Console.Tests.Unit.Cli;
public sealed partial class CommandAppTests
{
[Fact]
[Expectation("Should_Expose_Raw_Arguments")]
public void Should_Return_Correct_Text_When_Command_Is_Unknown()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.AddCommand<EmptyCommand>("test");
});
// When
var result = app.Run("test", "--foo", "32", "--lol");
// Then
result.Context.ShouldNotBeNull();
result.Context.Arguments.ShouldBe(new[] { "test", "--foo", "32", "--lol" });
}
}