From 22fdb6c53116b61007d687b957418a1cec383f41 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Sat, 9 Feb 2019 07:36:46 -0800 Subject: [PATCH] R: move some tests into more specific namespaces --- .../BindingCommandHandlerTests.cs | 6 +- .../Binding/TypeBinderTests.cs | 377 ++++++++++++++++++ .../{ => Binding}/TypeConversionTests.cs | 4 +- .../Binding/TypeWithInvokeAndCtor.cs | 2 - .../CancelOnProcessTerminationTests.cs | 2 +- .../{ => Invocation}/CommandHandlerTests.cs | 4 +- .../InvocationExtensionsTests.cs | 2 +- .../InvocationPipelineTests.cs | 2 +- .../MethodBinderTests.cs | 2 - .../TypeBinderTests.cs | 371 ----------------- src/System.CommandLine/Binding/BindingSide.cs | 2 +- src/System.CommandLine/Binding/IBinder.cs | 10 + .../Binding/ReflectionBinder.cs | 45 +-- src/System.CommandLine/ParserExtensions.cs | 34 +- 14 files changed, 448 insertions(+), 415 deletions(-) rename src/System.CommandLine.Tests/{ => Binding}/BindingCommandHandlerTests.cs (98%) create mode 100644 src/System.CommandLine.Tests/Binding/TypeBinderTests.cs rename src/System.CommandLine.Tests/{ => Binding}/TypeConversionTests.cs (99%) rename src/System.CommandLine.Tests/{ => Invocation}/CancelOnProcessTerminationTests.cs (98%) rename src/System.CommandLine.Tests/{ => Invocation}/CommandHandlerTests.cs (99%) rename src/System.CommandLine.Tests/{ => Invocation}/InvocationExtensionsTests.cs (98%) rename src/System.CommandLine.Tests/{ => Invocation}/InvocationPipelineTests.cs (99%) delete mode 100644 src/System.CommandLine.Tests/TypeBinderTests.cs diff --git a/src/System.CommandLine.Tests/BindingCommandHandlerTests.cs b/src/System.CommandLine.Tests/Binding/BindingCommandHandlerTests.cs similarity index 98% rename from src/System.CommandLine.Tests/BindingCommandHandlerTests.cs rename to src/System.CommandLine.Tests/Binding/BindingCommandHandlerTests.cs index da98989244..3c315732c4 100644 --- a/src/System.CommandLine.Tests/BindingCommandHandlerTests.cs +++ b/src/System.CommandLine.Tests/Binding/BindingCommandHandlerTests.cs @@ -2,14 +2,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.CommandLine.Invocation; -using System.CommandLine.Binding; -using System.CommandLine.Tests.Binding; -using FluentAssertions; using System.Linq; using System.Threading.Tasks; +using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests.Binding { public class BindingCommandHandlerTests { diff --git a/src/System.CommandLine.Tests/Binding/TypeBinderTests.cs b/src/System.CommandLine.Tests/Binding/TypeBinderTests.cs new file mode 100644 index 0000000000..15629b6ab3 --- /dev/null +++ b/src/System.CommandLine.Tests/Binding/TypeBinderTests.cs @@ -0,0 +1,377 @@ +// 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.CommandLine.Invocation; +using FluentAssertions; +using System.Linq; +using System.Threading; +using Xunit; + +namespace System.CommandLine.Tests.Binding +{ + public class TypeBinderTests + { + public class BuildOptions + { + [Fact] + public void Single_character_constructor_arguments_generate_aliases_with_a_single_dash_prefix() + { + var binder = new TypeBinder(typeof(ClassWithSingleLetterCtorParameter)); + + var options = binder.BuildOptions().ToArray(); + + options.Should().Contain(o => o.HasRawAlias("-x")); + options.Should().Contain(o => o.HasRawAlias("-y")); + } + + [Fact] + public void Multi_character_constructor_arguments_generate_aliases_that_accept_a_double_dash_prefix() + { + var binder = new TypeBinder(typeof(ClassWithMultiLetterCtorParameters)); + + var options = binder.BuildOptions().ToArray(); + + options.Should().Contain(o => o.HasRawAlias("--int-option")); + options.Should().Contain(o => o.HasRawAlias("--string-option")); + options.Should().Contain(o => o.HasRawAlias("--bool-option")); + } + + [Fact] + public void Single_character_setters_generate_aliases_that_accept_a_single_dash_prefix() + { + var binder = new TypeBinder(typeof(ClassWithSingleLetterProperty)); + + var options = binder.BuildOptions().ToArray(); + + options.Should().Contain(o => o.HasRawAlias("-x")); + options.Should().Contain(o => o.HasRawAlias("-y")); + } + + [Fact] + public void Multi_character_setters_generate_aliases_that_accept_a_single_dash_prefix() + { + var binder = new TypeBinder(typeof(ClassWithMultiLetterSetters)); + + var options = binder.BuildOptions().ToArray(); + + options.Should().Contain(o => o.HasRawAlias("--int-option")); + options.Should().Contain(o => o.HasRawAlias("--string-option")); + options.Should().Contain(o => o.HasRawAlias("--bool-option")); + } + + [Fact] + public void When_both_constructor_parameters_and_setters_are_present_then_BuildOptions_creates_options_for_all_of_them() + { + var binder = new TypeBinder(typeof(ClassWithSettersAndCtorParametersWithDifferentNames)); + + var options = binder.BuildOptions(); + + options.Should().Contain(o => o.HasRawAlias("--int-option")); + options.Should().Contain(o => o.HasRawAlias("--string-option")); + options.Should().Contain(o => o.HasRawAlias("--bool-option")); + + options.Should().Contain(o => o.HasRawAlias("-i")); + options.Should().Contain(o => o.HasRawAlias("-s")); + options.Should().Contain(o => o.HasRawAlias("-b")); + } + + [Fact] + public void Default_option_values_are_based_on_constructor_parameter_defaults() + { + var binder = new TypeBinder(typeof(ClassWithMultiLetterCtorParameters)); + + var options = binder.BuildOptions().ToArray(); + + options.Single(o => o.HasRawAlias("--int-option")) + .Argument + .GetDefaultValue() + .Should() + .Be(123); + + options.Single(o => o.HasRawAlias("--string-option")) + .Argument + .GetDefaultValue() + .Should() + .Be("the default"); + } + + [Theory] + [InlineData(typeof(IConsole))] + [InlineData(typeof(InvocationContext))] + [InlineData(typeof(ParseResult))] + [InlineData(typeof(CancellationToken))] + public void Options_are_not_built_for_infrastructure_types_exposed_by_properties(Type type) + { + var binder = new TypeBinder(typeof(ClassWithSetter<>).MakeGenericType(type)); + + var options = binder.BuildOptions(); + + options.Should() + .NotContain(o => o.Argument.ArgumentType == type); + } + } + + public class CreateInstance + { + [Fact] + public void Option_arguments_are_bound_by_name_to_constructor_parameters() + { + var argument = new Argument("the default"); + + var option = new Option("--string-option", + argument: argument); + + var command = new Command("the-command"); + command.AddOption(option); + var binder = new TypeBinder(typeof(ClassWithMultiLetterCtorParameters)); + + var parser = new Parser(command); + var invocationContext = new InvocationContext( + parser.Parse("--string-option not-the-default"), + parser); + + var instance = (ClassWithMultiLetterCtorParameters)binder.CreateInstance(invocationContext); + + instance.StringOption.Should().Be("not-the-default"); + } + + [Theory] + [InlineData(typeof(string), "hello", "hello")] + [InlineData(typeof(int), "123", 123)] + public void Command_arguments_are_bound_by_name_to_constructor_parameters( + Type type, + string commandLine, + object expectedValue) + { + var targetType = typeof(ClassWithCtorParameter<>).MakeGenericType(type); + var binder = new TypeBinder(targetType); + + var command = new Command("the-command") + { + Argument = new Argument + { + Name = "value", + ArgumentType = type + } + }; + var parser = new Parser(command); + + var invocationContext = new InvocationContext(parser.Parse(commandLine), parser); + + var instance = binder.CreateInstance(invocationContext); + + object valueReceivedValue = ((dynamic)instance).Value; + + valueReceivedValue.Should().Be(expectedValue); + } + + [Fact] + public void Explicitly_configured_default_values_can_be_bound_to_constructor_parameters() + { + var argument = new Argument("the default"); + + var option = new Option("--string-option", + argument: argument); + + var command = new Command("the-command"); + command.AddOption(option); + var binder = new TypeBinder(typeof(ClassWithMultiLetterCtorParameters)); + + var parser = new Parser(command); + var invocationContext = new InvocationContext( + parser.Parse(""), + parser); + + var instance = (ClassWithMultiLetterCtorParameters)binder.CreateInstance(invocationContext); + + instance.StringOption.Should().Be("the default"); + } + + [Fact] + public void Option_arguments_are_bound_by_name_to_property_setters() + { + var argument = new Argument(); + + var option = new Option("--bool-option", + argument: argument); + + var command = new Command("the-command"); + command.AddOption(option); + var binder = new TypeBinder(typeof(ClassWithMultiLetterSetters)); + + var parser = new Parser(command); + var invocationContext = new InvocationContext( + parser.Parse("--bool-option"), + parser); + + var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(invocationContext); + + instance.BoolOption.Should().BeTrue(); + } + + [Theory] + [InlineData(typeof(string), "hello", "hello")] + [InlineData(typeof(int), "123", 123)] + public void Command_arguments_are_bound_by_name_to_property_setters( + Type type, + string commandLine, + object expectedValue) + { + var targetType = typeof(ClassWithSetter<>).MakeGenericType(type); + var binder = new TypeBinder(targetType); + + var command = new Command("the-command") + { + Argument = new Argument + { + Name = "value", + ArgumentType = type + } + }; + var parser = new Parser(command); + + var invocationContext = new InvocationContext(parser.Parse(commandLine), parser); + + var instance = binder.CreateInstance(invocationContext); + + object valueReceivedValue = ((dynamic)instance).Value; + + valueReceivedValue.Should().Be(expectedValue); + } + + [Fact] + public void Explicitly_configured_default_values_can_be_bound_to_property_setters() + { + var argument = new Argument("the default"); + + var option = new Option("--string-option", + argument: argument); + + var command = new Command("the-command"); + command.AddOption(option); + var binder = new TypeBinder(typeof(ClassWithMultiLetterSetters)); + + var parser = new Parser(command); + var invocationContext = new InvocationContext( + parser.Parse(""), + parser); + + var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(invocationContext); + + instance.StringOption.Should().Be("the default"); + } + + [Fact] + public void Property_setters_with_no_default_value_and_no_matching_option_are_not_called() + { + var command = new Command("the-command"); + + var binder = new TypeBinder(typeof(ClassWithSettersAndCtorParametersWithDifferentNames)); + + foreach (var option in binder.BuildOptions()) + { + command.Add(option); + } + + var parser = new Parser(command); + var invocationContext = new InvocationContext( + parser.Parse(""), + parser); + + var instance = (ClassWithSettersAndCtorParametersWithDifferentNames)binder.CreateInstance(invocationContext); + + instance.StringOption.Should().Be("the default"); + } + } + + public class ClassWithSingleLetterCtorParameter + { + public ClassWithSingleLetterCtorParameter(int x, string y) + { + X = x; + Y = y; + } + + public int X { get; } + + public string Y { get; } + } + + public class ClassWithSingleLetterProperty + { + public int X { get; set; } + + public int Y { get; set; } + } + + public class ClassWithMultiLetterCtorParameters + { + public ClassWithMultiLetterCtorParameters( + int intOption = 123, + string stringOption = "the default", + bool boolOption = false) + { + IntOption = intOption; + StringOption = stringOption; + BoolOption = boolOption; + } + + public int IntOption { get; } + public string StringOption { get; } + public bool BoolOption { get; } + } + + public class ClassWithMultiLetterSetters + { + public int IntOption { get; set; } + public string StringOption { get; set; } + public bool BoolOption { get; set; } + } + + public class ClassWithSettersAndCtorParametersWithDifferentNames + { + public ClassWithSettersAndCtorParametersWithDifferentNames( + int i = 123, + string s = "the default", + bool b = false) + { + IntOption = i; + StringOption = s; + BoolOption = b; + } + + public int IntOption { get; set; } + public string StringOption { get; set; } + public bool BoolOption { get; set; } + } + + public class ClassWithSettersAndCtorParametersWithMatchingNames + { + public ClassWithSettersAndCtorParametersWithMatchingNames( + int intOption = 123, + string stringOption = "the default", + bool boolOption = false) + { + IntOption = intOption; + StringOption = stringOption; + BoolOption = boolOption; + } + + public int IntOption { get; set; } + public string StringOption { get; set; } + public bool BoolOption { get; set; } + } + + public class ClassWithCtorParameter + { + public ClassWithCtorParameter(T value) => Value = value; + + public T Value { get; } + } + + public class ClassWithSetter + { + public T Value { get; set; } + } + } +} diff --git a/src/System.CommandLine.Tests/TypeConversionTests.cs b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs similarity index 99% rename from src/System.CommandLine.Tests/TypeConversionTests.cs rename to src/System.CommandLine.Tests/Binding/TypeConversionTests.cs index 3160a371e0..96fbfb55db 100644 --- a/src/System.CommandLine.Tests/TypeConversionTests.cs +++ b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.IO; -using FluentAssertions; using System.Linq; +using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests.Binding { public class TypeConversionTests { diff --git a/src/System.CommandLine.Tests/Binding/TypeWithInvokeAndCtor.cs b/src/System.CommandLine.Tests/Binding/TypeWithInvokeAndCtor.cs index 13f04c5341..3c2da728ae 100644 --- a/src/System.CommandLine.Tests/Binding/TypeWithInvokeAndCtor.cs +++ b/src/System.CommandLine.Tests/Binding/TypeWithInvokeAndCtor.cs @@ -2,8 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; namespace System.CommandLine.Tests.Binding diff --git a/src/System.CommandLine.Tests/CancelOnProcessTerminationTests.cs b/src/System.CommandLine.Tests/Invocation/CancelOnProcessTerminationTests.cs similarity index 98% rename from src/System.CommandLine.Tests/CancelOnProcessTerminationTests.cs rename to src/System.CommandLine.Tests/Invocation/CancelOnProcessTerminationTests.cs index 2818f5f41b..8e5cf08d2f 100644 --- a/src/System.CommandLine.Tests/CancelOnProcessTerminationTests.cs +++ b/src/System.CommandLine.Tests/Invocation/CancelOnProcessTerminationTests.cs @@ -10,7 +10,7 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests.Invocation { public class CancelOnProcessTerminationTests { diff --git a/src/System.CommandLine.Tests/CommandHandlerTests.cs b/src/System.CommandLine.Tests/Invocation/CommandHandlerTests.cs similarity index 99% rename from src/System.CommandLine.Tests/CommandHandlerTests.cs rename to src/System.CommandLine.Tests/Invocation/CommandHandlerTests.cs index 0e5584bb6a..4ac67705e6 100644 --- a/src/System.CommandLine.Tests/CommandHandlerTests.cs +++ b/src/System.CommandLine.Tests/Invocation/CommandHandlerTests.cs @@ -2,11 +2,11 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.CommandLine.Invocation; -using FluentAssertions; using System.Threading.Tasks; +using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests.Invocation { public class CommandHandlerTests { diff --git a/src/System.CommandLine.Tests/InvocationExtensionsTests.cs b/src/System.CommandLine.Tests/Invocation/InvocationExtensionsTests.cs similarity index 98% rename from src/System.CommandLine.Tests/InvocationExtensionsTests.cs rename to src/System.CommandLine.Tests/Invocation/InvocationExtensionsTests.cs index 0763f9e428..145ea826f2 100644 --- a/src/System.CommandLine.Tests/InvocationExtensionsTests.cs +++ b/src/System.CommandLine.Tests/Invocation/InvocationExtensionsTests.cs @@ -6,7 +6,7 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests.Invocation { public class InvocationExtensionsTests { diff --git a/src/System.CommandLine.Tests/InvocationPipelineTests.cs b/src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs similarity index 99% rename from src/System.CommandLine.Tests/InvocationPipelineTests.cs rename to src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs index 9eba33e37f..d1e40ae265 100644 --- a/src/System.CommandLine.Tests/InvocationPipelineTests.cs +++ b/src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs @@ -9,7 +9,7 @@ using FluentAssertions; using Xunit; -namespace System.CommandLine.Tests +namespace System.CommandLine.Tests.Invocation { public class InvocationPipelineTests { diff --git a/src/System.CommandLine.Tests/MethodBinderTests.cs b/src/System.CommandLine.Tests/MethodBinderTests.cs index e3a9a188bc..9aec50280c 100644 --- a/src/System.CommandLine.Tests/MethodBinderTests.cs +++ b/src/System.CommandLine.Tests/MethodBinderTests.cs @@ -1,9 +1,7 @@ // 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.CommandLine.Builder; using System.CommandLine.Invocation; -using System.IO; using System.Threading; using System.Threading.Tasks; using FluentAssertions; diff --git a/src/System.CommandLine.Tests/TypeBinderTests.cs b/src/System.CommandLine.Tests/TypeBinderTests.cs deleted file mode 100644 index b94408b48c..0000000000 --- a/src/System.CommandLine.Tests/TypeBinderTests.cs +++ /dev/null @@ -1,371 +0,0 @@ -// 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.CommandLine.Invocation; -using FluentAssertions; -using System.Linq; -using System.Threading; -using Xunit; - -namespace System.CommandLine.Tests -{ - public class TypeBinderTests - { - [Fact] - public void Single_character_constructor_arguments_generate_aliases_with_a_single_dash_prefix() - { - var binder = new TypeBinder(typeof(ClassWithSingleLetterCtorParameter)); - - var options = binder.BuildOptions().ToArray(); - - options.Should().Contain(o => o.HasRawAlias("-x")); - options.Should().Contain(o => o.HasRawAlias("-y")); - } - - [Fact] - public void Multi_character_constructor_arguments_generate_aliases_that_accept_a_double_dash_prefix() - { - var binder = new TypeBinder(typeof(ClassWithMultiLetterCtorParameters)); - - var options = binder.BuildOptions().ToArray(); - - options.Should().Contain(o => o.HasRawAlias("--int-option")); - options.Should().Contain(o => o.HasRawAlias("--string-option")); - options.Should().Contain(o => o.HasRawAlias("--bool-option")); - } - - [Fact] - public void Single_character_setters_generate_aliases_that_accept_a_single_dash_prefix() - { - var binder = new TypeBinder(typeof(ClassWithSingleLetterProperty)); - - var options = binder.BuildOptions().ToArray(); - - options.Should().Contain(o => o.HasRawAlias("-x")); - options.Should().Contain(o => o.HasRawAlias("-y")); - } - - [Fact] - public void Multi_character_setters_generate_aliases_that_accept_a_single_dash_prefix() - { - var binder = new TypeBinder(typeof(ClassWithMultiLetterSetters)); - - var options = binder.BuildOptions().ToArray(); - - options.Should().Contain(o => o.HasRawAlias("--int-option")); - options.Should().Contain(o => o.HasRawAlias("--string-option")); - options.Should().Contain(o => o.HasRawAlias("--bool-option")); - } - - [Fact] - public void When_both_constructor_parameters_and_setters_are_present_then_BuildOptions_creates_options_for_all_of_them() - { - var binder = new TypeBinder(typeof(ClassWithSettersAndCtorParametersWithDifferentNames)); - - var options = binder.BuildOptions(); - - options.Should().Contain(o => o.HasRawAlias("--int-option")); - options.Should().Contain(o => o.HasRawAlias("--string-option")); - options.Should().Contain(o => o.HasRawAlias("--bool-option")); - - options.Should().Contain(o => o.HasRawAlias("-i")); - options.Should().Contain(o => o.HasRawAlias("-s")); - options.Should().Contain(o => o.HasRawAlias("-b")); - } - - [Fact] - public void Default_option_values_are_based_on_constructor_parameter_defaults() - { - var binder = new TypeBinder(typeof(ClassWithMultiLetterCtorParameters)); - - var options = binder.BuildOptions().ToArray(); - - options.Single(o => o.HasRawAlias("--int-option")) - .Argument - .GetDefaultValue() - .Should() - .Be(123); - - options.Single(o => o.HasRawAlias("--string-option")) - .Argument - .GetDefaultValue() - .Should() - .Be("the default"); - } - - [Fact] - public void Option_arguments_are_bound_by_name_to_constructor_parameters() - { - var argument = new Argument("the default"); - - var option = new Option("--string-option", - argument: argument); - - var command = new Command("the-command"); - command.AddOption(option); - var binder = new TypeBinder(typeof(ClassWithMultiLetterCtorParameters)); - - var parser = new Parser(command); - var invocationContext = new InvocationContext( - parser.Parse("--string-option not-the-default"), - parser); - - var instance = (ClassWithMultiLetterCtorParameters)binder.CreateInstance(invocationContext); - - instance.StringOption.Should().Be("not-the-default"); - } - - [Theory] - [InlineData(typeof(string), "hello", "hello")] - [InlineData(typeof(int), "123", 123)] - public void Command_arguments_are_bound_by_name_to_constructor_parameters( - Type type, - string commandLine, - object expectedValue) - { - var targetType = typeof(ClassWithCtorParameter<>).MakeGenericType(type); - var binder = new TypeBinder(targetType); - - var command = new Command("the-command") - { - Argument = new Argument - { - Name = "value", - ArgumentType = type - } - }; - var parser = new Parser(command); - - var invocationContext = new InvocationContext(parser.Parse(commandLine), parser); - - var instance = binder.CreateInstance(invocationContext); - - object valueReceivedValue = ((dynamic)instance).Value; - - valueReceivedValue.Should().Be(expectedValue); - } - - [Fact] - public void Explicitly_configured_default_values_can_be_bound_to_constructor_parameters() - { - var argument = new Argument("the default"); - - var option = new Option("--string-option", - argument: argument); - - var command = new Command("the-command"); - command.AddOption(option); - var binder = new TypeBinder(typeof(ClassWithMultiLetterCtorParameters)); - - var parser = new Parser(command); - var invocationContext = new InvocationContext( - parser.Parse(""), - parser); - - var instance = (ClassWithMultiLetterCtorParameters)binder.CreateInstance(invocationContext); - - instance.StringOption.Should().Be("the default"); - } - - [Fact] - public void Option_arguments_are_bound_by_name_to_property_setters() - { - var argument = new Argument(); - - var option = new Option("--bool-option", - argument: argument); - - var command = new Command("the-command"); - command.AddOption(option); - var binder = new TypeBinder(typeof(ClassWithMultiLetterSetters)); - - var parser = new Parser(command); - var invocationContext = new InvocationContext( - parser.Parse("--bool-option"), - parser); - - var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(invocationContext); - - instance.BoolOption.Should().BeTrue(); - } - - [Theory] - [InlineData(typeof(string), "hello", "hello")] - [InlineData(typeof(int), "123", 123)] - public void Command_arguments_are_bound_by_name_to_property_setters( - Type type, - string commandLine, - object expectedValue) - { - var targetType = typeof(ClassWithSetter<>).MakeGenericType(type); - var binder = new TypeBinder(targetType); - - var command = new Command("the-command") - { - Argument = new Argument - { - Name = "value", - ArgumentType = type - } - }; - var parser = new Parser(command); - - var invocationContext = new InvocationContext(parser.Parse(commandLine), parser); - - var instance = binder.CreateInstance(invocationContext); - - object valueReceivedValue = ((dynamic)instance).Value; - - valueReceivedValue.Should().Be(expectedValue); - } - - [Fact] - public void Explicitly_configured_default_values_can_be_bound_to_property_setters() - { - var argument = new Argument("the default"); - - var option = new Option("--string-option", - argument: argument); - - var command = new Command("the-command"); - command.AddOption(option); - var binder = new TypeBinder(typeof(ClassWithMultiLetterSetters)); - - var parser = new Parser(command); - var invocationContext = new InvocationContext( - parser.Parse(""), - parser); - - var instance = (ClassWithMultiLetterSetters)binder.CreateInstance(invocationContext); - - instance.StringOption.Should().Be("the default"); - } - - [Fact] - public void Property_setters_with_no_default_value_and_no_matching_option_are_not_called() - { - var command = new Command("the-command"); - - var binder = new TypeBinder(typeof(ClassWithSettersAndCtorParametersWithDifferentNames)); - - foreach (var option in binder.BuildOptions()) - { - command.Add(option); - } - - var parser = new Parser(command); - var invocationContext = new InvocationContext( - parser.Parse(""), - parser); - - var instance = (ClassWithSettersAndCtorParametersWithDifferentNames)binder.CreateInstance(invocationContext); - - instance.StringOption.Should().Be("the default"); - } - - [Theory] - [InlineData(typeof(IConsole))] - [InlineData(typeof(InvocationContext))] - [InlineData(typeof(ParseResult))] - [InlineData(typeof(CancellationToken))] - public void Options_are_not_built_for_infrastructure_types_exposed_by_properties(Type type) - { - var binder = new TypeBinder(typeof(ClassWithSetter<>).MakeGenericType(type)); - - var options = binder.BuildOptions(); - - options.Should() - .NotContain(o => o.Argument.ArgumentType == type); - } - - public class ClassWithSingleLetterCtorParameter - { - public ClassWithSingleLetterCtorParameter(int x, string y) - { - X = x; - Y = y; - } - - public int X { get; } - - public string Y { get; } - } - - public class ClassWithSingleLetterProperty - { - public int X { get; set; } - - public int Y { get; set; } - } - - public class ClassWithMultiLetterCtorParameters - { - public ClassWithMultiLetterCtorParameters( - int intOption = 123, - string stringOption = "the default", - bool boolOption = false) - { - IntOption = intOption; - StringOption = stringOption; - BoolOption = boolOption; - } - - public int IntOption { get; } - public string StringOption { get; } - public bool BoolOption { get; } - } - - public class ClassWithMultiLetterSetters - { - public int IntOption { get; set; } - public string StringOption { get; set; } - public bool BoolOption { get; set; } - } - - public class ClassWithSettersAndCtorParametersWithDifferentNames - { - public ClassWithSettersAndCtorParametersWithDifferentNames( - int i = 123, - string s = "the default", - bool b = false) - { - IntOption = i; - StringOption = s; - BoolOption = b; - } - - public int IntOption { get; set; } - public string StringOption { get; set; } - public bool BoolOption { get; set; } - } - - public class ClassWithSettersAndCtorParametersWithMatchingNames - { - public ClassWithSettersAndCtorParametersWithMatchingNames( - int intOption = 123, - string stringOption = "the default", - bool boolOption = false) - { - IntOption = intOption; - StringOption = stringOption; - BoolOption = boolOption; - } - - public int IntOption { get; set; } - public string StringOption { get; set; } - public bool BoolOption { get; set; } - } - - public class ClassWithCtorParameter - { - public ClassWithCtorParameter(T value) => Value = value; - - public T Value { get; } - } - - public class ClassWithSetter - { - public T Value { get; set; } - } - } -} diff --git a/src/System.CommandLine/Binding/BindingSide.cs b/src/System.CommandLine/Binding/BindingSide.cs index aba70e0bcd..e8d19797a3 100644 --- a/src/System.CommandLine/Binding/BindingSide.cs +++ b/src/System.CommandLine/Binding/BindingSide.cs @@ -5,7 +5,7 @@ namespace System.CommandLine.Binding { public abstract class BindingSide { - public BindingSide(BindingGetter get, BindingSetter set) + protected BindingSide(BindingGetter get, BindingSetter set) { // Set is null in some key scenarios like services Set = set; diff --git a/src/System.CommandLine/Binding/IBinder.cs b/src/System.CommandLine/Binding/IBinder.cs index 970602a34f..71ba5a0926 100644 --- a/src/System.CommandLine/Binding/IBinder.cs +++ b/src/System.CommandLine/Binding/IBinder.cs @@ -7,4 +7,14 @@ public interface IBinder { object GetTarget(BindingContext context); } + + + + + + + + + + } diff --git a/src/System.CommandLine/Binding/ReflectionBinder.cs b/src/System.CommandLine/Binding/ReflectionBinder.cs index bd7cd531cc..3003fdabad 100644 --- a/src/System.CommandLine/Binding/ReflectionBinder.cs +++ b/src/System.CommandLine/Binding/ReflectionBinder.cs @@ -12,13 +12,13 @@ namespace System.CommandLine.Binding public class ReflectionBinder : IBinder { public ReflectionBinder(Type type) - => _type = type ; + => _type = type; + + internal const BindingFlags CommonBindingFlags = + BindingFlags.IgnoreCase + | BindingFlags.Public + | BindingFlags.Instance; - private const BindingFlags CommonBindingFlags = BindingFlags.FlattenHierarchy - | BindingFlags.IgnoreCase - | BindingFlags.Public - | BindingFlags.Instance; - private object _explicitlySetTarget; private readonly Type _type; @@ -77,7 +77,7 @@ public void AddBinding(PropertyInfo propertyInfo, Func valueFunc) => AddBinding(propertyInfo, ValueBindingSide.Create(valueFunc)); public void AddBinding(PropertyInfo propertyInfo, BindingSide parserBindingSide) - { + { var propertyBindingSide = new PropertyBindingSide(propertyInfo); if (propertyInfo.GetAccessors(true)[0].IsStatic) { @@ -88,7 +88,7 @@ public void AddBinding(PropertyInfo propertyInfo, BindingSide parserBindingSide) _handlerBindingSet.AddBinding(propertyBindingSide, parserBindingSide); } } - + public void AddBindings(Type type, MethodInfo methodInfo, ICommand command) { if (command == null) @@ -96,18 +96,6 @@ public void AddBindings(Type type, MethodInfo methodInfo, ICommand command) throw new ArgumentNullException(nameof(command)); } - if (type == null) - { - } - - if ( methodInfo == null) - { - } - - if (type != null && methodInfo != null) - { - } - type = type ?? methodInfo.DeclaringType; // bind in same order as invocation. not sure this matters @@ -115,7 +103,7 @@ public void AddBindings(Type type, MethodInfo methodInfo, ICommand command) AddBindingForPropertiesToCommand(type, command); AddBindingForParametersToCommand(methodInfo, command); AddBindingForServiceParameters(); - + _isBoundToCommand = true; } @@ -141,13 +129,13 @@ private object[] GetNullHandledInvocationArguments() private void BindConstructor(BindingContext context) => _constructorBindingSet.Bind(context, null); - private void BindInvocation(BindingContext context, object target) + private void BindProperties(BindingContext context, object target) => _handlerBindingSet.Bind(context, target); public object GetTarget(BindingContext context) { AddBindingsIfNeeded(context.ParseResult.CommandResult.Command); - + // Allow for the possibility that constructor binding explicitly sets the target. BindConstructor(context); @@ -162,7 +150,8 @@ public object GetTarget(BindingContext context) GetNullHandledConstructorArguments()); } - BindInvocation(context, target); + BindProperties(context, target); + return target; } @@ -188,6 +177,7 @@ public object InvokeAsync(InvocationContext context) var target = GetTarget(context.BindingContext); // Invocation bind is done during Target construction (to allow dependency on properties) var value = _handlerMethodInfo.Invoke(target, GetNullHandledInvocationArguments()); + return CommandHandler.GetResultCodeAsync(value, context); } @@ -230,7 +220,8 @@ private void AddBindingForPropertyParameters(Type type, IEnumerable matchingProperties = type.GetProperties(bindingFlags) .Where(p => p.Name.Equals(parameterInfo.Name, StringComparison.InvariantCultureIgnoreCase)) .ToList(); @@ -314,10 +305,10 @@ private void AddBindingForMethodBase(MethodBase methodBase, ICommand command) AddBinding(parameterInfo, command); } } - + private void AddBinding(PropertyInfo propertyInfo, ICommand command) { - var symbol = FindMatchingSymbol(propertyInfo.Name, command); + var symbol = FindMatchingSymbol(propertyInfo.Name, command); switch (symbol) { diff --git a/src/System.CommandLine/ParserExtensions.cs b/src/System.CommandLine/ParserExtensions.cs index a903146ee5..767265646b 100644 --- a/src/System.CommandLine/ParserExtensions.cs +++ b/src/System.CommandLine/ParserExtensions.cs @@ -1,6 +1,7 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// 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.CommandLine.Binding; using System.Linq; namespace System.CommandLine @@ -11,5 +12,36 @@ public static ParseResult Parse( this Parser parser, string input) => parser.Parse(input.Tokenize().ToArray(), input); + + public static T CreateInstance( + this Parser parser, + string commandLine) + { + var parseResult = parser.Parse(commandLine); + + var bindingContext = new BindingContext(parseResult, parser); + + var binder = new ReflectionBinder(typeof(T)); + + var instance = (T)binder.GetTarget(bindingContext); + + return instance; + } + + public static void UpdateInstance( + this Parser parser, + T instance, + string commandLine) + { + var parseResult = parser.Parse(commandLine); + + var bindingContext = new BindingContext(parseResult, parser); + + var binder = new ReflectionBinder(typeof(T)); + + + } } + + }