From 4a016ab3d1d6ce0be892a2989de71094960d5b84 Mon Sep 17 00:00:00 2001 From: RobIII Date: Mon, 9 May 2022 17:51:06 +0200 Subject: [PATCH 1/4] Add support for DateOnly and TimeOnly on .Net 6 (#1732). --- .../Binding/TypeConversionTests.cs | 76 ++++++++++++++----- .../ArgumentConverter.StringConverters.cs | 36 ++++++++- 2 files changed, 91 insertions(+), 21 deletions(-) diff --git a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs index 8b64bbd00e..e58d71c87b 100644 --- a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs +++ b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs @@ -1,11 +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 FluentAssertions; using System.Collections; using System.Collections.Generic; using System.CommandLine.Utility; using System.IO; -using FluentAssertions; using System.Linq; using Xunit; @@ -229,7 +229,7 @@ public void Nullable_bool_parses_as_false_when_the_option_has_been_applied(strin public void Nullable_bool_parses_as_null_when_the_option_has_not_been_applied() { var option = new Option("-x"); - + option .Parse("") .GetValueForOption(option) @@ -251,7 +251,7 @@ public void Generic_option_bool_parses_when_passed_to_non_generic_GetValueForOpt parseResult.GetValueForOption((Option)option).Should().Be(true); } - + [Fact] public void When_exactly_one_argument_is_expected_and_none_are_provided_then_getting_value_throws() { @@ -273,7 +273,7 @@ public void When_exactly_one_argument_is_expected_and_none_are_provided_then_get .Should() .Be("Required argument missing for option: '-x'."); } - + [Theory] [InlineData("c -a o c c")] [InlineData("c c -a o c")] @@ -495,7 +495,7 @@ public void Values_can_be_correctly_converted_to_decimal_without_the_parser_spec value.Should().Be(123.456m); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_decimal_without_the_parser_specifying_a_custom_converter() { @@ -505,7 +505,7 @@ public void Values_can_be_correctly_converted_to_nullable_decimal_without_the_pa value.Should().Be(123.456m); } - + [Fact] public void Values_can_be_correctly_converted_to_double_without_the_parser_specifying_a_custom_converter() { @@ -515,7 +515,7 @@ public void Values_can_be_correctly_converted_to_double_without_the_parser_speci value.Should().Be(123.456d); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_double_without_the_parser_specifying_a_custom_converter() { @@ -535,7 +535,7 @@ public void Values_can_be_correctly_converted_to_float_without_the_parser_specif value.Should().Be(123.456f); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_float_without_the_parser_specifying_a_custom_converter() { @@ -556,7 +556,7 @@ public void Values_can_be_correctly_converted_to_Guid_without_the_parser_specify value.Should().Be(Guid.Parse(guidString)); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_Guid_without_the_parser_specifying_a_custom_converter() { @@ -688,7 +688,7 @@ public void Values_can_be_correctly_converted_to_nullable_ushort_without_the_par value.Should().Be(1234); } - + [Fact] public void Values_can_be_correctly_converted_to_sbyte_without_the_parser_specifying_a_custom_converter() { @@ -698,7 +698,7 @@ public void Values_can_be_correctly_converted_to_sbyte_without_the_parser_specif value.Should().Be(123); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_sbyte_without_the_parser_specifying_a_custom_converter() { @@ -708,7 +708,49 @@ public void Values_can_be_correctly_converted_to_nullable_sbyte_without_the_pars value.Should().Be(123); } - + +#if NET6_0_OR_GREATER + [Fact] + public void Values_can_be_correctly_converted_to_dateonly_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-us"); + + var value = option.Parse("-us 2022-03-02").GetValueForOption(option); + + value.Should().Be(DateOnly.Parse("2022-03-02")); + } + + [Fact] + public void Values_can_be_correctly_converted_to_nullable_dateonly_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-x"); + + var value = option.Parse("-x 2022-03-02").GetValueForOption(option); + + value.Should().Be(DateOnly.Parse("2022-03-02")); + } + + [Fact] + public void Values_can_be_correctly_converted_to_timeonly_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-us"); + + var value = option.Parse("-us 12:34:56").GetValueForOption(option); + + value.Should().Be(TimeOnly.Parse("12:34:56")); + } + + [Fact] + public void Values_can_be_correctly_converted_to_nullable_timeonly_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-x"); + + var value = option.Parse("-x 12:34:56").GetValueForOption(option); + + value.Should().Be(TimeOnly.Parse("12:34:56")); + } +#endif + [Fact] public void Values_can_be_correctly_converted_to_byte_without_the_parser_specifying_a_custom_converter() { @@ -718,7 +760,7 @@ public void Values_can_be_correctly_converted_to_byte_without_the_parser_specify value.Should().Be(123); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_byte_without_the_parser_specifying_a_custom_converter() { @@ -728,7 +770,7 @@ public void Values_can_be_correctly_converted_to_nullable_byte_without_the_parse value.Should().Be(123); } - + [Fact] public void Values_can_be_correctly_converted_to_uint_without_the_parser_specifying_a_custom_converter() { @@ -738,7 +780,7 @@ public void Values_can_be_correctly_converted_to_uint_without_the_parser_specify value.Should().Be(1234); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_uint_without_the_parser_specifying_a_custom_converter() { @@ -758,7 +800,7 @@ public void Values_can_be_correctly_converted_to_array_of_int_without_the_parser value.Should().BeEquivalentTo(1, 2, 3); } - + [Theory] [InlineData(0, 100_000, typeof(string[]))] [InlineData(0, 3, typeof(string[]))] @@ -770,7 +812,7 @@ public void Values_can_be_correctly_converted_to_array_of_int_without_the_parser [InlineData(0, 3, typeof(IList))] [InlineData(0, 100_000, typeof(ICollection))] [InlineData(0, 3, typeof(ICollection))] - + [InlineData(1, 100_000, typeof(string[]))] [InlineData(1, 3, typeof(string[]))] [InlineData(1, 100_000, typeof(IEnumerable))] diff --git a/src/System.CommandLine/Binding/ArgumentConverter.StringConverters.cs b/src/System.CommandLine/Binding/ArgumentConverter.StringConverters.cs index c4e56bad5e..06094ec8df 100644 --- a/src/System.CommandLine/Binding/ArgumentConverter.StringConverters.cs +++ b/src/System.CommandLine/Binding/ArgumentConverter.StringConverters.cs @@ -24,6 +24,20 @@ internal static partial class ArgumentConverter return false; }, +#if NET6_0_OR_GREATER + [typeof(DateOnly)] = (string input, out object? value) => + { + if (DateOnly.TryParse(input, out var parsed)) + { + value = parsed; + return true; + } + + value = default; + return false; + }, +#endif + [typeof(DateTime)] = (string input, out object? value) => { if (DateTime.TryParse(input, out var parsed)) @@ -62,7 +76,7 @@ internal static partial class ArgumentConverter [typeof(DirectoryInfo)] = (string path, out object? value) => { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { value = default; return false; @@ -85,7 +99,7 @@ internal static partial class ArgumentConverter [typeof(FileInfo)] = (string path, out object? value) => { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { value = default; return false; @@ -96,7 +110,7 @@ internal static partial class ArgumentConverter [typeof(FileSystemInfo)] = (string path, out object? value) => { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { value = default; return false; @@ -129,7 +143,7 @@ internal static partial class ArgumentConverter value = default; return false; }, - + [typeof(Guid)] = (string input, out object? value) => { if (Guid.TryParse(input, out var parsed)) @@ -178,6 +192,20 @@ internal static partial class ArgumentConverter return false; }, +#if NET6_0_OR_GREATER + [typeof(TimeOnly)] = (string input, out object? value) => + { + if (TimeOnly.TryParse(input, out var parsed)) + { + value = parsed; + return true; + } + + value = default; + return false; + }, +#endif + [typeof(uint)] = (string token, out object? value) => { if (uint.TryParse(token, out var uintValue)) From 1ac7dfd14c70c1af882fc78602afe1ee81b584d0 Mon Sep 17 00:00:00 2001 From: Rob Janssen Date: Tue, 10 May 2022 01:48:58 +0200 Subject: [PATCH 2/4] Add support for IPAddress and IPEndpoint arguments (#1735) * Add support of IPAddress and IPEndpoint arguments * IPEndPoint.TryParse isn't supported until .Net 7 * TryParse should work from .Net Core >= 3.1 * Add missing [Fact] attributes * Remove nullable IPAddress/IPEndPoint tests --- .../Binding/TypeConversionTests.cs | 25 ++++++++++++++++- .../ArgumentConverter.StringConverters.cs | 27 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs index 8b64bbd00e..afdbc3f415 100644 --- a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs +++ b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs @@ -8,6 +8,7 @@ using FluentAssertions; using System.Linq; using Xunit; +using System.Net; namespace System.CommandLine.Tests.Binding { @@ -708,7 +709,29 @@ public void Values_can_be_correctly_converted_to_nullable_sbyte_without_the_pars value.Should().Be(123); } - + + [Fact] + public void Values_can_be_correctly_converted_to_ipaddress_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-us"); + + var value = option.Parse("-us 1.2.3.4").GetValueForOption(option); + + value.Should().Be(IPAddress.Parse("1.2.3.4")); + } + +#if NETCOREAPP3_0_OR_GREATER + [Fact] + public void Values_can_be_correctly_converted_to_ipendpoint_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-us"); + + var value = option.Parse("-us 1.2.3.4:56").GetValueForOption(option); + + value.Should().Be(IPEndPoint.Parse("1.2.3.4:56")); + } +#endif + [Fact] public void Values_can_be_correctly_converted_to_byte_without_the_parser_specifying_a_custom_converter() { diff --git a/src/System.CommandLine/Binding/ArgumentConverter.StringConverters.cs b/src/System.CommandLine/Binding/ArgumentConverter.StringConverters.cs index c4e56bad5e..82cb8d90eb 100644 --- a/src/System.CommandLine/Binding/ArgumentConverter.StringConverters.cs +++ b/src/System.CommandLine/Binding/ArgumentConverter.StringConverters.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.Net; namespace System.CommandLine.Binding; @@ -154,6 +155,32 @@ internal static partial class ArgumentConverter return false; }, + [typeof(IPAddress)] = (string token, out object? value) => + { + if (IPAddress.TryParse(token, out var ip)) + { + value = ip; + return true; + } + + value = default; + return false; + }, + +#if NETCOREAPP3_0_OR_GREATER + [typeof(IPEndPoint)] = (string token, out object? value) => + { + if (IPEndPoint.TryParse(token, out var ipendpoint)) + { + value = ipendpoint; + return true; + } + + value = default; + return false; + }, +#endif + [typeof(long)] = (string token, out object? value) => { if (long.TryParse(token, out var longValue)) From ea0a10b8f84919f8f0b89227d5b0a014c23198da Mon Sep 17 00:00:00 2001 From: RobIII Date: Mon, 9 May 2022 17:51:06 +0200 Subject: [PATCH 3/4] Add support for DateOnly and TimeOnly on .Net 6 (#1732). --- .../Binding/TypeConversionTests.cs | 76 ++++++++++++++----- .../ArgumentConverter.StringConverters.cs | 36 ++++++++- 2 files changed, 90 insertions(+), 22 deletions(-) diff --git a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs index afdbc3f415..90b5e5bebf 100644 --- a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs +++ b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs @@ -1,14 +1,14 @@ // 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 FluentAssertions; using System.Collections; using System.Collections.Generic; using System.CommandLine.Utility; using System.IO; -using FluentAssertions; using System.Linq; -using Xunit; using System.Net; +using Xunit; namespace System.CommandLine.Tests.Binding { @@ -230,7 +230,7 @@ public void Nullable_bool_parses_as_false_when_the_option_has_been_applied(strin public void Nullable_bool_parses_as_null_when_the_option_has_not_been_applied() { var option = new Option("-x"); - + option .Parse("") .GetValueForOption(option) @@ -252,7 +252,7 @@ public void Generic_option_bool_parses_when_passed_to_non_generic_GetValueForOpt parseResult.GetValueForOption((Option)option).Should().Be(true); } - + [Fact] public void When_exactly_one_argument_is_expected_and_none_are_provided_then_getting_value_throws() { @@ -274,7 +274,7 @@ public void When_exactly_one_argument_is_expected_and_none_are_provided_then_get .Should() .Be("Required argument missing for option: '-x'."); } - + [Theory] [InlineData("c -a o c c")] [InlineData("c c -a o c")] @@ -496,7 +496,7 @@ public void Values_can_be_correctly_converted_to_decimal_without_the_parser_spec value.Should().Be(123.456m); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_decimal_without_the_parser_specifying_a_custom_converter() { @@ -506,7 +506,7 @@ public void Values_can_be_correctly_converted_to_nullable_decimal_without_the_pa value.Should().Be(123.456m); } - + [Fact] public void Values_can_be_correctly_converted_to_double_without_the_parser_specifying_a_custom_converter() { @@ -516,7 +516,7 @@ public void Values_can_be_correctly_converted_to_double_without_the_parser_speci value.Should().Be(123.456d); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_double_without_the_parser_specifying_a_custom_converter() { @@ -536,7 +536,7 @@ public void Values_can_be_correctly_converted_to_float_without_the_parser_specif value.Should().Be(123.456f); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_float_without_the_parser_specifying_a_custom_converter() { @@ -557,7 +557,7 @@ public void Values_can_be_correctly_converted_to_Guid_without_the_parser_specify value.Should().Be(Guid.Parse(guidString)); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_Guid_without_the_parser_specifying_a_custom_converter() { @@ -689,7 +689,7 @@ public void Values_can_be_correctly_converted_to_nullable_ushort_without_the_par value.Should().Be(1234); } - + [Fact] public void Values_can_be_correctly_converted_to_sbyte_without_the_parser_specifying_a_custom_converter() { @@ -699,7 +699,7 @@ public void Values_can_be_correctly_converted_to_sbyte_without_the_parser_specif value.Should().Be(123); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_sbyte_without_the_parser_specifying_a_custom_converter() { @@ -730,7 +730,47 @@ public void Values_can_be_correctly_converted_to_ipendpoint_without_the_parser_s value.Should().Be(IPEndPoint.Parse("1.2.3.4:56")); } -#endif + +#if NET6_0_OR_GREATER + [Fact] + public void Values_can_be_correctly_converted_to_dateonly_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-us"); + + var value = option.Parse("-us 2022-03-02").GetValueForOption(option); + + value.Should().Be(DateOnly.Parse("2022-03-02")); + } + + [Fact] + public void Values_can_be_correctly_converted_to_nullable_dateonly_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-x"); + + var value = option.Parse("-x 2022-03-02").GetValueForOption(option); + + value.Should().Be(DateOnly.Parse("2022-03-02")); + } + + [Fact] + public void Values_can_be_correctly_converted_to_timeonly_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-us"); + + var value = option.Parse("-us 12:34:56").GetValueForOption(option); + + value.Should().Be(TimeOnly.Parse("12:34:56")); + } + + [Fact] + public void Values_can_be_correctly_converted_to_nullable_timeonly_without_the_parser_specifying_a_custom_converter() + { + var option = new Option("-x"); + + var value = option.Parse("-x 12:34:56").GetValueForOption(option); + + value.Should().Be(TimeOnly.Parse("12:34:56")); + } [Fact] public void Values_can_be_correctly_converted_to_byte_without_the_parser_specifying_a_custom_converter() @@ -741,7 +781,7 @@ public void Values_can_be_correctly_converted_to_byte_without_the_parser_specify value.Should().Be(123); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_byte_without_the_parser_specifying_a_custom_converter() { @@ -751,7 +791,7 @@ public void Values_can_be_correctly_converted_to_nullable_byte_without_the_parse value.Should().Be(123); } - + [Fact] public void Values_can_be_correctly_converted_to_uint_without_the_parser_specifying_a_custom_converter() { @@ -761,7 +801,7 @@ public void Values_can_be_correctly_converted_to_uint_without_the_parser_specify value.Should().Be(1234); } - + [Fact] public void Values_can_be_correctly_converted_to_nullable_uint_without_the_parser_specifying_a_custom_converter() { @@ -781,7 +821,7 @@ public void Values_can_be_correctly_converted_to_array_of_int_without_the_parser value.Should().BeEquivalentTo(1, 2, 3); } - + [Theory] [InlineData(0, 100_000, typeof(string[]))] [InlineData(0, 3, typeof(string[]))] @@ -793,7 +833,7 @@ public void Values_can_be_correctly_converted_to_array_of_int_without_the_parser [InlineData(0, 3, typeof(IList))] [InlineData(0, 100_000, typeof(ICollection))] [InlineData(0, 3, typeof(ICollection))] - + [InlineData(1, 100_000, typeof(string[]))] [InlineData(1, 3, typeof(string[]))] [InlineData(1, 100_000, typeof(IEnumerable))] diff --git a/src/System.CommandLine/Binding/ArgumentConverter.StringConverters.cs b/src/System.CommandLine/Binding/ArgumentConverter.StringConverters.cs index 82cb8d90eb..0846fb7a3c 100644 --- a/src/System.CommandLine/Binding/ArgumentConverter.StringConverters.cs +++ b/src/System.CommandLine/Binding/ArgumentConverter.StringConverters.cs @@ -25,6 +25,20 @@ internal static partial class ArgumentConverter return false; }, +#if NET6_0_OR_GREATER + [typeof(DateOnly)] = (string input, out object? value) => + { + if (DateOnly.TryParse(input, out var parsed)) + { + value = parsed; + return true; + } + + value = default; + return false; + }, +#endif + [typeof(DateTime)] = (string input, out object? value) => { if (DateTime.TryParse(input, out var parsed)) @@ -63,7 +77,7 @@ internal static partial class ArgumentConverter [typeof(DirectoryInfo)] = (string path, out object? value) => { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { value = default; return false; @@ -86,7 +100,7 @@ internal static partial class ArgumentConverter [typeof(FileInfo)] = (string path, out object? value) => { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { value = default; return false; @@ -97,7 +111,7 @@ internal static partial class ArgumentConverter [typeof(FileSystemInfo)] = (string path, out object? value) => { - if (String.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { value = default; return false; @@ -130,7 +144,7 @@ internal static partial class ArgumentConverter value = default; return false; }, - + [typeof(Guid)] = (string input, out object? value) => { if (Guid.TryParse(input, out var parsed)) @@ -205,6 +219,20 @@ internal static partial class ArgumentConverter return false; }, +#if NET6_0_OR_GREATER + [typeof(TimeOnly)] = (string input, out object? value) => + { + if (TimeOnly.TryParse(input, out var parsed)) + { + value = parsed; + return true; + } + + value = default; + return false; + }, +#endif + [typeof(uint)] = (string token, out object? value) => { if (uint.TryParse(token, out var uintValue)) From 35eae17e23ae0a9f5cd03c05360abe4af62067f3 Mon Sep 17 00:00:00 2001 From: RobIII Date: Tue, 10 May 2022 09:58:30 +0200 Subject: [PATCH 4/4] Got rid of merge marker --- src/System.CommandLine.Tests/Binding/TypeConversionTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs index d5424ebb02..62552003ad 100644 --- a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs +++ b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs @@ -710,7 +710,6 @@ public void Values_can_be_correctly_converted_to_nullable_sbyte_without_the_pars value.Should().Be(123); } -<<<<<<< HEAD [Fact] public void Values_can_be_correctly_converted_to_ipaddress_without_the_parser_specifying_a_custom_converter() {