Skip to content

Commit a56720f

Browse files
committed
Fix Nullable in UnParserExtensions
1 parent 70a60b9 commit a56720f

File tree

4 files changed

+168
-25
lines changed

4 files changed

+168
-25
lines changed

src/CommandLine/UnParserExtensions.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public static string FormatCommandLine<T>(this Parser parser, T options, Action<
134134
Value = pi.GetValue(options, null).NormalizeValue(),
135135
PropertyValue = pi.GetValue(options, null)
136136
})
137-
where !info.PropertyValue.IsEmpty(info.Specification,settings.SkipDefault)
137+
where !info.PropertyValue.IsEmpty(info.Specification, settings.SkipDefault)
138138
select info)
139139
.Memorize();
140140

@@ -204,7 +204,7 @@ private static string FormatValue(Specification spec, object value)
204204

205205
private static object FormatWithQuotesIfString(object value)
206206
{
207-
if (value is DateTime) value = $"\"{value}\"";
207+
if (value is DateTime || value is TimeSpan || value is DateTimeOffset) return $"\"{value}\"";
208208
Func<string, string> doubQt = v
209209
=> v.Contains("\"") ? v.Replace("\"", "\\\"") : v;
210210

@@ -256,11 +256,13 @@ private static object NormalizeValue(this object value)
256256
return value;
257257
}
258258

259-
private static bool IsEmpty(this object value, Specification specification,bool skipDefault)
259+
private static bool IsEmpty(this object value, Specification specification, bool skipDefault)
260260
{
261261
if (value == null) return true;
262262

263263
if (skipDefault && value.Equals(specification.DefaultValue.FromJust())) return true;
264+
if (Nullable.GetUnderlyingType(specification.ConversionType) != null) return false; //nullable
265+
264266
#if !SKIP_FSHARP
265267
if (ReflectionHelper.IsFSharpOptionType(value.GetType()) && !FSharpOptionHelper.IsSome(value)) return true;
266268
#endif

tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs

+11
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,16 @@ class Options_With_Defaults
1111
[Option(Default = Shapes.Square)]
1212
public Shapes P4 { get; set; }
1313
}
14+
class Nuulable_Options_With_Defaults
15+
{
16+
[Option(Default = 99)]
17+
public int? P1 { get; set; }
18+
[Option()]
19+
public string P2 { get; set; }
20+
[Option(Default = 88)]
21+
public int? P3 { get; set; }
22+
[Option(Default = Shapes.Square)]
23+
public Shapes? P4 { get; set; }
24+
}
1425
}
1526

tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace CommandLine.Tests.Fakes
44
{
5-
enum Shapes
5+
public enum Shapes
66
{
77
Circle,
88
Square,

tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs

+151-21
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ public static void UnParsing_instance_with_dash_in_value_and_dashdash_disabled_r
105105
.Should().BeEquivalentTo("-something with dash");
106106
}
107107

108+
#region PR 550
109+
108110
[Fact]
109111
public static void UnParsing_instance_with_default_values_when_skip_default_is_false()
110112
{
@@ -114,31 +116,159 @@ public static void UnParsing_instance_with_default_values_when_skip_default_is_f
114116
.Should().BeEquivalentTo("--p1 99 --p2 xyz --p3 88 --p4 Square");
115117
}
116118

117-
[Fact]
118-
public static void UnParsing_instance_with_default_values_when_skip_default_is_true()
119+
[Theory]
120+
[InlineData(true, "--p2 xyz")]
121+
[InlineData(false, "--p1 99 --p2 xyz --p3 88 --p4 Square")]
122+
public static void UnParsing_instance_with_default_values_when_skip_default_is_true(bool skipDefault, string expected)
119123
{
120-
var options = new Options_With_Defaults {P2 = "xyz", P1 = 99, P3 = 88,P4= Shapes.Square } ;
124+
var options = new Options_With_Defaults { P2 = "xyz", P1 = 99, P3 = 88, P4 = Shapes.Square };
121125
new Parser()
122-
.FormatCommandLine(options,x=>x.SkipDefault=true)
123-
.Should().BeEquivalentTo("--p2 xyz");
126+
.FormatCommandLine(options, x => x.SkipDefault = skipDefault)
127+
.Should().BeEquivalentTo(expected);
124128
}
125129

130+
[Theory]
131+
[InlineData(true, "--p2 xyz")]
132+
[InlineData(false, "--p1 99 --p2 xyz --p3 88 --p4 Square")]
133+
public static void UnParsing_instance_with_nullable_default_values_when_skip_default_is_true(bool skipDefault, string expected)
134+
{
135+
var options = new Nuulable_Options_With_Defaults { P2 = "xyz", P1 = 99, P3 = 88, P4 = Shapes.Square };
136+
new Parser()
137+
.FormatCommandLine(options, x => x.SkipDefault = skipDefault)
138+
.Should().BeEquivalentTo(expected);
139+
}
126140
[Fact]
127141
public static void UnParsing_instance_with_datetime()
128142
{
129-
var date = new DateTime(2019, 5, 1);
130-
var options = new Options_Date { Start=date };
143+
var date = new DateTime(2019, 5, 1);
144+
var options = new Options_Date { Start = date };
145+
var result = new Parser()
146+
.FormatCommandLine(options)
147+
.Should().MatchRegex("--start\\s\".+\"");
148+
}
149+
150+
[Fact]
151+
public static void UnParsing_instance_with_datetime_nullable()
152+
{
153+
var date = new DateTime(2019, 5, 1);
154+
var options = new Options_Date_Nullable { Start = date };
155+
var result = new Parser()
156+
.FormatCommandLine(options)
157+
.Should().MatchRegex("--start\\s\".+\"");
158+
}
159+
160+
[Fact]
161+
public static void UnParsing_instance_with_datetime_offset()
162+
{
163+
DateTimeOffset date = new DateTime(2019, 5, 1);
164+
var options = new Options_DateTimeOffset { Start = date };
131165
var result = new Parser()
132-
.FormatCommandLine(options); //--start "1/5/2019 12:00:00 AM", date is based on Culture
133-
var expected = Regex.Match(result, @"--start\s"".+""").Success; //result contain quote
134-
Assert.True(expected);
166+
.FormatCommandLine(options)
167+
.Should().MatchRegex("--start\\s\".+\"");
135168
}
136169

137-
internal class Options_Date
170+
[Fact]
171+
public static void UnParsing_instance_with_timespan()
172+
{
173+
var ts = new TimeSpan(1,2,3);
174+
var options = new Options_TimeSpan { Start = ts };
175+
var result = new Parser()
176+
.FormatCommandLine(options)
177+
.Should().BeEquivalentTo("--start \"01:02:03\"");
178+
}
179+
180+
[Theory]
181+
[InlineData(false, 0, "")] //default behaviour based on type
182+
[InlineData(false, 1, "-v 1")] //default skip=false
183+
[InlineData(false, 2, "-v 2")]
184+
[InlineData(true, 1, "")] //default skip=true
185+
public static void UnParsing_instance_with_int(bool skipDefault, int value, string expected)
186+
{
187+
var options = new Option_Int { VerboseLevel = value };
188+
var result = new Parser()
189+
.FormatCommandLine(options, x => x.SkipDefault = skipDefault)
190+
.Should().BeEquivalentTo(expected);
191+
192+
}
193+
194+
[Theory]
195+
[InlineData(false, 0, "-v 0")]
196+
[InlineData(false, 1, "-v 1")] //default
197+
[InlineData(false, 2, "-v 2")]
198+
[InlineData(false, null, "")]
199+
[InlineData(true, 1, "")] //default
200+
public static void UnParsing_instance_with_int_nullable(bool skipDefault, int? value, string expected)
201+
{
202+
var options = new Option_Int_Nullable { VerboseLevel = value };
203+
var result = new Parser()
204+
.FormatCommandLine(options, x => x.SkipDefault = skipDefault)
205+
.Should().BeEquivalentTo(expected);
206+
207+
}
208+
[Theory]
209+
[InlineData(Shapes.Circle, "--shape circle")]
210+
[InlineData(Shapes.Square, "--shape square")]
211+
[InlineData(null, "")]
212+
public static void UnParsing_instance_with_nullable_enum(Shapes? shape, string expected)
213+
{
214+
var options = new Option_Nullable_Enum { Shape = shape };
215+
var result = new Parser()
216+
.FormatCommandLine(options)
217+
.Should().BeEquivalentTo(expected);
218+
}
219+
220+
[Theory]
221+
[InlineData(true, "-v True")]
222+
[InlineData(false, "-v False")]
223+
[InlineData(null, "")]
224+
public static void UnParsing_instance_with_nullable_bool(bool? flag, string expected)
225+
{
226+
var options = new Option_Nullable_Bool { Verbose = flag };
227+
var result = new Parser()
228+
.FormatCommandLine(options)
229+
.Should().BeEquivalentTo(expected);
230+
}
231+
class Option_Int_Nullable
232+
{
233+
[Option('v', Default = 1)]
234+
public int? VerboseLevel { get; set; }
235+
}
236+
class Option_Int
237+
{
238+
[Option('v', Default = 1)]
239+
public int VerboseLevel { get; set; }
240+
}
241+
class Option_Nullable_Bool
242+
{
243+
[Option('v')]
244+
public bool? Verbose { get; set; }
245+
}
246+
class Option_Nullable_Enum
247+
{
248+
[Option]
249+
public Shapes? Shape { get; set; }
250+
}
251+
class Options_Date
252+
{
253+
[Option]
254+
public DateTime Start { get; set; }
255+
}
256+
class Options_Date_Nullable
138257
{
139258
[Option]
140259
public DateTime? Start { get; set; }
141260
}
261+
class Options_TimeSpan
262+
{
263+
[Option]
264+
public TimeSpan Start { get; set; }
265+
}
266+
class Options_DateTimeOffset
267+
{
268+
[Option]
269+
public DateTimeOffset Start { get; set; }
270+
}
271+
#endregion
142272
public static IEnumerable<object[]> UnParseData
143273
{
144274
get
@@ -172,15 +302,15 @@ public static IEnumerable<object[]> UnParseDataImmutable
172302
get
173303
{
174304
yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty<int>(), default(bool), default(long)), "" };
175-
yield return new object[] { new Immutable_Simple_Options ("", Enumerable.Empty<int>(), true, default(long) ), "-x" };
176-
yield return new object[] { new Immutable_Simple_Options ("", new[] { 1, 2, 3 }, default(bool), default(long) ), "-i 1 2 3" };
177-
yield return new object[] { new Immutable_Simple_Options ("nospaces", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue nospaces" };
178-
yield return new object[] { new Immutable_Simple_Options (" with spaces ", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \" with spaces \"" };
179-
yield return new object[] { new Immutable_Simple_Options ("with\"quote", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \"with\\\"quote\"" };
180-
yield return new object[] { new Immutable_Simple_Options ("with \"quotes\" spaced", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \"with \\\"quotes\\\" spaced\"" };
181-
yield return new object[] { new Immutable_Simple_Options ("", Enumerable.Empty<int>(), default(bool), 123456789), "123456789" };
182-
yield return new object[] { new Immutable_Simple_Options ("nospaces", new[] { 1, 2, 3 }, true, 123456789), "-i 1 2 3 --stringvalue nospaces -x 123456789" };
183-
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" };
305+
yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty<int>(), true, default(long)), "-x" };
306+
yield return new object[] { new Immutable_Simple_Options("", new[] { 1, 2, 3 }, default(bool), default(long)), "-i 1 2 3" };
307+
yield return new object[] { new Immutable_Simple_Options("nospaces", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue nospaces" };
308+
yield return new object[] { new Immutable_Simple_Options(" with spaces ", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \" with spaces \"" };
309+
yield return new object[] { new Immutable_Simple_Options("with\"quote", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \"with\\\"quote\"" };
310+
yield return new object[] { new Immutable_Simple_Options("with \"quotes\" spaced", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \"with \\\"quotes\\\" spaced\"" };
311+
yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty<int>(), default(bool), 123456789), "123456789" };
312+
yield return new object[] { new Immutable_Simple_Options("nospaces", new[] { 1, 2, 3 }, true, 123456789), "-i 1 2 3 --stringvalue nospaces -x 123456789" };
313+
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" };
184314
}
185315
}
186316

@@ -189,7 +319,7 @@ public static IEnumerable<object[]> UnParseDataHidden
189319
get
190320
{
191321
yield return new object[] { new Hidden_Option { HiddenOption = "hidden" }, true, "--hiddenOption hidden" };
192-
yield return new object[] { new Hidden_Option { HiddenOption = "hidden" }, false, ""};
322+
yield return new object[] { new Hidden_Option { HiddenOption = "hidden" }, false, "" };
193323
}
194324
}
195325
#if !SKIP_FSHARP

0 commit comments

Comments
 (0)