diff --git a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs index 8b64bbd00e..62552003ad 100644 --- a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs +++ b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs @@ -1,12 +1,13 @@ // 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 System.Net; using Xunit; namespace System.CommandLine.Tests.Binding @@ -229,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) @@ -251,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() { @@ -273,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")] @@ -495,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() { @@ -505,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() { @@ -515,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() { @@ -535,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() { @@ -556,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() { @@ -688,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() { @@ -698,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() { @@ -708,7 +709,71 @@ 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 + +#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 +783,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 +793,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 +803,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 +823,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 +835,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..0846fb7a3c 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; @@ -24,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)) @@ -62,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; @@ -85,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; @@ -96,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; @@ -129,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)) @@ -154,6 +169,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)) @@ -178,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))