diff --git a/src/CommandLine/UnParserExtensions.cs b/src/CommandLine/UnParserExtensions.cs index 7db948e7..59c4d3b7 100644 --- a/src/CommandLine/UnParserExtensions.cs +++ b/src/CommandLine/UnParserExtensions.cs @@ -19,6 +19,7 @@ public class UnParserSettings private bool groupSwitches; private bool useEqualToken; private bool showHidden; + private bool skipDefault; /// /// Gets or sets a value indicating whether unparsing process shall prefer short or long names. @@ -56,6 +57,14 @@ public bool ShowHidden set { PopsicleSetter.Set(Consumed, ref showHidden, value); } } /// + /// Gets or sets a value indicating whether unparsing process shall skip options with DefaultValue. + /// + public bool SkipDefault + { + get { return skipDefault; } + set { PopsicleSetter.Set(Consumed, ref skipDefault, value); } + } + /// /// Factory method that creates an instance of with GroupSwitches set to true. /// /// A properly initalized instance. @@ -90,7 +99,7 @@ public static class UnParserExtensions /// A string with command line arguments. public static string FormatCommandLine(this Parser parser, T options) { - return parser.FormatCommandLine(options, config => {}); + return parser.FormatCommandLine(options, config => { }); } /// @@ -119,34 +128,38 @@ public static string FormatCommandLine(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( @@ -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 doubQt = v => v.Contains("\"") ? v.Replace("\"", "\\\"") : v; @@ -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 @@ -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 diff --git a/tests/CommandLine.Tests/Fakes/Hidden_Option.cs b/tests/CommandLine.Tests/Fakes/Hidden_Option.cs index 1f18f216..1917ecab 100644 --- a/tests/CommandLine.Tests/Fakes/Hidden_Option.cs +++ b/tests/CommandLine.Tests/Fakes/Hidden_Option.cs @@ -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; } } } diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs b/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs new file mode 100644 index 00000000..eca68790 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Defaults.cs @@ -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; } + } +} + diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs b/tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs index 4e1560b1..e3ede175 100644 --- a/tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs +++ b/tests/CommandLine.Tests/Fakes/Options_With_Enum_Having_HelpText.cs @@ -2,7 +2,7 @@ namespace CommandLine.Tests.Fakes { - enum Shapes + public enum Shapes { Circle, Square, diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 8ff702a5..90cb8929 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -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; @@ -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 UnParseData { get @@ -136,15 +302,15 @@ public static IEnumerable UnParseDataImmutable get { yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty(), default(bool), default(long)), "" }; - yield return new object[] { new Immutable_Simple_Options ("", Enumerable.Empty(), 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(), default(bool), default(long)), "--stringvalue nospaces" }; - yield return new object[] { new Immutable_Simple_Options (" with spaces ", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \" with spaces \"" }; - yield return new object[] { new Immutable_Simple_Options ("with\"quote", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \"with\\\"quote\"" }; - yield return new object[] { new Immutable_Simple_Options ("with \"quotes\" spaced", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \"with \\\"quotes\\\" spaced\"" }; - yield return new object[] { new Immutable_Simple_Options ("", Enumerable.Empty(), 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(), 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(), default(bool), default(long)), "--stringvalue nospaces" }; + yield return new object[] { new Immutable_Simple_Options(" with spaces ", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \" with spaces \"" }; + yield return new object[] { new Immutable_Simple_Options("with\"quote", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \"with\\\"quote\"" }; + yield return new object[] { new Immutable_Simple_Options("with \"quotes\" spaced", Enumerable.Empty(), default(bool), default(long)), "--stringvalue \"with \\\"quotes\\\" spaced\"" }; + yield return new object[] { new Immutable_Simple_Options("", Enumerable.Empty(), 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" }; } } @@ -153,7 +319,7 @@ public static IEnumerable 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