Skip to content

enable CliAction to be non-exclusive #2147

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ System.CommandLine
public static CliArgument<System.IO.FileSystemInfo> AcceptExistingOnly(this CliArgument<System.IO.FileSystemInfo> argument)
public static CliArgument<T> AcceptExistingOnly<T>(this CliArgument<T> argument)
public abstract class CliAction
public System.Boolean Exclusive { get; }
public System.Int32 Invoke(ParseResult parseResult)
public System.Threading.Tasks.Task<System.Int32> InvokeAsync(ParseResult parseResult, System.Threading.CancellationToken cancellationToken = null)
protected System.Void set_Exclusive(System.Boolean value)
public abstract class CliArgument : CliSymbol
public ArgumentArity Arity { get; set; }
public System.Collections.Generic.List<System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem>>> CompletionSources { get; }
Expand Down Expand Up @@ -76,6 +78,8 @@ System.CommandLine
public ParseResult Parse(System.Collections.Generic.IReadOnlyList<System.String> args)
public ParseResult Parse(System.String commandLine)
public System.Void ThrowIfInvalid()
public class CliConfigurationException : System.Exception, System.Runtime.Serialization.ISerializable
.ctor(System.String message)
public class CliDirective : CliSymbol
.ctor(System.String name)
public CliAction Action { get; set; }
Expand Down Expand Up @@ -111,8 +115,6 @@ System.CommandLine
public System.Collections.Generic.IEnumerable<CliSymbol> Parents { get; }
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions(System.CommandLine.Completions.CompletionContext context)
public System.String ToString()
public class CommandLineConfigurationException : System.Exception, System.Runtime.Serialization.ISerializable
.ctor(System.String message)
public static class CompletionSourceExtensions
public static System.Void Add(this System.Collections.Generic.List<System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem>>> completionSources, System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.String>> completionsDelegate)
public static System.Void Add(this System.Collections.Generic.List<System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem>>> completionSources, System.String[] completions)
Expand Down
20 changes: 10 additions & 10 deletions src/System.CommandLine.Tests/CommandLineConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace System.CommandLine.Tests;

public class CommandLineConfigurationTests
public class CliConfigurationTests
{
[Fact]
public void ThrowIfInvalid_throws_if_there_are_duplicate_sibling_option_aliases_on_the_root_command()
Expand All @@ -26,7 +26,7 @@ public void ThrowIfInvalid_throws_if_there_are_duplicate_sibling_option_aliases_
var validate = () => config.ThrowIfInvalid();

validate.Should()
.Throw<CommandLineConfigurationException>()
.Throw<CliConfigurationException>()
.Which
.Message
.Should()
Expand Down Expand Up @@ -54,7 +54,7 @@ public void ThrowIfInvalid_throws_if_there_are_duplicate_sibling_option_aliases_
var validate = () => config.ThrowIfInvalid();

validate.Should()
.Throw<CommandLineConfigurationException>()
.Throw<CliConfigurationException>()
.Which
.Message
.Should()
Expand All @@ -79,7 +79,7 @@ public void ThrowIfInvalid_throws_if_there_are_duplicate_sibling_subcommand_alia
var validate = () => config.ThrowIfInvalid();

validate.Should()
.Throw<CommandLineConfigurationException>()
.Throw<CliConfigurationException>()
.Which
.Message
.Should()
Expand All @@ -103,7 +103,7 @@ public void ThrowIfInvalid_throws_if_there_are_duplicate_sibling_subcommand_alia
var validate = () => config.ThrowIfInvalid();

validate.Should()
.Throw<CommandLineConfigurationException>()
.Throw<CliConfigurationException>()
.Which
.Message
.Should()
Expand All @@ -128,7 +128,7 @@ public void ThrowIfInvalid_throws_if_sibling_command_and_option_aliases_collide_
var validate = () => config.ThrowIfInvalid();

validate.Should()
.Throw<CommandLineConfigurationException>()
.Throw<CliConfigurationException>()
.Which
.Message
.Should()
Expand Down Expand Up @@ -156,7 +156,7 @@ public void ThrowIfInvalid_throws_if_sibling_command_and_option_aliases_collide_
var validate = () => config.ThrowIfInvalid();

validate.Should()
.Throw<CommandLineConfigurationException>()
.Throw<CliConfigurationException>()
.Which
.Message
.Should()
Expand All @@ -179,7 +179,7 @@ public void ThrowIfInvalid_throws_if_there_are_duplicate_sibling_global_option_a
var validate = () => config.ThrowIfInvalid();

validate.Should()
.Throw<CommandLineConfigurationException>()
.Throw<CliConfigurationException>()
.Which
.Message
.Should()
Expand Down Expand Up @@ -235,7 +235,7 @@ public void ThrowIfInvalid_throws_if_a_command_is_its_own_parent()
var validate = () => config.ThrowIfInvalid();

validate.Should()
.Throw<CommandLineConfigurationException>()
.Throw<CliConfigurationException>()
.Which
.Message
.Should()
Expand All @@ -254,7 +254,7 @@ public void ThrowIfInvalid_throws_if_a_parentage_cycle_is_detected()
var validate = () => config.ThrowIfInvalid();

validate.Should()
.Throw<CommandLineConfigurationException>()
.Throw<CliConfigurationException>()
.Which
.Message
.Should()
Expand Down
121 changes: 115 additions & 6 deletions src/System.CommandLine.Tests/DirectiveTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Execution;
using Xunit;

namespace System.CommandLine.Tests
Expand Down Expand Up @@ -31,6 +33,16 @@ public void Raw_tokens_still_hold_directives()
result.Tokens.Should().Contain(t => t.Value == "[parse]");
}

