This commit is contained in:
Tyrrrz
2022-01-10 16:45:09 +02:00
parent 7cbbb220b4
commit ff38f4916a
4 changed files with 93 additions and 68 deletions

View File

@@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsTestProject>true</IsTestProject>
<CollectCoverage>true</CollectCoverage>
<CoverletOutputFormat>opencover</CoverletOutputFormat>
</PropertyGroup>

View File

@@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<IsTestProject>true</IsTestProject>
<CollectCoverage>true</CollectCoverage>
<CoverletOutputFormat>opencover</CoverletOutputFormat>
</PropertyGroup>

View File

@@ -496,6 +496,83 @@ public class Command : ICommand
);
}
[Fact]
public async Task Option_binding_supports_multiple_inheritance_through_default_interface_members()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
@"
public static class SharedContext
{
public static int Foo { get; set; }
public static bool Bar { get; set; }
}
public interface IHasFoo : ICommand
{
[CommandOption(""foo"")]
public int Foo
{
get => SharedContext.Foo;
set => SharedContext.Foo = value;
}
}
public interface IHasBar : ICommand
{
[CommandOption(""bar"")]
public bool Bar
{
get => SharedContext.Bar;
set => SharedContext.Bar = value;
}
}
public interface IHasBaz : ICommand
{
public string Baz { get; set; }
}
[Command]
public class Command : IHasFoo, IHasBar, IHasBaz
{
[CommandOption(""baz"")]
public string Baz { get; set; }
public ValueTask ExecuteAsync(IConsole console)
{
console.Output.WriteLine(""Foo = "" + SharedContext.Foo);
console.Output.WriteLine(""Bar = "" + SharedContext.Bar);
console.Output.WriteLine(""Baz = "" + Baz);
return default;
}
}
");
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] { "--foo", "42", "--bar", "--baz", "xyz" }
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
stdOut.Should().ConsistOfLines(
"Foo = 42",
"Bar = True",
"Baz = xyz"
);
}
[Fact]
public async Task Option_binding_does_not_consider_a_negative_number_as_an_option_name_or_short_name()
{
@@ -704,64 +781,4 @@ public class Command : ICommand
exitCode.Should().NotBe(0);
stdErr.Should().Contain("expects a single argument, but provided with multiple");
}
[Fact]
public async Task Option_bound_using_interfaces_for_multiple_inheritance_should_work()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
@"
public static class FooBarLogger
{
public static bool Foo { get; set; } = false;
public static bool Bar { get; set; } = false;
}
public interface IOptionBar : ICommand
{
[CommandOption(""bar"")]
public bool Bar
{
get => FooBarLogger.Bar;
set => FooBarLogger.Bar = value;
}
}
public interface IOptionFoo : ICommand
{
[CommandOption(""foo"")]
public bool Foo
{
get => FooBarLogger.Foo;
set => FooBarLogger.Foo = value;
}
}
[Command]
public class Command : IOptionFoo, IOptionBar
{
public ValueTask ExecuteAsync(IConsole console)
{
console.Output.WriteLine($""Foo: { FooBarLogger.Foo }"");
console.Output.WriteLine($""Bar: { FooBarLogger.Bar }"");
return default;
}
}
");
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] {"--foo" , "--bar"});
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
stdOut.Trim().Should().Be("Foo: True" + Environment.NewLine + "Bar: True");
}
}

View File

@@ -87,10 +87,20 @@ internal partial class CommandSchema
? new[] {OptionSchema.HelpOption, OptionSchema.VersionOption}
: new[] {OptionSchema.HelpOption};
// Include interface members for multiple inheritance
// If interface inherits from ICommand, it will be included
var interfaces = type.GetInterfaces().Where(i => i != typeof(ICommand) && typeof(ICommand).IsAssignableFrom(i) );
var properties = type.GetProperties().Concat(interfaces.SelectMany(i => i.GetProperties())).ToArray();
var properties = type
// Get properties directly on command type
.GetProperties()
// Get non-abstract properties on interfaces (to support default interfaces members)
.Union(type
.GetInterfaces()
// Only interfaces implementing ICommand for explicitness
.Where(i => typeof(ICommand).IsAssignableFrom(i) && i != typeof(ICommand))
.SelectMany(i => i
.GetProperties()
.Where(p => !p.GetMethod.IsAbstract && !p.SetMethod.IsAbstract)
)
)
.ToArray();
var parameterSchemas = properties
.Select(ParameterSchema.TryResolve)