Skip to content

Commit f184e77

Browse files
authored
Merge pull request #550 from moh-hassan/UnParser
Add ability to skip options with default values for UnParserSettings (#541) and Quote DateTime (#502)
2 parents ec1bab1 + a56720f commit f184e77

File tree

5 files changed

+242
-32
lines changed

5 files changed

+242
-32
lines changed

src/CommandLine/UnParserExtensions.cs

+38-20
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class UnParserSettings
1919
private bool groupSwitches;
2020
private bool useEqualToken;
2121
private bool showHidden;
22+
private bool skipDefault;
2223

2324
/// <summary>
2425
/// Gets or sets a value indicating whether unparsing process shall prefer short or long names.
@@ -56,6 +57,14 @@ public bool ShowHidden
5657
set { PopsicleSetter.Set(Consumed, ref showHidden, value); }
5758
}
5859
/// <summary>
60+
/// Gets or sets a value indicating whether unparsing process shall skip options with DefaultValue.
61+
/// </summary>
62+
public bool SkipDefault
63+
{
64+
get { return skipDefault; }
65+
set { PopsicleSetter.Set(Consumed, ref skipDefault, value); }
66+
}
67+
/// <summary>
5968
/// Factory method that creates an instance of <see cref="CommandLine.UnParserSettings"/> with GroupSwitches set to true.
6069
/// </summary>
6170
/// <returns>A properly initalized <see cref="CommandLine.UnParserSettings"/> instance.</returns>
@@ -90,7 +99,7 @@ public static class UnParserExtensions
9099
/// <returns>A string with command line arguments.</returns>
91100
public static string FormatCommandLine<T>(this Parser parser, T options)
92101
{
93-
return parser.FormatCommandLine(options, config => {});
102+
return parser.FormatCommandLine(options, config => { });
94103
}
95104

96105
/// <summary>
@@ -119,34 +128,38 @@ public static string FormatCommandLine<T>(this Parser parser, T options, Action<
119128
var specs =
120129
(from info in
121130
type.GetSpecifications(
122-
pi => new { Specification = Specification.FromProperty(pi),
123-
Value = pi.GetValue(options, null).NormalizeValue(), PropertyValue = pi.GetValue(options, null) })
124-
where !info.PropertyValue.IsEmpty()
125-
select info)
131+
pi => new
132+
{
133+
Specification = Specification.FromProperty(pi),
134+
Value = pi.GetValue(options, null).NormalizeValue(),
135+
PropertyValue = pi.GetValue(options, null)
136+
})
137+
where !info.PropertyValue.IsEmpty(info.Specification, settings.SkipDefault)
138+
select info)
126139
.Memorize();
127140

128141
var allOptSpecs = from info in specs.Where(i => i.Specification.Tag == SpecificationType.Option)
129-
let o = (OptionSpecification)info.Specification
130-
where o.TargetType != TargetType.Switch || (o.TargetType == TargetType.Switch && ((bool)info.Value))
131-
where !o.Hidden || settings.ShowHidden
132-
orderby o.UniqueName()
133-
select info;
142+
let o = (OptionSpecification)info.Specification
143+
where o.TargetType != TargetType.Switch || (o.TargetType == TargetType.Switch && ((bool)info.Value))
144+
where !o.Hidden || settings.ShowHidden
145+
orderby o.UniqueName()
146+
select info;
134147

135148
var shortSwitches = from info in allOptSpecs
136-
let o = (OptionSpecification)info.Specification
137-
where o.TargetType == TargetType.Switch
138-
where o.ShortName.Length > 0
139-
orderby o.UniqueName()
140-
select info;
149+
let o = (OptionSpecification)info.Specification
150+
where o.TargetType == TargetType.Switch
151+
where o.ShortName.Length > 0
152+
orderby o.UniqueName()
153+
select info;
141154

142155
var optSpecs = settings.GroupSwitches
143156
? allOptSpecs.Where(info => !shortSwitches.Contains(info))
144157
: allOptSpecs;
145158

146159
var valSpecs = from info in specs.Where(i => i.Specification.Tag == SpecificationType.Value)
147-
let v = (ValueSpecification)info.Specification
148-
orderby v.Index
149-
select info;
160+
let v = (ValueSpecification)info.Specification
161+
orderby v.Index
162+
select info;
150163

