Skip to content

Commit

Permalink
Merge pull request #550 from moh-hassan/UnParser
Browse files Browse the repository at this point in the history
Add ability to skip options with default values for UnParserSettings (#541) and Quote DateTime (#502)
  • Loading branch information
moh-hassan authored Dec 24, 2019
2 parents ec1bab1 + a56720f commit f184e77
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 32 deletions.
58 changes: 38 additions & 20 deletions src/CommandLine/UnParserExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class UnParserSettings
private bool groupSwitches;
private bool useEqualToken;
private bool showHidden;
private bool skipDefault;

/// <summary>
/// Gets or sets a value indicating whether unparsing process shall prefer short or long names.
Expand Down Expand Up @@ -56,6 +57,14 @@ public bool ShowHidden
set { PopsicleSetter.Set(Consumed, ref showHidden, value); }
}
/// <summary>
/// Gets or sets a value indicating whether unparsing process shall skip options with DefaultValue.
/// </summary>
public bool SkipDefault
{
get { return skipDefault; }
set { PopsicleSetter.Set(Consumed, ref skipDefault, value); }
}
/// <summary>
/// Factory method that creates an instance of <see cref="CommandLine.UnParserSettings"/> with GroupSwitches set to true.
/// </summary>
/// <returns>A properly initalized <see cref="CommandLine.UnParserSettings"/> instance.</returns>
Expand Down Expand Up @@ -90,7 +99,7 @@ public static class UnParserExtensions
/// <returns>A string with command line arguments.</returns>
public static string FormatCommandLine<T>(this Parser parser, T options)
{
return parser.FormatCommandLine(options, config => {});
return parser.FormatCommandLine(options, config => { });
}

/// <summary>
Expand Down Expand Up @@ -119,34 +128,38 @@ public static string FormatCommandLine<T>(this Parser parser, T options, Action<
var specs =
(from info in
type.GetSpecifications(
pi => new { Specification = Specification.FromProperty(pi),
Value = pi.GetValue(options, null).NormalizeValue(), PropertyValue = pi.GetValue(options, null) })
where !info.PropertyValue.IsEmpty()
select info)
pi => new
{
Specification = Specification.FromProperty(pi),
Value = pi.GetValue(options, null).NormalizeValue(),
PropertyValue = pi.GetValue(options, null)
})
where !info.PropertyValue.IsEmpty(info.Specification, settings.SkipDefault)
select info)
.Memorize();

var allOptSpecs = from info in specs.Where(i => i.Specification.Tag == SpecificationType.Option)
let o = (OptionSpecification)info.Specification
where o.TargetType != TargetType.Switch || (o.TargetType == TargetType.Switch && ((bool)info.Value))
where !o.Hidden || settings.ShowHidden
orderby o.UniqueName()
select info;
let o = (OptionSpecification)info.Specification
where o.TargetType != TargetType.Switch || (o.TargetType == TargetType.Switch && ((bool)info.Value))
where !o.Hidden || settings.ShowHidden
orderby o.UniqueName()
select info;

var shortSwitches = from info in allOptSpecs
let o = (OptionSpecification)info.Specification
where o.TargetType == TargetType.Switch
where o.ShortName.Length > 0
orderby o.UniqueName()
select info;
let o = (OptionSpecification)info.Specification
where o.TargetType == TargetType.Switch
where o.ShortName.Length > 0
orderby o.UniqueName()
select info;

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

var valSpecs = from info in specs.Where(i => i.Specification.Tag == SpecificationType.Value)
let v = (ValueSpecification)info.Specification
orderby v.Index
select info;
let v = (ValueSpecification)info.Specification
orderby v.Index
select info;

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

private static object FormatWithQuotesIfString(object 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 @@ -218,7 +232,7 @@ private static string FormatName(this OptionSpecification optionSpec, UnParserSe
{
// Have a long name and short name not preferred? Go with long!
// No short name? Has to be long!
var longName = (optionSpec.LongName.Length > 0 && !settings.PreferShortName)
var longName = (optionSpec.LongName.Length > 0 && !settings.PreferShortName)
|| optionSpec.ShortName.Length == 0;

return
Expand All @@ -242,9 +256,13 @@ private static object NormalizeValue(this object value)
return value;
}

private static bool IsEmpty(this object value)
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
2 changes: 1 addition & 1 deletion tests/CommandLine.Tests/Fakes/Hidden_Option.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace CommandLine.Tests.Fakes
{
public class Hidden_Option
{
[Option('h', "hiddenOption", Default="hidden", Hidden = true)]
[Option('h', "hiddenOption", Hidden = true)]
public string HiddenOption { get; set; }
}
}
26 changes: 26 additions & 0 deletions tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace CommandLine.Tests.Fakes
{
class 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; }
}
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
186 changes: 176 additions & 10 deletions tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using CommandLine.Tests.Fakes;
using Xunit;
using FluentAssertions;
Expand Down Expand Up @@ -103,6 +105,170 @@ 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()
{
var options = new Options_With_Defaults { P2 = "xyz", P1 = 99, P3 = 88, P4 = Shapes.Square };
new Parser()
.FormatCommandLine(options)
.Should().BeEquivalentTo("--p1 99 --p2 xyz --p3 88 --p4 Square");
}

[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 };
new Parser()
.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 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)
.Should().MatchRegex("--start\\s\".+\"");
}

[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 @@ -136,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 @@ -153,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 f184e77

Please sign in to comment.