From eb188aed9674b44c98954b5336d480a8314f1ae8 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Mon, 27 Mar 2023 17:41:22 -0700 Subject: [PATCH 1/2] add SetAction overloads returning int and Task --- ...ommandLine_api_is_not_changed.approved.txt | 2 + .../Invocation/InvocationExtensionsTests.cs | 73 +++++++++++++++---- src/System.CommandLine/Command.cs | 20 +++++ .../Invocation/AnonymousCliAction.cs | 24 +++--- 4 files changed, 92 insertions(+), 27 deletions(-) diff --git a/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt b/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt index 6ecff1a482..b0f148f4f2 100644 --- a/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt +++ b/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt @@ -54,7 +54,9 @@ System.CommandLine public ParseResult Parse(System.Collections.Generic.IReadOnlyList args, CommandLineConfiguration configuration = null) public ParseResult Parse(System.String commandLine, CommandLineConfiguration configuration = null) public System.Void SetAction(System.Action action) + public System.Void SetAction(System.Func action) public System.Void SetAction(System.Func action) + public System.Void SetAction(System.Func> action) public class CommandLineBuilder .ctor(Command rootCommand) public Command Command { get; } diff --git a/src/System.CommandLine.Tests/Invocation/InvocationExtensionsTests.cs b/src/System.CommandLine.Tests/Invocation/InvocationExtensionsTests.cs index 3305fd48f9..453e801240 100644 --- a/src/System.CommandLine.Tests/Invocation/InvocationExtensionsTests.cs +++ b/src/System.CommandLine.Tests/Invocation/InvocationExtensionsTests.cs @@ -14,7 +14,7 @@ namespace System.CommandLine.Tests.Invocation public class InvocationExtensionsTests { [Fact] - public async Task Command_InvokeAsync_uses_default_pipeline_by_default() + public async Task Command_InvokeAsync_enables_help_by_default() { var command = new Command("the-command") { @@ -31,14 +31,13 @@ public async Task Command_InvokeAsync_uses_default_pipeline_by_default() await command.Parse("-h", config).InvokeAsync(); - output - .ToString() - .Should() - .Contain(theHelpText); + output.ToString() + .Should() + .Contain(theHelpText); } [Fact] - public void Command_Invoke_uses_default_pipeline_by_default() + public void Command_Invoke_enables_help_by_default() { var command = new Command("the-command") { @@ -55,10 +54,9 @@ public void Command_Invoke_uses_default_pipeline_by_default() command.Parse("-h", config).Invoke(); - output - .ToString() - .Should() - .Contain(theHelpText); + output.ToString() + .Should() + .Contain(theHelpText); } [Fact] @@ -132,17 +130,66 @@ public void RootCommand_Invoke_returns_1_when_handler_throws() } [Fact] - public async Task RootCommand_Action_can_set_custom_result_code() + public void Custom_RootCommand_Action_can_set_custom_result_code_via_Invoke() { - var rootCommand = new RootCommand() + var rootCommand = new RootCommand { Action = new CustomExitCodeAction() }; rootCommand.Parse("").Invoke().Should().Be(123); + } + + [Fact] + public async Task Custom_RootCommand_Action_can_set_custom_result_code_via_InvokeAsync() + { + var rootCommand = new RootCommand + { + Action = new CustomExitCodeAction() + }; + (await rootCommand.Parse("").InvokeAsync()).Should().Be(456); } + [Fact] + public void Anonymous_RootCommand_Task_returning_Action_can_set_custom_result_code_via_Invoke() + { + var rootCommand = new RootCommand(); + + rootCommand.SetAction((_, _) => Task.FromResult(123)); + + rootCommand.Parse("").Invoke().Should().Be(123); + } + + [Fact] + public async Task Anonymous_RootCommand_Task_returning_Action_can_set_custom_result_code_via_InvokeAsync() + { + var rootCommand = new RootCommand(); + + rootCommand.SetAction((_, _) => Task.FromResult(123)); + + (await rootCommand.Parse("").InvokeAsync()).Should().Be(123); + } + [Fact] + public void Anonymous_RootCommand_int_returning_Action_can_set_custom_result_code_via_Invoke() + { + var rootCommand = new RootCommand(); + + rootCommand.SetAction(_ => 123); + + rootCommand.Parse("").Invoke().Should().Be(123); + } + + [Fact] + public async Task Anonymous_RootCommand_int_returning_Action_can_set_custom_result_code_via_InvokeAsync() + { + var rootCommand = new RootCommand(); + + rootCommand.SetAction(_ => 123); + + (await rootCommand.Parse("").InvokeAsync()).Should().Be(123); + } + internal sealed class CustomExitCodeAction : CliAction { public override int Invoke(InvocationContext context) @@ -157,7 +204,7 @@ public async Task Command_InvokeAsync_with_cancelation_token_invokes_command_han { using CancellationTokenSource cts = new(); var command = new Command("test"); - command.SetAction((InvocationContext context, CancellationToken cancellationToken) => + command.SetAction((_, cancellationToken) => { cancellationToken.Should().Be(cts.Token); return Task.CompletedTask; diff --git a/src/System.CommandLine/Command.cs b/src/System.CommandLine/Command.cs index 6c53c4f2fd..c26295edba 100644 --- a/src/System.CommandLine/Command.cs +++ b/src/System.CommandLine/Command.cs @@ -104,12 +104,32 @@ public IEnumerable Children /// Sets a synchronous action. /// public void SetAction(Action action) + => Action = new AnonymousCliAction(context => + { + action(context); + return 0; + }); + + /// + /// Sets a synchronous action. + /// + public void SetAction(Func action) => Action = new AnonymousCliAction(action); /// /// Sets an asynchronous action. /// public void SetAction(Func action) + => Action = new AnonymousCliAction(async (context, cancellationToken) => + { + await action(context, cancellationToken); + return 0; + }); + + /// + /// Sets an asynchronous action. + /// + public void SetAction(Func> action) => Action = new AnonymousCliAction(action); /// diff --git a/src/System.CommandLine/Invocation/AnonymousCliAction.cs b/src/System.CommandLine/Invocation/AnonymousCliAction.cs index ee84e8fdaa..1b63bbc91a 100644 --- a/src/System.CommandLine/Invocation/AnonymousCliAction.cs +++ b/src/System.CommandLine/Invocation/AnonymousCliAction.cs @@ -8,44 +8,40 @@ namespace System.CommandLine.Invocation { internal sealed class AnonymousCliAction : CliAction { - private readonly Func? _asyncAction; - private readonly Action? _syncAction; + private readonly Func>? _asyncAction; + private readonly Func? _syncAction; - internal AnonymousCliAction(Action action) + internal AnonymousCliAction(Func action) => _syncAction = action ?? throw new ArgumentNullException(nameof(action)); - internal AnonymousCliAction(Func action) + internal AnonymousCliAction(Func> action) => _asyncAction = action ?? throw new ArgumentNullException(nameof(action)); public override int Invoke(InvocationContext context) { if (_syncAction is not null) { - _syncAction(context); + return _syncAction(context); } else { - SyncUsingAsync(context); // kept in a separate method to avoid JITting + return SyncUsingAsync(context); // kept in a separate method to avoid JITting } - return 0; - - void SyncUsingAsync(InvocationContext context) + int SyncUsingAsync(InvocationContext context) => _asyncAction!(context, CancellationToken.None).GetAwaiter().GetResult(); } - public async override Task InvokeAsync(InvocationContext context, CancellationToken cancellationToken) + public override async Task InvokeAsync(InvocationContext context, CancellationToken cancellationToken) { if (_asyncAction is not null) { - await _asyncAction(context, cancellationToken); + return await _asyncAction(context, cancellationToken); } else { - _syncAction!(context); + return _syncAction!(context); } - - return 0; } } } \ No newline at end of file From 81d7740f8db77611acbc98e71d5505ab60fbaf02 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Tue, 28 Mar 2023 08:32:35 -0700 Subject: [PATCH 2/2] respond to PR comments, improve XML docs --- src/System.CommandLine/Command.cs | 46 +++++++++++++++---- .../Invocation/AnonymousCliAction.cs | 4 +- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/System.CommandLine/Command.cs b/src/System.CommandLine/Command.cs index c26295edba..36e48edcbb 100644 --- a/src/System.CommandLine/Command.cs +++ b/src/System.CommandLine/Command.cs @@ -101,36 +101,66 @@ public IEnumerable Children public CliAction? Action { get; set; } /// - /// Sets a synchronous action. + /// Sets a synchronous action to be run when the command is invoked. /// public void SetAction(Action action) - => Action = new AnonymousCliAction(context => + { + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + + Action = new AnonymousCliAction(context => { action(context); return 0; }); + } /// - /// Sets a synchronous action. + /// Sets a synchronous action to be run when the command is invoked. /// + /// The value returned from the delegate can be used to set the process exit code. public void SetAction(Func action) - => Action = new AnonymousCliAction(action); + { + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + + Action = new AnonymousCliAction(action); + } /// - /// Sets an asynchronous action. + /// Sets an asynchronous action to be run when the command is invoked. /// public void SetAction(Func action) - => Action = new AnonymousCliAction(async (context, cancellationToken) => + { + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + + Action = new AnonymousCliAction(async (context, cancellationToken) => { await action(context, cancellationToken); return 0; }); + } /// - /// Sets an asynchronous action. + /// Sets an asynchronous action when the command is invoked. /// + /// The value returned from the delegate can be used to set the process exit code. public void SetAction(Func> action) - => Action = new AnonymousCliAction(action); + { + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + + Action = new AnonymousCliAction(action); + } /// /// Adds a to the command. diff --git a/src/System.CommandLine/Invocation/AnonymousCliAction.cs b/src/System.CommandLine/Invocation/AnonymousCliAction.cs index 1b63bbc91a..2c940ecc5d 100644 --- a/src/System.CommandLine/Invocation/AnonymousCliAction.cs +++ b/src/System.CommandLine/Invocation/AnonymousCliAction.cs @@ -12,10 +12,10 @@ internal sealed class AnonymousCliAction : CliAction private readonly Func? _syncAction; internal AnonymousCliAction(Func action) - => _syncAction = action ?? throw new ArgumentNullException(nameof(action)); + => _syncAction = action; internal AnonymousCliAction(Func> action) - => _asyncAction = action ?? throw new ArgumentNullException(nameof(action)); + => _asyncAction = action; public override int Invoke(InvocationContext context) {