Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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