[Fact]
public void Directives_must_precede_other_symbols()
{
CliDirective directive = new("parse");

ParseResult result = Parse(new CliOption<bool>("-y"), directive, "-y [parse]");

result.FindResultFor(directive).Should().BeNull();
}

[Fact]
public void Multiple_directives_are_allowed()
{
Expand All @@ -47,14 +59,111 @@ public void Multiple_directives_are_allowed()
result.FindResultFor(suggestDirective).Should().NotBeNull();
}

[Fact]
public void Directives_must_be_the_first_argument()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task Multiple_instances_of_the_same_directive_can_be_invoked(bool invokeAsync)
{
CliDirective directive = new("parse");
var commandActionWasCalled = false;
var directiveCallCount = 0;

var testDirective = new TestDirective("test")
{
Action = new NonexclusiveTestAction(_ => directiveCallCount++)
};

var config = new CliConfiguration(new CliRootCommand
{
Action = new NonexclusiveTestAction(_ => commandActionWasCalled = true)
})
{
Directives = { testDirective }
};

if (invokeAsync)
{
await config.InvokeAsync("[test:1] [test:2]");
}
else
{
config.Invoke("[test:1] [test:2]");
}

using var _ = new AssertionScope();

commandActionWasCalled.Should().BeTrue();
directiveCallCount.Should().Be(2);
}

ParseResult result = Parse(new CliOption<bool>("-y"), directive, "-y [parse]");
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task Multiple_different_directives_can_be_invoked(bool invokeAsync)
{
bool commandActionWasCalled = false;
bool directiveOneActionWasCalled = false;
bool directiveTwoActionWasCalled = false;

var directiveOne = new TestDirective("one")
{
Action = new NonexclusiveTestAction(_ => directiveOneActionWasCalled = true)
};
var directiveTwo = new TestDirective("two")
{
Action = new NonexclusiveTestAction(_ => directiveTwoActionWasCalled = true)
};
var config = new CliConfiguration(new CliRootCommand
{
Action = new NonexclusiveTestAction(_ => commandActionWasCalled = true)
})
{
Directives = { directiveOne, directiveTwo }
};

if (invokeAsync)
{
await config.InvokeAsync("[one] [two]");
}
else
{
config.Invoke("[one] [two]");
}

using var _ = new AssertionScope();

commandActionWasCalled.Should().BeTrue();
directiveOneActionWasCalled.Should().BeTrue();
directiveTwoActionWasCalled.Should().BeTrue();
}

result.FindResultFor(directive).Should().BeNull();
public class TestDirective : CliDirective
{
public TestDirective(string name) : base(name)
{
}
}

private class NonexclusiveTestAction : CliAction
{
private readonly Action<ParseResult> _invoke;

public NonexclusiveTestAction(Action<ParseResult> invoke)
{
_invoke = invoke;
Exclusive = false;
}

public override int Invoke(ParseResult parseResult)
{
_invoke(parseResult);
return 0;
}

public override Task<int> InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken = default)
{
;
return Task.FromResult(Invoke(parseResult));
}
}

[Theory]
Expand Down
Loading