From a56720f3fcba88c50d3fbacc5f9a16a34fd6c173 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Wed, 11 Dec 2019 05:56:19 +0200 Subject: [PATCH] Fix Nullable in UnParserExtensions --- src/CommandLine/UnParserExtensions.cs | 8 +- .../Fakes/Options_With_Defaults.cs | 11 ++ .../Options_With_Enum_Having_HelpText.cs | 2 +- .../Unit/UnParserExtensionsTests.cs | 172 +++++++++++++++--- 4 files changed, 168 insertions(+), 25 deletions(-) diff --git a/src/CommandLine/UnParserExtensions.cs b/src/CommandLine/UnParserExtensions.cs index 701c04d2..59c4d3b7 100644 --- a/src/CommandLine/UnParserExtensions.cs +++ b/src/CommandLine/UnParserExtensions.cs @@ -134,7 +134,7 @@ public static string FormatCommandLine(this Parser parser, T options, Action< Value = pi.GetValue(options, null).NormalizeValue(), PropertyValue = pi.GetValue(options, null) }) - where !info.PropertyValue.IsEmpty(info.Specification,settings.SkipDefault) + where !info.PropertyValue.IsEmpty(info.Specification, settings.SkipDefault) select info) .Memorize(); @@ -204,7 +204,7 @@ private static string FormatValue(Specification spec, object value) private static object FormatWithQuotesIfString(object value) { - if (value is DateTime) value = $"\"{value}\""; + if (value is DateTime || value is TimeSpan || value is DateTimeOffset) return $"\"{value}\""; Func doubQt = v => v.Contains("\"") ? v.Replace("\"", "\\\"") : v; @@ -256,11 +256,13 @@ private static object NormalizeValue(this object value) return value; } - private static bool IsEmpty(this object value, Specification specification,bool skipDefault) + private static bool IsEmpty(this object value, Specification specification, bool skipDefault) { if (value == null) return true; if (skipDefault && value.Equals(specification.DefaultValue.FromJust())) return true; + if (Nullable.GetUnderlyingType(specification.ConversionType) != null) return false; //nullable + #if !SKIP_FSHARP if (ReflectionHelper.IsFSharpOptionType(value.GetType()) && !FSharpOptionHelper.IsSome(value)) return true; #endif diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs b/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs index 792f7933..eca68790 100644 --- a/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs +++ b/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs @@ -11,5 +11,16 @@ class Options_With_Defaults [Option(Default = Shapes.Square)] public Shapes P4 { get; set; } } + class Nuulable_Options_With_Defaults + { + [Option(Default = 99)] + public int? P1 { get; set; } + [Option()] + public string P2 { get; set; } + [Option(Default = 88)] + public int? P3 { get; set; } + [Option(Default = Shapes.Square)] + public Shapes? P4 { get; set; } + } } diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs b/tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs index 4e1560b1..e3ede175 100644 --- a/tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs +++ b/tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs @@ -2,7 +2,7 @@ namespace CommandLine.Tests.Fakes { - enum Shapes + public enum Shapes { Circle, Square, diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 03247105..90cb8929 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -105,6 +105,8 @@ public static void UnParsing_instance_with_dash_in_value_and_dashdash_disabled_r .Should().BeEquivalentTo("-something with dash"); } + #region PR 550 + [Fact] public static void UnParsing_instance_with_default_values_when_skip_default_is_false() { @@ -114,31 +116,159 @@ public static void UnParsing_instance_with_default_values_when_skip_default_is_f .Should().BeEquivalentTo("--p1 99 --p2 xyz --p3 88 --p4 Square"); } - [Fact] - public static void UnParsing_instance_with_default_values_when_skip_default_is_true() + [Theory] + [InlineData(true, "--p2 xyz")] + [InlineData(false, "--p1 99 --p2 xyz --p3 88 --p4 Square")] + public static void UnParsing_instance_with_default_values_when_skip_default_is_true(bool skipDefault, string expected) { - var options = new Options_With_Defaults {P2 = "xyz", P1 = 99, P3 = 88,P4= Shapes.Square } ; + var options = new Options_With_Defaults { P2 = "xyz", P1 = 99, P3 = 88, P4 = Shapes.Square }; new Parser() - .FormatCommandLine(options,x=>x.SkipDefault=true) - .Should().BeEquivalentTo("--p2 xyz"); + .FormatCommandLine(options, x => x.SkipDefault = skipDefault) + .Should().BeEquivalentTo(expected); } + [Theory] + [InlineData(true, "--p2 xyz")] + [InlineData(false, "--p1 99 --p2 xyz --p3 88 --p4 Square")] + public static void UnParsing_instance_with_nullable_default_values_when_skip_default_is_true(bool skipDefault, string expected) + { + var options = new Nuulable_Options_With_Defaults { P2 = "xyz", P1 = 99, P3 = 88, P4 = Shapes.Square }; + new Parser() + .FormatCommandLine(options, x => x.SkipDefault = skipDefault) + .Should().BeEquivalentTo(expected); + } [Fact] public static void UnParsing_instance_with_datetime() { - var date = new DateTime(2019, 5, 1); - var options = new Options_Date { Start=date }; + var date = new DateTime(2019, 5, 1); + var options = new Options_Date { Start = date }; + var result = new Parser() + .FormatCommandLine(options) + .Should().MatchRegex("--start\\s\".+\""); + } + + [Fact] + public static void UnParsing_instance_with_datetime_nullable() + { + var date = new DateTime(2019, 5, 1); + var options = new Options_Date_Nullable { Start = date }; + var result = new Parser() + .FormatCommandLine(options) + .Should().MatchRegex("--start\\s\".+\""); + } + + [Fact] + public static void UnParsing_instance_with_datetime_offset() + { + DateTimeOffset date = new DateTime(2019, 5, 1); + var options = new Options_DateTimeOffset { Start = date }; var result = new Parser() - .FormatCommandLine(options); //--start "1/5/2019 12:00:00 AM", date is based on Culture - var expected = Regex.Match(result, @"--start\s"".+""").Success; //result contain quote - Assert.True(expected); + .FormatCommandLine(options) + .Should().MatchRegex("--start\\s\".+\""); } - internal class Options_Date + [Fact] + public static void UnParsing_instance_with_timespan() + { + var ts = new TimeSpan(1,2,3); + var options = new Options_TimeSpan { Start = ts }; + var result = new Parser() + .FormatCommandLine(options) + .Should().BeEquivalentTo("--start \"01:02:03\""); + } + + [Theory] + [InlineData(false, 0, "")] //default behaviour based on type + [InlineData(false, 1, "-v 1")] //default skip=false + [InlineData(false, 2, "-v 2")] + [InlineData(true, 1, "")] //default skip=true + public static void UnParsing_instance_with_int(bool skipDefault, int value, string expected) + { + var options = new Option_Int { VerboseLevel = value }; + var result = new Parser() + .FormatCommandLine(options, x => x.SkipDefault = skipDefault) + .Should().BeEquivalentTo(expected); + + } + + [Theory] + [InlineData(false, 0, "-v 0")] + [InlineData(false, 1, "-v 1")] //default + [InlineData(false, 2, "-v 2")] + [InlineData(false, null, "")] + [InlineData(true, 1, "")] //default + public static void UnParsing_instance_with_int_nullable(bool skipDefault, int? value, string expected) + { + var options = new Option_Int_Nullable { VerboseLevel = value }; + var result = new Parser() + .FormatCommandLine(options, x => x.SkipDefault = skipDefault) + .Should().BeEquivalentTo(expected); + + } + [Theory] + [InlineData(Shapes.Circle, "--shape circle")] + [InlineData(Shapes.Square, "--shape square")] + [InlineData(null, "")] + public static void UnParsing_instance_with_nullable_enum(Shapes? shape, string expected) + { + var options = new Option_Nullable_Enum { Shape = shape }; + var result = new Parser() + .FormatCommandLine(options) + .Should().BeEquivalentTo(expected); + } + + [Theory] + [InlineData(true, "-v True")] + [InlineData(false, "-v False")] + [InlineData(null, "")] + public static void UnParsing_instance_with_nullable_bool(bool? flag, string expected) + { + var options = new Option_Nullable_Bool { Verbose = flag }; + var result = new Parser() + .FormatCommandLine(options) + .Should().BeEquivalentTo(expected); + } + class Option_Int_Nullable + { + [Option('v', Default = 1)] + public int? VerboseLevel { get; set; } + } + class Option_Int + { + [Option('v', Default = 1)] + public int VerboseLevel { get; set; } + } + class Option_Nullable_Bool + { + [Option('v')] + public bool? Verbose { get; set; } + } + class Option_Nullable_Enum + { + [Option] + public Shapes? Shape { get; set; } + } + class Options_Date + { + [Option] + public DateTime Start { get; set; } + } + class Options_Date_Nullable { [Option] public DateTime? Start { get; set; } } + class Options_TimeSpan + { + [Option] + public TimeSpan Start { get; set; } + } + class Options_DateTimeOffset + { + [Option] + public DateTimeOffset Start { get; set; } + } + #endregion public static IEnumerable UnParseData { get @@ -172,15 +302,15 @@ public static IEnumerable UnParseDataImmutable get { yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty(), default(bool), default(long)), "" }; - yield return new object[] { new Immutable_Simple_Options ("", Enumerable.Empty(), true, default(long) ), "-x" }; - yield return new object[] { new Immutable_Simple_Options ("", new[] { 1, 2, 3 }, default(bool), default(long) ), "-i 1 2 3" }; - yield return new object[] { new Immutable_Simple_Options ("nospaces", Enumerable.Empty(), default(bool), default(long)), "--stringvalue nospaces" }; - yield return new object[] { new Immutable_Simple_Options (" with spaces ", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \" with spaces \"" }; - yield return new object[] { new Immutable_Simple_Options ("with\"quote", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \"with\\\"quote\"" }; - yield return new object[] { new Immutable_Simple_Options ("with \"quotes\" spaced", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \"with \\\"quotes\\\" spaced\"" }; - yield return new object[] { new Immutable_Simple_Options ("", Enumerable.Empty(), default(bool), 123456789), "123456789" }; - yield return new object[] { new Immutable_Simple_Options ("nospaces", new[] { 1, 2, 3 }, true, 123456789), "-i 1 2 3 --stringvalue nospaces -x 123456789" }; - yield return new object[] { new Immutable_Simple_Options ("with \"quotes\" spaced", new[] { 1, 2, 3 }, true, 123456789), "-i 1 2 3 --stringvalue \"with \\\"quotes\\\" spaced\" -x 123456789" }; + yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty(), true, default(long)), "-x" }; + yield return new object[] { new Immutable_Simple_Options("", new[] { 1, 2, 3 }, default(bool), default(long)), "-i 1 2 3" }; + yield return new object[] { new Immutable_Simple_Options("nospaces", Enumerable.Empty(), default(bool), default(long)), "--stringvalue nospaces" }; + yield return new object[] { new Immutable_Simple_Options(" with spaces ", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \" with spaces \"" }; + yield return new object[] { new Immutable_Simple_Options("with\"quote", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \"with\\\"quote\"" }; + yield return new object[] { new Immutable_Simple_Options("with \"quotes\" spaced", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \"with \\\"quotes\\\" spaced\"" }; + yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty(), default(bool), 123456789), "123456789" }; + yield return new object[] { new Immutable_Simple_Options("nospaces", new[] { 1, 2, 3 }, true, 123456789), "-i 1 2 3 --stringvalue nospaces -x 123456789" }; + yield return new object[] { new Immutable_Simple_Options("with \"quotes\" spaced", new[] { 1, 2, 3 }, true, 123456789), "-i 1 2 3 --stringvalue \"with \\\"quotes\\\" spaced\" -x 123456789" }; } } @@ -189,7 +319,7 @@ public static IEnumerable UnParseDataHidden get { yield return new object[] { new Hidden_Option { HiddenOption = "hidden" }, true, "--hiddenOption hidden" }; - yield return new object[] { new Hidden_Option { HiddenOption = "hidden" }, false, ""}; + yield return new object[] { new Hidden_Option { HiddenOption = "hidden" }, false, "" }; } } #if !SKIP_FSHARP