Skip to content

Commit

Permalink
Fix Nullable in UnParserExtensions
Browse files Browse the repository at this point in the history
  • Loading branch information
moh-hassan committed Dec 11, 2019
1 parent 70a60b9 commit a56720f
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 25 deletions.
8 changes: 5 additions & 3 deletions src/CommandLine/UnParserExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public static string FormatCommandLine<T>(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();

Expand Down Expand Up @@ -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<string, string> doubQt = v
=> v.Contains("\"") ? v.Replace("\"", "\\\"") : v;

Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace CommandLine.Tests.Fakes
{
enum Shapes
public enum Shapes
{
Circle,
Square,
Expand Down
172 changes: 151 additions & 21 deletions tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand All @@ -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<object[]> UnParseData
{
get
Expand Down Expand Up @@ -172,15 +302,15 @@ public static IEnumerable<object[]> UnParseDataImmutable
get
{
yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty<int>(), default(bool), default(long)), "" };
yield return new object[] { new Immutable_Simple_Options ("", Enumerable.Empty<int>(), 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<int>(), default(bool), default(long)), "--stringvalue nospaces" };
yield return new object[] { new Immutable_Simple_Options (" with spaces ", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \" with spaces \"" };
yield return new object[] { new Immutable_Simple_Options ("with\"quote", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \"with\\\"quote\"" };
yield return new object[] { new Immutable_Simple_Options ("with \"quotes\" spaced", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \"with \\\"quotes\\\" spaced\"" };
yield return new object[] { new Immutable_Simple_Options ("", Enumerable.Empty<int>(), 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<int>(), 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<int>(), default(bool), default(long)), "--stringvalue nospaces" };
yield return new object[] { new Immutable_Simple_Options(" with spaces ", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \" with spaces \"" };
yield return new object[] { new Immutable_Simple_Options("with\"quote", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \"with\\\"quote\"" };
yield return new object[] { new Immutable_Simple_Options("with \"quotes\" spaced", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \"with \\\"quotes\\\" spaced\"" };
yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty<int>(), 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" };
}
}

Expand All @@ -189,7 +319,7 @@ public static IEnumerable<object[]> 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
Expand Down

0 comments on commit a56720f

Please sign in to comment.