151164
builder = settings.GroupSwitches && shortSwitches.Any()
152165
? builder.Append('-').Append(string.Join(string.Empty, shortSwitches.Select(
@@ -191,6 +204,7 @@ private static string FormatValue(Specification spec, object value)
191204

192205
private static object FormatWithQuotesIfString(object value)
193206
{
207+
if (value is DateTime || value is TimeSpan || value is DateTimeOffset) return $"\"{value}\"";
194208
Func<string, string> doubQt = v
195209
=> v.Contains("\"") ? v.Replace("\"", "\\\"") : v;
196210

@@ -218,7 +232,7 @@ private static string FormatName(this OptionSpecification optionSpec, UnParserSe
218232
{
219233
// Have a long name and short name not preferred? Go with long!
220234
// No short name? Has to be long!
221-
var longName = (optionSpec.LongName.Length > 0 && !settings.PreferShortName)
235+
var longName = (optionSpec.LongName.Length > 0 && !settings.PreferShortName)
222236
|| optionSpec.ShortName.Length == 0;
223237

224238
return
@@ -242,9 +256,13 @@ private static object NormalizeValue(this object value)
242256
return value;
243257
}
244258

245-
private static bool IsEmpty(this object value)
259+
private static bool IsEmpty(this object value, Specification specification, bool skipDefault)
246260
{
247261
if (value == null) return true;
262+
263+
if (skipDefault && value.Equals(specification.DefaultValue.FromJust())) return true;
264+
if (Nullable.GetUnderlyingType(specification.ConversionType) != null) return false; //nullable
265+
248266
#if !SKIP_FSHARP
249267
if (ReflectionHelper.IsFSharpOptionType(value.GetType()) && !FSharpOptionHelper.IsSome(value)) return true;
250268
#endif

tests/CommandLine.Tests/Fakes/Hidden_Option.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace CommandLine.Tests.Fakes
88
{
99
public class Hidden_Option
1010
{
11-
[Option('h', "hiddenOption", Default="hidden", Hidden = true)]
11+
[Option('h', "hiddenOption", Hidden = true)]
1212
public string HiddenOption { get; set; }
1313
}
1414
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace CommandLine.Tests.Fakes
2+
{
3+
class Options_With_Defaults
4+
{
5+
[Option(Default = 99)]
6+
public int P1 { get; set; }
7+
[Option()]
8+
public string P2 { get; set; }
9+
[Option(Default = 88)]
10+
public int P3 { get; set; }
11+
[Option(Default = Shapes.Square)]
12+
public Shapes P4 { get; set; }
13+
}
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+
}
25+
}
26+

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

+176-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information.
22

3+
using System;
34
using System.Collections.Generic;
45
using System.Linq;
6+
using System.Text.RegularExpressions;
57
using CommandLine.Tests.Fakes;
68
using Xunit;
79
using FluentAssertions;
@@ -103,6 +105,170 @@ public static void UnParsing_instance_with_dash_in_value_and_dashdash_disabled_r
103105
.Should().BeEquivalentTo("-something with dash");
104106
}
105107

108+
#region PR 550
109+
110+
[Fact]
111+
public static void UnParsing_instance_with_default_values_when_skip_default_is_false()
112+
{
113+
var options = new Options_With_Defaults { P2 = "xyz", P1 = 99, P3 = 88, P4 = Shapes.Square };
114+
new Parser()
115+
.FormatCommandLine(options)
116+
.Should().BeEquivalentTo("--p1 99 --p2 xyz --p3 88 --p4 Square");
117+
}
118+
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)
123+
{
124+
var options = new Options_With_Defaults { P2 = "xyz", P1 = 99, P3 = 88, P4 = Shapes.Square };
125+
new Parser()
126+
.FormatCommandLine(options, x => x.SkipDefault = skipDefault)
127+
.Should().BeEquivalentTo(expected);
128+
}
129+
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+
}
140+
[Fact]
141+
public static void UnParsing_instance_with_datetime()
142+
{
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 };
165+
var result = new Parser()
166+
.FormatCommandLine(options)
167+
.Should().MatchRegex("--start\\s\".+\"");
168+
}
169+
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
257+
{
258+
[Option]
259+
public DateTime? Start { get; set; }
260+
}
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
106272
public static IEnumerable<object[]> UnParseData
107273
{
108274
get
@@ -136,15 +302,15 @@ public static IEnumerable<object[]> UnParseDataImmutable
136302
get
137303
{
138304
yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty<int>(), default(bool), default(long)), "" };
139-
yield return new object[] { new Immutable_Simple_Options ("", Enumerable.Empty<int>(), true, default(long) ), "-x" };
140-
yield return new object[] { new Immutable_Simple_Options ("", new[] { 1, 2, 3 }, default(bool), default(long) ), "-i 1 2 3" };
141-
yield return new object[] { new Immutable_Simple_Options ("nospaces", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue nospaces" };
142-
yield return new object[] { new Immutable_Simple_Options (" with spaces ", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \" with spaces \"" };
143-
yield return new object[] { new Immutable_Simple_Options ("with\"quote", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \"with\\\"quote\"" };
144-
yield return new object[] { new Immutable_Simple_Options ("with \"quotes\" spaced", Enumerable.Empty<int>(), default(bool), default(long)), "--stringvalue \"with \\\"quotes\\\" spaced\"" };
145-
yield return new object[] { new Immutable_Simple_Options ("", Enumerable.Empty<int>(), default(bool), 123456789), "123456789" };
146-
yield return new object[] { new Immutable_Simple_Options ("nospaces", new[] { 1, 2, 3 }, true, 123456789), "-i 1 2 3 --stringvalue nospaces -x 123456789" };
147-
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" };
148314
}
149315
}
150316

@@ -153,7 +319,7 @@ public static IEnumerable<object[]> UnParseDataHidden
153319
get
154320
{
155321
yield return new object[] { new Hidden_Option { HiddenOption = "hidden" }, true, "--hiddenOption hidden" };
156-
yield return new object[] { new Hidden_Option { HiddenOption = "hidden" }, false, ""};
322+
yield return new object[] { new Hidden_Option { HiddenOption = "hidden" }, false, "" };
157323
}
158324
}
159325
#if !SKIP_FSHARP

0 commit comments

Comments
 (0)