From 465cbd0a84847368e122c93beb0817b3336f1899 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 8 Dec 2019 13:29:09 +0200 Subject: [PATCH 01/14] Introduce option groups When one or more options has group set, at least one of these properties should have set value (they behave as required) --- src/CommandLine/Core/OptionSpecification.cs | 16 +++-- .../Core/SpecificationExtensions.cs | 1 + .../Core/SpecificationPropertyRules.cs | 71 +++++++++++++------ src/CommandLine/Error.cs | 22 +++++- src/CommandLine/GroupAttribute.cs | 15 ++++ src/CommandLine/OptionAttribute.cs | 14 +++- .../Unit/Core/NameLookupTests.cs | 14 ++-- .../Unit/Core/OptionMapperTests.cs | 6 +- .../Unit/Core/TokenPartitionerTests.cs | 23 +++--- .../Unit/Core/TokenizerTests.cs | 22 +++--- 10 files changed, 149 insertions(+), 55 deletions(-) create mode 100644 src/CommandLine/GroupAttribute.cs diff --git a/src/CommandLine/Core/OptionSpecification.cs b/src/CommandLine/Core/OptionSpecification.cs index 0bbbbb06..45dc2f58 100644 --- a/src/CommandLine/Core/OptionSpecification.cs +++ b/src/CommandLine/Core/OptionSpecification.cs @@ -1,4 +1,4 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +// 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; @@ -13,16 +13,18 @@ sealed class OptionSpecification : Specification private readonly string longName; private readonly char separator; private readonly string setName; + private readonly Maybe group; public OptionSpecification(string shortName, string longName, bool required, string setName, Maybe min, Maybe max, char separator, Maybe defaultValue, string helpText, string metaValue, IEnumerable enumValues, - Type conversionType, TargetType targetType, bool hidden = false) + Type conversionType, TargetType targetType, Maybe group, bool hidden = false) : base(SpecificationType.Option, required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden) { this.shortName = shortName; this.longName = longName; this.separator = separator; this.setName = setName; + this.group = group; } public static OptionSpecification FromAttribute(OptionAttribute attribute, Type conversionType, IEnumerable enumValues) @@ -41,13 +43,14 @@ public static OptionSpecification FromAttribute(OptionAttribute attribute, Type enumValues, conversionType, conversionType.ToTargetType(), + string.IsNullOrWhiteSpace(attribute.Group) ? Maybe.Nothing() : Maybe.Just(attribute.Group), attribute.Hidden); } public static OptionSpecification NewSwitch(string shortName, string longName, bool required, string helpText, string metaValue, bool hidden = false) { return new OptionSpecification(shortName, longName, required, string.Empty, Maybe.Nothing(), Maybe.Nothing(), - '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, hidden); + '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, Maybe.Nothing(), hidden); } public string ShortName @@ -69,5 +72,10 @@ public string SetName { get { return setName; } } + + public Maybe Group + { + get { return group; } + } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Core/SpecificationExtensions.cs b/src/CommandLine/Core/SpecificationExtensions.cs index 5f77a5dd..e223e987 100644 --- a/src/CommandLine/Core/SpecificationExtensions.cs +++ b/src/CommandLine/Core/SpecificationExtensions.cs @@ -34,6 +34,7 @@ public static OptionSpecification WithLongName(this OptionSpecification specific specification.EnumValues, specification.ConversionType, specification.TargetType, + specification.Group, specification.Hidden); } diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index 71145e8a..c6a5ca50 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -1,4 +1,4 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +// 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; @@ -16,12 +16,43 @@ public static IEnumerable, IEnumerable, IEnumerable>> { EnforceMutuallyExclusiveSet(), + EnforceGroup(), EnforceRequired(), EnforceRange(), EnforceSingle(tokens) }; } + private static Func, IEnumerable> EnforceGroup() + { + return specProps => + { + var optionsValues = + from sp in specProps + where sp.Specification.IsOption() + let o = (OptionSpecification)sp.Specification + where o.Group.IsJust() + select new + { + Option = o, + Value = sp.Value + }; + + var groups = from o in optionsValues + group o by o.Option.Group.GetValueOrDefault(null) into g + select g; + + var errorGroups = groups.Where(gr => gr.All(g => g.Value.IsNothing())); + + if (errorGroups.Any()) + { + return errorGroups.Select(gr => new MissingGroupOptionError(gr.Key)); + } + + return Enumerable.Empty(); + }; + } + private static Func, IEnumerable> EnforceMutuallyExclusiveSet() { return specProps => @@ -51,12 +82,12 @@ private static Func, IEnumerable> Enfo return specProps => { var requiredWithValue = from sp in specProps - where sp.Specification.IsOption() - where sp.Specification.Required - where sp.Value.IsJust() - let o = (OptionSpecification)sp.Specification - where o.SetName.Length > 0 - select sp.Specification; + where sp.Specification.IsOption() + where sp.Specification.Required + where sp.Value.IsJust() + let o = (OptionSpecification)sp.Specification + where o.SetName.Length > 0 + select sp.Specification; var setWithRequiredValue = ( from s in requiredWithValue let o = (OptionSpecification)s @@ -64,13 +95,13 @@ where o.SetName.Length > 0 select o.SetName) .Distinct(); var requiredWithoutValue = from sp in specProps - where sp.Specification.IsOption() - where sp.Specification.Required - where sp.Value.IsNothing() - let o = (OptionSpecification)sp.Specification - where o.SetName.Length > 0 - where setWithRequiredValue.ContainsIfNotEmpty(o.SetName) - select sp.Specification; + where sp.Specification.IsOption() + where sp.Specification.Required + where sp.Value.IsNothing() + let o = (OptionSpecification)sp.Specification + where o.SetName.Length > 0 + where setWithRequiredValue.ContainsIfNotEmpty(o.SetName) + select sp.Specification; var missing = requiredWithoutValue .Except(requiredWithValue) @@ -130,11 +161,11 @@ from o in to.DefaultIfEmpty() where o != null select new { o.ShortName, o.LongName }; var longOptions = from t in tokens - where t.IsName() - join o in specs on t.Text equals o.LongName into to - from o in to.DefaultIfEmpty() - where o != null - select new { o.ShortName, o.LongName }; + where t.IsName() + join o in specs on t.Text equals o.LongName into to + from o in to.DefaultIfEmpty() + where o != null + select new { o.ShortName, o.LongName }; var groups = from x in shortOptions.Concat(longOptions) group x by x into g let count = g.Count() @@ -155,4 +186,4 @@ private static bool ContainsIfNotEmpty(this IEnumerable sequence, T value) return true; } } -} \ No newline at end of file +} diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index 2f208dec..d32ee293 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -210,7 +210,7 @@ public override bool Equals(object obj) /// A hash code for the current . public override int GetHashCode() { - return new {Tag, StopsProcessing, Token}.GetHashCode(); + return new { Tag, StopsProcessing, Token }.GetHashCode(); } /// @@ -289,7 +289,7 @@ public override bool Equals(object obj) /// A hash code for the current . public override int GetHashCode() { - return new {Tag, StopsProcessing, NameInfo}.GetHashCode(); + return new { Tag, StopsProcessing, NameInfo }.GetHashCode(); } /// @@ -526,4 +526,22 @@ internal InvalidAttributeConfigurationError() { } } + + public sealed class MissingGroupOptionError : Error + { + public const string ErrorMessage = "At least one option in a group must have value."; + + private readonly string group; + + internal MissingGroupOptionError(string group) + : base(ErrorType.HelpRequestedError, true) + { + this.group = group; + } + + public string Group + { + get { return group; } + } + } } diff --git a/src/CommandLine/GroupAttribute.cs b/src/CommandLine/GroupAttribute.cs new file mode 100644 index 00000000..82832d14 --- /dev/null +++ b/src/CommandLine/GroupAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace CommandLine +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class GroupAttribute : Attribute + { + public GroupAttribute(string name) + { + Name = name; + } + + public string Name { get; } + } +} diff --git a/src/CommandLine/OptionAttribute.cs b/src/CommandLine/OptionAttribute.cs index 8ef6d63d..966f5baa 100644 --- a/src/CommandLine/OptionAttribute.cs +++ b/src/CommandLine/OptionAttribute.cs @@ -1,8 +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 CommandLine.Infrastructure; +using System; + namespace CommandLine { /// @@ -15,6 +16,7 @@ public sealed class OptionAttribute : BaseAttribute private readonly string shortName; private string setName; private char separator; + private string group; private OptionAttribute(string shortName, string longName) : base() { @@ -100,8 +102,14 @@ public string SetName /// public char Separator { - get { return separator ; } + get { return separator; } set { separator = value; } } + + public string Group + { + get { return group; } + set { group = value; } + } } -} \ No newline at end of file +} diff --git a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs index f27e033c..caa0a386 100644 --- a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs @@ -1,11 +1,15 @@ // 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 CommandLine.Core; + +using CSharpx; + using FluentAssertions; + +using System; +using System.Collections.Generic; + using Xunit; -using CSharpx; namespace CommandLine.Tests.Unit.Core { @@ -17,7 +21,7 @@ public void Lookup_name_of_sequence_option_with_separator() // Fixture setup var expected = Maybe.Just("."); var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence)}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing())}; // Exercize system var result = NameLookup.HavingSeparator("string-seq", specs, StringComparer.Ordinal); @@ -35,7 +39,7 @@ public void Get_name_from_option_specification() // Fixture setup var expected = new NameInfo(ShortName, LongName); - var spec = new OptionSpecification(ShortName, LongName, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence); + var spec = new OptionSpecification(ShortName, LongName, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing()); // Exercize system var result = spec.FromOptionSpecification(); diff --git a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs index 460dac3b..41b93bf5 100644 --- a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs @@ -4,13 +4,17 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; + #if PLATFORM_DOTNET using System.Reflection; #endif using CommandLine.Core; using CommandLine.Tests.Fakes; + using Xunit; + using CSharpx; + using RailwaySharp.ErrorHandling; namespace CommandLine.Tests.Unit.Core @@ -28,7 +32,7 @@ public void Map_boolean_switch_creates_boolean_value() var specProps = new[] { SpecificationProperty.Create( - new OptionSpecification("x", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(bool), TargetType.Switch), + new OptionSpecification("x", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(bool), TargetType.Switch, Maybe.Nothing()), typeof(Simple_Options).GetProperties().Single(p => p.Name.Equals("BoolValue", StringComparison.Ordinal)), Maybe.Nothing()) }; diff --git a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs index d787645d..4266b2d4 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs @@ -1,10 +1,13 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using CommandLine.Core; + +using CSharpx; + using System; using System.Collections.Generic; using System.Linq; -using CommandLine.Core; -using CSharpx; + using Xunit; namespace CommandLine.Tests.Unit.Core @@ -17,12 +20,12 @@ public void Partition_sequence_returns_sequence() // Fixture setup var expectedSequence = new[] { - new KeyValuePair>("i", new[] {"10", "20", "30", "40"}) + new KeyValuePair>("i", new[] {"10", "20", "30", "40"}) }; - var specs =new[] + var specs = new[] { - new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar), - new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence) + new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, Maybe.Nothing()), + new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing()) }; // Exercize system @@ -44,12 +47,12 @@ public void Partition_sequence_returns_sequence_with_duplicates() // Fixture setup var expectedSequence = new[] { - new KeyValuePair>("i", new[] {"10", "10", "30", "40"}) + new KeyValuePair>("i", new[] {"10", "10", "30", "40"}) }; - var specs =new[] + var specs = new[] { - new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar), - new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence) + new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, Maybe.Nothing()), + new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing()) }; // Exercize system diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index ecb21266..ac037cde 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -1,18 +1,20 @@ // 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 CommandLine.Core; using CommandLine.Infrastructure; -using Xunit; using CSharpx; using FluentAssertions; using RailwaySharp.ErrorHandling; +using System; +using System.Collections.Generic; +using System.Linq; + +using Xunit; + namespace CommandLine.Tests.Unit.Core { public class TokenizerTests @@ -24,7 +26,7 @@ public void Explode_scalar_with_separator_in_odd_args_input_returns_sequence() var expectedTokens = new[] { Token.Name("i"), Token.Value("10"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), Token.Value("cccc"), Token.Name("switch") }; var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence)}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing())}; // Exercize system var result = @@ -47,7 +49,7 @@ public void Explode_scalar_with_separator_in_even_args_input_returns_sequence() var expectedTokens = new[] { Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), Token.Value("cccc"), Token.Name("switch") }; var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence)}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing())}; // Exercize system var result = @@ -77,14 +79,14 @@ public void Normalize_should_remove_all_value_with_explicit_assignment_of_existi // Exercize system var result = Tokenizer.Normalize( - //Result.Succeed( + //Result.Succeed( Enumerable.Empty() .Concat( new[] { Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), Token.Name("unknown"), Token.Value("value0", true), Token.Name("switch") }) - //,Enumerable.Empty()), - ,nameLookup); + //,Enumerable.Empty()), + , nameLookup); // Verify outcome result.Should().BeEquivalentTo(expectedTokens); @@ -127,5 +129,5 @@ public void Should_return_error_if_option_format_with_equals_is_not_correct() Assert.Equal(ErrorType.BadFormatTokenError, tokens.Last().Tag); } } - + } From f442e9aaefcd06fc36598f280605448c66f61442 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 8 Dec 2019 14:24:54 +0200 Subject: [PATCH 02/14] Add help text on error --- .../Core/SpecificationPropertyRules.cs | 5 +++-- src/CommandLine/Error.cs | 18 ++++++++++++--- src/CommandLine/Text/SentenceBuilder.cs | 22 +++++++++++++------ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index c6a5ca50..d44b29c6 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -1,9 +1,10 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using CSharpx; + using System; using System.Collections.Generic; using System.Linq; -using CSharpx; namespace CommandLine.Core { @@ -46,7 +47,7 @@ group o by o.Option.Group.GetValueOrDefault(null) into g if (errorGroups.Any()) { - return errorGroups.Select(gr => new MissingGroupOptionError(gr.Key)); + return errorGroups.Select(gr => new MissingGroupOptionError(gr.Key, gr.Select(g => new NameInfo(g.Option.ShortName, g.Option.LongName)))); } return Enumerable.Empty(); diff --git a/src/CommandLine/Error.cs b/src/CommandLine/Error.cs index d32ee293..e54dbf6a 100644 --- a/src/CommandLine/Error.cs +++ b/src/CommandLine/Error.cs @@ -1,6 +1,7 @@ // 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; namespace CommandLine { @@ -69,7 +70,11 @@ public enum ErrorType /// /// Value of type. /// - InvalidAttributeConfigurationError + InvalidAttributeConfigurationError, + /// + /// Value of type. + /// + MissingGroupOptionError } @@ -532,16 +537,23 @@ public sealed class MissingGroupOptionError : Error public const string ErrorMessage = "At least one option in a group must have value."; private readonly string group; + private readonly IEnumerable names; - internal MissingGroupOptionError(string group) - : base(ErrorType.HelpRequestedError, true) + internal MissingGroupOptionError(string group, IEnumerable names) + : base(ErrorType.MissingGroupOptionError) { this.group = group; + this.names = names; } public string Group { get { return group; } } + + public IEnumerable Names + { + get { return names; } + } } } diff --git a/src/CommandLine/Text/SentenceBuilder.cs b/src/CommandLine/Text/SentenceBuilder.cs index 1c150b67..cdcf409c 100644 --- a/src/CommandLine/Text/SentenceBuilder.cs +++ b/src/CommandLine/Text/SentenceBuilder.cs @@ -1,10 +1,11 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using CommandLine.Infrastructure; + using System; using System.Collections.Generic; using System.Linq; using System.Text; -using CommandLine.Infrastructure; namespace CommandLine.Text { @@ -41,7 +42,7 @@ public static SentenceBuilder Create() /// /// Gets a delegate that returns usage text block heading text. /// - public abstract Func UsageHeadingText { get; } + public abstract Func UsageHeadingText { get; } /// /// Get a delegate that returns the help text of help command. @@ -53,7 +54,7 @@ public static SentenceBuilder Create() /// Get a delegate that returns the help text of vesion command. /// The delegates must accept a boolean that is equal true for options; otherwise false for verbs. /// - public abstract Func VersionCommandText { get; } + public abstract Func VersionCommandText { get; } /// /// Gets a delegate that handles singular error formatting. @@ -67,7 +68,7 @@ public static SentenceBuilder Create() /// public abstract Func, string> FormatMutuallyExclusiveSetErrors { get; } - private class DefaultSentenceBuilder : SentenceBuilder + private class DefaultSentenceBuilder : SentenceBuilder { public override Func RequiredWord { @@ -140,6 +141,13 @@ public override Func FormatError case ErrorType.SetValueExceptionError: var setValueError = (SetValueExceptionError)error; return "Error setting value to option '".JoinTo(setValueError.NameInfo.NameText, "': ", setValueError.Exception.Message); + case ErrorType.MissingGroupOptionError: + var missingGroupOptionError = (MissingGroupOptionError)error; + return "At least one option from group '".JoinTo( + missingGroupOptionError.Group, + "' (", + string.Join(", ", missingGroupOptionError.Names.Select(n => n.NameText)), + ") is required."); } throw new InvalidOperationException(); }; @@ -153,8 +161,8 @@ public override Func, string> FormatMutua return errors => { var bySet = from e in errors - group e by e.SetName into g - select new { SetName = g.Key, Errors = g.ToList() }; + group e by e.SetName into g + select new { SetName = g.Key, Errors = g.ToList() }; var msgs = bySet.Select( set => @@ -169,7 +177,7 @@ group e by e.SetName into g (from x in (from s in bySet where !s.SetName.Equals(set.SetName) from e in s.Errors select e) .Distinct() - select "'".JoinTo(x.NameInfo.NameText, "', ")).ToArray()); + select "'".JoinTo(x.NameInfo.NameText, "', ")).ToArray()); return new StringBuilder("Option") From 1fe68f0d531ead6c3b5ed2aa0958721e5daed2eb Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 8 Dec 2019 14:46:13 +0200 Subject: [PATCH 03/14] Add general help text. Display group name in option details --- src/CommandLine/OptionAttribute.cs | 3 + src/CommandLine/Text/HelpText.cs | 134 +++++++++++++----------- src/CommandLine/Text/SentenceBuilder.cs | 10 ++ 3 files changed, 86 insertions(+), 61 deletions(-) diff --git a/src/CommandLine/OptionAttribute.cs b/src/CommandLine/OptionAttribute.cs index 966f5baa..ce2e1fae 100644 --- a/src/CommandLine/OptionAttribute.cs +++ b/src/CommandLine/OptionAttribute.cs @@ -106,6 +106,9 @@ public char Separator set { separator = value; } } + /// + /// Gets or sets the option group name. When one or more options are grouped, at least one of them should have value. + /// public string Group { get { return group; } diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index b23bb804..a67e43dd 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -1,15 +1,17 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +using CommandLine.Core; +using CommandLine.Infrastructure; + +using CSharpx; + using System; using System.Collections; using System.Collections.Generic; using System.IO; -using System.Text; using System.Linq; using System.Reflection; -using CommandLine.Infrastructure; -using CommandLine.Core; -using CSharpx; +using System.Text; namespace CommandLine.Text { @@ -17,9 +19,9 @@ namespace CommandLine.Text /// Provides means to format an help screen. /// You can assign it in place of a instance. /// - - - + + + public struct ComparableOption { public bool Required; @@ -29,7 +31,7 @@ public struct ComparableOption public string ShortName; public int Index; } - + public class HelpText { @@ -55,40 +57,40 @@ ComparableOption ToComparableOption(Specification spec, int index) public Comparison OptionComparison { get; set; } = null; - public static Comparison RequiredThenAlphaComparison = (ComparableOption attr1, ComparableOption attr2) => - { - if (attr1.IsOption && attr2.IsOption) - { - if (attr1.Required && !attr2.Required) - { - return -1; - } - else if (!attr1.Required && attr2.Required) - { - return 1; - } - - return String.Compare(attr1.LongName, attr2.LongName, StringComparison.Ordinal); - - } - else if (attr1.IsOption && attr2.IsValue) - { - return -1; - } - else - { - return 1; - } - }; - + public static Comparison RequiredThenAlphaComparison = (ComparableOption attr1, ComparableOption attr2) => + { + if (attr1.IsOption && attr2.IsOption) + { + if (attr1.Required && !attr2.Required) + { + return -1; + } + else if (!attr1.Required && attr2.Required) + { + return 1; + } + + return String.Compare(attr1.LongName, attr2.LongName, StringComparison.Ordinal); + + } + else if (attr1.IsOption && attr2.IsValue) + { + return -1; + } + else + { + return 1; + } + }; + #endregion - + private const int BuilderCapacity = 128; private const int DefaultMaximumLength = 80; // default console width /// /// The number of spaces between an option and its associated help text /// - private const int OptionToHelpTextSeparatorWidth = 4; + private const int OptionToHelpTextSeparatorWidth = 4; /// /// The width of the option prefix (either "--" or " " /// @@ -334,7 +336,7 @@ public static HelpText AutoBuild( var errors = Enumerable.Empty(); - + if (onError != null && parserResult.Tag == ParserResultType.NotParsed) { errors = ((NotParsed)parserResult).Errors; @@ -392,7 +394,7 @@ public static HelpText AutoBuild(ParserResult parserResult, int maxDisplay var errors = ((NotParsed)parserResult).Errors; if (errors.Any(e => e.Tag == ErrorType.VersionRequestedError)) - return new HelpText($"{HeadingInfo.Default}{Environment.NewLine}"){MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine); + return new HelpText($"{HeadingInfo.Default}{Environment.NewLine}") { MaximumDisplayWidth = maxDisplayWidth }.AddPreOptionsLine(Environment.NewLine); if (!errors.Any(e => e.Tag == ErrorType.HelpVerbRequestedError)) return AutoBuild(parserResult, current => DefaultParsingErrorsHandler(parserResult, current), e => e, maxDisplayWidth: maxDisplayWidth); @@ -520,6 +522,7 @@ public HelpText AddOptions(ParserResult result) return AddOptionsImpl( GetSpecificationsFromType(result.TypeInfo.Current), SentenceBuilder.RequiredWord(), + SentenceBuilder.OptionGroupWord(), MaximumDisplayWidth); } @@ -537,6 +540,7 @@ public HelpText AddVerbs(params Type[] types) return AddOptionsImpl( AdaptVerbsToSpecifications(types), SentenceBuilder.RequiredWord(), + SentenceBuilder.OptionGroupWord(), MaximumDisplayWidth); } @@ -553,6 +557,7 @@ public HelpText AddOptions(int maximumLength, ParserResult result) return AddOptionsImpl( GetSpecificationsFromType(result.TypeInfo.Current), SentenceBuilder.RequiredWord(), + SentenceBuilder.OptionGroupWord(), maximumLength); } @@ -571,6 +576,7 @@ public HelpText AddVerbs(int maximumLength, params Type[] types) return AddOptionsImpl( AdaptVerbsToSpecifications(types), SentenceBuilder.RequiredWord(), + SentenceBuilder.OptionGroupWord(), maximumLength); } @@ -614,7 +620,7 @@ public static IEnumerable RenderParsingErrorsTextAsLines( if (meaningfulErrors.Empty()) yield break; - foreach(var error in meaningfulErrors + foreach (var error in meaningfulErrors .Where(e => e.Tag != ErrorType.MutuallyExclusiveSetError)) { var line = new StringBuilder(indent.Spaces()) @@ -751,9 +757,9 @@ private IEnumerable GetSpecificationsFromType(Type type) var optionSpecs = specs .OfType(); if (autoHelp) - optionSpecs = optionSpecs.Concat(new [] { MakeHelpEntry() }); + optionSpecs = optionSpecs.Concat(new[] { MakeHelpEntry() }); if (autoVersion) - optionSpecs = optionSpecs.Concat(new [] { MakeVersionEntry() }); + optionSpecs = optionSpecs.Concat(new[] { MakeVersionEntry() }); var valueSpecs = specs .OfType() .OrderBy(v => v.Index); @@ -780,29 +786,30 @@ private static Maybe>> GetUsageFromTy private IEnumerable AdaptVerbsToSpecifications(IEnumerable types) { var optionSpecs = from verbTuple in Verb.SelectFromTypes(types) - select - OptionSpecification.NewSwitch( - string.Empty, - verbTuple.Item1.Name, - false, - verbTuple.Item1.HelpText, - string.Empty, - verbTuple.Item1.Hidden); + select + OptionSpecification.NewSwitch( + string.Empty, + verbTuple.Item1.Name, + false, + verbTuple.Item1.HelpText, + string.Empty, + verbTuple.Item1.Hidden); if (autoHelp) - optionSpecs = optionSpecs.Concat(new [] { MakeHelpEntry() }); + optionSpecs = optionSpecs.Concat(new[] { MakeHelpEntry() }); if (autoVersion) - optionSpecs = optionSpecs.Concat(new [] { MakeVersionEntry() }); + optionSpecs = optionSpecs.Concat(new[] { MakeVersionEntry() }); return optionSpecs; } private HelpText AddOptionsImpl( IEnumerable specifications, string requiredWord, + string optionGroupWord, int maximumLength) { var maxLength = GetMaxLength(specifications); - - + + optionsHelp = new StringBuilder(BuilderCapacity); @@ -822,14 +829,14 @@ private HelpText AddOptionsImpl( foreach (var comparable in comparables) { Specification spec = specifications.ElementAt(comparable.Index); - AddOption(requiredWord, maxLength, spec, remainingSpace); + AddOption(requiredWord, optionGroupWord, maxLength, spec, remainingSpace); } } else { specifications.ForEach( option => - AddOption(requiredWord, maxLength, option, remainingSpace)); + AddOption(requiredWord, optionGroupWord, maxLength, option, remainingSpace)); } @@ -865,7 +872,7 @@ private HelpText AddPreOptionsLine(string value, int maximumLength) return this; } - private HelpText AddOption(string requiredWord, int maxLength, Specification specification, int widthOfHelpText) + private HelpText AddOption(string requiredWord, string optionGroupWord, int maxLength, Specification specification, int widthOfHelpText) { if (specification.Hidden) return this; @@ -891,11 +898,16 @@ private HelpText AddOption(string requiredWord, int maxLength, Specification spe if (specification.Required) optionHelpText = "{0} ".FormatInvariant(requiredWord) + optionHelpText; - + + if (specification.Tag == SpecificationType.Option && specification is OptionSpecification optionSpecification && optionSpecification.Group.IsJust()) + { + optionHelpText = "({0}: {1})".FormatInvariant(optionGroupWord, optionSpecification.Group.GetValueOrDefault(null)) + optionHelpText; + } + //note that we need to indent trim the start of the string because it's going to be //appended to an existing line that is as long as the indent-level - var indented = TextWrapper.WrapAndIndentText(optionHelpText, maxLength+TotalOptionPadding, widthOfHelpText).TrimStart(); - + var indented = TextWrapper.WrapAndIndentText(optionHelpText, maxLength + TotalOptionPadding, widthOfHelpText).TrimStart(); + optionsHelp .Append(indented) .Append(Environment.NewLine) @@ -989,7 +1001,7 @@ private int GetMaxOptionLength(OptionSpecification spec) } if (hasShort && hasLong) - specLength += OptionPrefixWidth; + specLength += OptionPrefixWidth; return specLength; } @@ -1037,7 +1049,7 @@ private static string FormatDefaultValue(T value) : string.Empty; } - + } } diff --git a/src/CommandLine/Text/SentenceBuilder.cs b/src/CommandLine/Text/SentenceBuilder.cs index cdcf409c..d58bf9c7 100644 --- a/src/CommandLine/Text/SentenceBuilder.cs +++ b/src/CommandLine/Text/SentenceBuilder.cs @@ -34,6 +34,11 @@ public static SentenceBuilder Create() /// public abstract Func RequiredWord { get; } + /// + /// Gets a delegate that returns the word 'group'. + /// + public abstract Func OptionGroupWord { get; } + /// /// Gets a delegate that returns that errors block heading text. /// @@ -85,6 +90,11 @@ public override Func UsageHeadingText get { return () => "USAGE:"; } } + public override Func OptionGroupWord + { + get { return () => "Group"; } + } + public override Func HelpCommandText { get From 06fcda7c70134b74284e46e59c9d06f5d2f522c5 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 8 Dec 2019 14:52:04 +0200 Subject: [PATCH 04/14] Ignore required rules when group specification has group option set. --- src/CommandLine/Core/SpecificationPropertyRules.cs | 2 ++ src/CommandLine/OptionAttribute.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index d44b29c6..31a20027 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -101,6 +101,7 @@ where sp.Specification.Required where sp.Value.IsNothing() let o = (OptionSpecification)sp.Specification where o.SetName.Length > 0 + where o.Group.IsNothing() where setWithRequiredValue.ContainsIfNotEmpty(o.SetName) select sp.Specification; var missing = @@ -113,6 +114,7 @@ where sp.Specification.Required where sp.Value.IsNothing() let o = (OptionSpecification)sp.Specification where o.SetName.Length == 0 + where o.Group.IsNothing() select sp.Specification) .Concat( from sp in specProps diff --git a/src/CommandLine/OptionAttribute.cs b/src/CommandLine/OptionAttribute.cs index ce2e1fae..4a38a4da 100644 --- a/src/CommandLine/OptionAttribute.cs +++ b/src/CommandLine/OptionAttribute.cs @@ -107,7 +107,7 @@ public char Separator } /// - /// Gets or sets the option group name. When one or more options are grouped, at least one of them should have value. + /// Gets or sets the option group name. When one or more options are grouped, at least one of them should have value. Required rules are ignored. /// public string Group { From 56a8138dd39b18b9419930ceef23202f6e34217b Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 8 Dec 2019 22:01:23 +0200 Subject: [PATCH 05/14] Remove required word if option group is available in help text --- src/CommandLine/Text/HelpText.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index a67e43dd..5c0ecea0 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -874,6 +874,16 @@ private HelpText AddPreOptionsLine(string value, int maximumLength) private HelpText AddOption(string requiredWord, string optionGroupWord, int maxLength, Specification specification, int widthOfHelpText) { + OptionSpecification GetOptionGroupSpecification() + { + if (specification.Tag == SpecificationType.Option && specification is OptionSpecification optionSpecification && optionSpecification.Group.IsJust()) + { + return optionSpecification; + } + + return null; + } + if (specification.Hidden) return this; @@ -896,12 +906,14 @@ private HelpText AddOption(string requiredWord, string optionGroupWord, int maxL specification.DefaultValue.Do( defaultValue => optionHelpText = "(Default: {0}) ".FormatInvariant(FormatDefaultValue(defaultValue)) + optionHelpText); - if (specification.Required) + var optionGroupSpecification = GetOptionGroupSpecification(); + + if (specification.Required && optionGroupSpecification == null) optionHelpText = "{0} ".FormatInvariant(requiredWord) + optionHelpText; - if (specification.Tag == SpecificationType.Option && specification is OptionSpecification optionSpecification && optionSpecification.Group.IsJust()) + if (optionGroupSpecification != null) { - optionHelpText = "({0}: {1})".FormatInvariant(optionGroupWord, optionSpecification.Group.GetValueOrDefault(null)) + optionHelpText; + optionHelpText = "({0}: {1}) ".FormatInvariant(optionGroupWord, optionGroupSpecification.Group.GetValueOrDefault(null)) + optionHelpText; } //note that we need to indent trim the start of the string because it's going to be From 33dfcce8465af089b3a58991475ae5f95263fcaa Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 8 Dec 2019 22:01:43 +0200 Subject: [PATCH 06/14] Remove redundant xml doc parameter --- src/CommandLine/Text/HelpText.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 5c0ecea0..949e6876 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -306,7 +306,6 @@ public SentenceBuilder SentenceBuilder /// A delegate used to customize model used to render text block of usage examples. /// If true the output style is consistent with verb commands (no dashes), otherwise it outputs options. /// The maximum width of the display. - /// a comparison lambda to order options in help text /// The parameter is not ontly a metter of formatting, it controls whether to handle verbs or options. public static HelpText AutoBuild( ParserResult parserResult, From dc9cc826ce074e0115ee0992620ba38fb212ee20 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Thu, 12 Dec 2019 14:39:01 +0200 Subject: [PATCH 07/14] Option group help text tests without errors section --- ...mple_Options_With_Multiple_OptionGroups.cs | 19 +++++ .../Fakes/Simple_Options_With_OptionGroup.cs | 14 ++++ ...imple_Options_With_Required_OptionGroup.cs | 14 ++++ .../Unit/Text/HelpTextTests.cs | 78 +++++++++++++++++-- 4 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_Multiple_OptionGroups.cs create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup.cs create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_Multiple_OptionGroups.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Multiple_OptionGroups.cs new file mode 100644 index 00000000..d4496666 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Multiple_OptionGroups.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_With_Multiple_OptionGroups + { + [Option(HelpText = "Define a string value here.", Group = "string-group")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Group = "string-group")] + public string ShortAndLong { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.", Group = "second-group")] + public bool BoolValue { get; set; } + + [Option('i', Min = 3, Max = 4, HelpText = "Define a int sequence here.", Group = "second-group")] + public IEnumerable IntSequence { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup.cs new file mode 100644 index 00000000..f1b2bfdd --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup.cs @@ -0,0 +1,14 @@ +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_With_OptionGroup + { + [Option(HelpText = "Define a string value here.", Group = "string-group")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Group = "string-group")] + public string ShortAndLong { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.")] + public bool BoolValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs new file mode 100644 index 00000000..3cf6f478 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs @@ -0,0 +1,14 @@ +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_With_Required_OptionGroup + { + [Option(HelpText = "Define a string value here.", Required = true, Group = "string-group")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Group = "string-group")] + public string ShortAndLong { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.")] + public bool BoolValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 1dd8d45f..c6eb9312 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -3,20 +3,25 @@ using System; using System.Collections.Generic; using System.Globalization; -using CommandLine.Core; using System.Linq; using System.Reflection; +using System.Text; + +using CommandLine.Core; using CommandLine.Infrastructure; using CommandLine.Tests.Fakes; using CommandLine.Text; + using FluentAssertions; + using Xunit; -using System.Text; namespace CommandLine.Tests.Unit.Text { public class HelpTextTests : IDisposable { + private readonly HeadingInfo headingInfo = new HeadingInfo("CommandLine.Tests.dll", "1.9.4.131"); + public void Dispose() { ReflectionHelper.SetAttributeOverride(null); @@ -143,7 +148,7 @@ public void When_help_text_is_longer_than_width_it_will_wrap_around_as_if_in_a_c { // Fixture setup // Exercize system - var sut = new HelpText(new HeadingInfo("CommandLine.Tests.dll", "1.9.4.131")); + var sut = new HelpText(headingInfo); sut.MaximumDisplayWidth = 40; sut.AddOptions( new NotParsed( @@ -166,7 +171,7 @@ public void When_help_text_is_longer_than_width_it_will_wrap_around_as_if_in_a_c { // Fixture setup // Exercize system - var sut = new HelpText(new HeadingInfo("CommandLine.Tests.dll", "1.9.4.131")) { MaximumDisplayWidth = 100} ; + var sut = new HelpText(headingInfo) { MaximumDisplayWidth = 100} ; sut.AddOptions( new NotParsed( TypeInfo.Create(typeof(Simple_Options_With_HelpText_Set_To_Long_Description)), @@ -185,7 +190,7 @@ public void When_help_text_has_hidden_option_it_should_not_be_added_to_help_text { // Fixture setup // Exercize system - var sut = new HelpText(new HeadingInfo("CommandLine.Tests.dll", "1.9.4.131")); + var sut = new HelpText(headingInfo); sut.MaximumDisplayWidth = 80; sut.AddOptions( new NotParsed( @@ -205,7 +210,7 @@ public void Long_help_text_without_spaces() { // Fixture setup // Exercize system - var sut = new HelpText(new HeadingInfo("CommandLine.Tests.dll", "1.9.4.131")); + var sut = new HelpText(headingInfo); sut.MaximumDisplayWidth = 40; sut.AddOptions( new NotParsed( @@ -739,5 +744,66 @@ public void Options_should_be_separated_by_spaces() // Teardown } + + [Fact] + public void Options_Should_Render_OptionGroup_In_Parenthesis_When_Available() + { + var sut = new HelpText(headingInfo) { AddDashesToOption = true, MaximumDisplayWidth = 100 } + .AddOptions( + new NotParsed(TypeInfo.Create(typeof(Simple_Options_With_OptionGroup)), Enumerable.Empty())); + + var text = sut.ToString(); + var lines = text.ToLines().TrimStringArray(); + + + lines[0].Should().BeEquivalentTo(headingInfo.ToString()); + lines[1].Should().BeEmpty(); + lines[2].Should().BeEquivalentTo("--stringvalue (Group: string-group) Define a string value here."); + lines[3].Should().BeEquivalentTo("-s, --shortandlong (Group: string-group) Example with both short and long name."); + lines[4].Should().BeEquivalentTo("-x Define a boolean or switch value here."); + lines[5].Should().BeEquivalentTo("--help Display this help screen."); + lines[6].Should().BeEquivalentTo("--version Display version information."); + } + + [Fact] + public void Options_Should_Render_OptionGroup_When_Available_And_Should_Not_Render_Required() + { + var sut = new HelpText(headingInfo) { AddDashesToOption = true, MaximumDisplayWidth = 100 } + .AddOptions( + new NotParsed(TypeInfo.Create(typeof(Simple_Options_With_Required_OptionGroup)), Enumerable.Empty())); + + var text = sut.ToString(); + var lines = text.ToLines().TrimStringArray(); + + + lines[0].Should().BeEquivalentTo(headingInfo.ToString()); + lines[1].Should().BeEmpty(); + lines[2].Should().BeEquivalentTo("--stringvalue (Group: string-group) Define a string value here."); + lines[3].Should().BeEquivalentTo("-s, --shortandlong (Group: string-group) Example with both short and long name."); + lines[4].Should().BeEquivalentTo("-x Define a boolean or switch value here."); + lines[5].Should().BeEquivalentTo("--help Display this help screen."); + lines[6].Should().BeEquivalentTo("--version Display version information."); + } + + [Fact] + public void Options_Should_Render_Multiple_OptionGroups_When_Available() + { + var sut = new HelpText(headingInfo) { AddDashesToOption = true, MaximumDisplayWidth = 100 } + .AddOptions( + new NotParsed(TypeInfo.Create(typeof(Simple_Options_With_Multiple_OptionGroups)), Enumerable.Empty())); + + var text = sut.ToString(); + var lines = text.ToLines().TrimStringArray(); + + + lines[0].Should().BeEquivalentTo(headingInfo.ToString()); + lines[1].Should().BeEmpty(); + lines[2].Should().BeEquivalentTo("--stringvalue (Group: string-group) Define a string value here."); + lines[3].Should().BeEquivalentTo("-s, --shortandlong (Group: string-group) Example with both short and long name."); + lines[4].Should().BeEquivalentTo("-x (Group: second-group) Define a boolean or switch value here."); + lines[5].Should().BeEquivalentTo("-i (Group: second-group) Define a int sequence here."); + lines[6].Should().BeEquivalentTo("--help Display this help screen."); + lines[7].Should().BeEquivalentTo("--version Display version information."); + } } } From a1952beb22f5f880a0470af276962a15e284e3a9 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Thu, 12 Dec 2019 15:20:36 +0200 Subject: [PATCH 08/14] Error render test update --- tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index c6eb9312..df447ac9 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -255,6 +255,11 @@ public void Long_pre_and_post_lines_without_spaces() public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text() { // Fixture setup + var optionsInGroup = new List + { + new NameInfo("t", "testOption1"), + new NameInfo("c", "testOption2") + }; var fakeResult = new NotParsed( TypeInfo.Create(typeof(NullInstance)), new Error[] @@ -267,7 +272,8 @@ public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text( new NoVerbSelectedError(), new BadVerbSelectedError("badverb"), new HelpRequestedError(), // should be ignored - new HelpVerbRequestedError(null, null, false) // should be ignored + new HelpVerbRequestedError(null, null, false), // should be ignored + new MissingGroupOptionError("bad-option-group", optionsInGroup), }); Func fakeRenderer = err => { @@ -287,6 +293,11 @@ public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text( return "ERR no-verb-selected"; case ErrorType.BadVerbSelectedError: return "ERR " + ((BadVerbSelectedError)err).Token; + case ErrorType.MissingGroupOptionError: + { + var groupErr = (MissingGroupOptionError)err; + return "ERR " + groupErr.Group + ": " + string.Join("---", groupErr.Names.Select(n => n.NameText)); + } default: throw new InvalidOperationException(); } @@ -306,6 +317,7 @@ public void Invoking_RenderParsingErrorsText_returns_appropriate_formatted_text( lines[4].Should().BeEquivalentTo(" ERR s, sequence"); lines[5].Should().BeEquivalentTo(" ERR no-verb-selected"); lines[6].Should().BeEquivalentTo(" ERR badverb"); + lines[7].Should().BeEquivalentTo(" ERR bad-option-group: t, testOption1---c, testOption2"); // Teardown } From 8d91fb04734a3830ec6253b314d99d796eff9d6a Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Thu, 12 Dec 2019 15:33:28 +0200 Subject: [PATCH 09/14] instance builder tests with option group error --- .../Fakes/Options_With_Group.cs | 14 ++++++ .../Unit/Core/InstanceBuilderTests.cs | 43 +++++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Options_With_Group.cs diff --git a/tests/CommandLine.Tests/Fakes/Options_With_Group.cs b/tests/CommandLine.Tests/Fakes/Options_With_Group.cs new file mode 100644 index 00000000..849171bd --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Options_With_Group.cs @@ -0,0 +1,14 @@ +namespace CommandLine.Tests.Fakes +{ + public class Options_With_Group + { + [Option('v', "version")] + public string Version { get; set; } + + [Option("option1", Group = "err-group")] + public string Option1 { get; set; } + + [Option("option2", Group = "err-group")] + public string Option2 { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index c6234089..59cdf882 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -4,15 +4,16 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using Microsoft.FSharp.Core; + using CommandLine.Core; using CommandLine.Infrastructure; +using CommandLine.Tests.Fakes; using CSharpx; -using CommandLine.Tests.Fakes; + using FluentAssertions; + using Xunit; -using System.Reflection; namespace CommandLine.Tests.Unit.Core { @@ -1158,6 +1159,42 @@ public void OptionClass_IsImmutable_HasNoCtor() act.Should().Throw(); } + [Fact] + public void Options_In_Group_With_No_Values_Generates_MissingGroupOptionError() + { + // Fixture setup + var optionNames = new List + { + new NameInfo("", "option1"), + new NameInfo("", "option2") + }; + var expectedResult = new[] { new MissingGroupOptionError("err-group", optionNames) }; + + // Exercize system + var result = InvokeBuild( + new[] { "-v 10.42" }); + + // Verify outcome + ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); + + // Teardown + } + + [Theory] + [InlineData("-v", "10.5", "--option1", "test1", "--option2", "test2")] + [InlineData("-v", "10.5", "--option1", "test1")] + [InlineData("-v", "10.5", "--option2", "test2")] + public void Options_In_Group_With_Values_Does_Not_Generate_MissingGroupOptionError(params string[] args) + { + // Exercize system + var result = InvokeBuild(args); + + // Verify outcome + result.Should().BeOfType>(); + + // Teardown + } + private class ValueWithNoSetterOptions { [Value(0, MetaName = "Test", Default = 0)] From d002cf487ba3b518553e43fefcc3fc202090a4c8 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Thu, 12 Dec 2019 16:08:23 +0200 Subject: [PATCH 10/14] Fix instance builder tests - missing fsharp using --- tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index 59cdf882..a776a7ce 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -4,16 +4,15 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; - +using Microsoft.FSharp.Core; using CommandLine.Core; using CommandLine.Infrastructure; -using CommandLine.Tests.Fakes; using CSharpx; - +using CommandLine.Tests.Fakes; using FluentAssertions; - using Xunit; +using System.Reflection; namespace CommandLine.Tests.Unit.Core { From dd51d955f01fce0be974e2d96bdaa52cb34520ac Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Fri, 13 Dec 2019 14:25:28 +0200 Subject: [PATCH 11/14] Delete unused GroupAttribute --- src/CommandLine/GroupAttribute.cs | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 src/CommandLine/GroupAttribute.cs diff --git a/src/CommandLine/GroupAttribute.cs b/src/CommandLine/GroupAttribute.cs deleted file mode 100644 index 82832d14..00000000 --- a/src/CommandLine/GroupAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace CommandLine -{ - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] - public class GroupAttribute : Attribute - { - public GroupAttribute(string name) - { - Name = name; - } - - public string Name { get; } - } -} From 108ecfb222ebfa63012335c5122f3f7c28841568 Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 15 Dec 2019 22:13:17 +0200 Subject: [PATCH 12/14] Option group - empty name tests + ignore required tests --- ...tions_With_OptionGroup_WithDefaultValue.cs | 14 +++++ ...imple_Options_With_Required_OptionGroup.cs | 2 +- .../Unit/Core/InstanceBuilderTests.cs | 56 +++++++++++++++++-- 3 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithDefaultValue.cs diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithDefaultValue.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithDefaultValue.cs new file mode 100644 index 00000000..ccfc643d --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_OptionGroup_WithDefaultValue.cs @@ -0,0 +1,14 @@ +namespace CommandLine.Tests.Fakes +{ + public class Simple_Options_With_OptionGroup_WithDefaultValue + { + [Option(HelpText = "Define a string value here.", Required = true, Group = "")] + public string StringValue { get; set; } + + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Required = true, Group = "")] + public string ShortAndLong { get; set; } + + [Option('x', HelpText = "Define a boolean or switch value here.")] + public bool BoolValue { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs index 3cf6f478..bbeaaf53 100644 --- a/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs +++ b/tests/CommandLine.Tests/Fakes/Simple_Options_With_Required_OptionGroup.cs @@ -5,7 +5,7 @@ public class Simple_Options_With_Required_OptionGroup [Option(HelpText = "Define a string value here.", Required = true, Group = "string-group")] public string StringValue { get; set; } - [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Group = "string-group")] + [Option('s', "shortandlong", HelpText = "Example with both short and long name.", Required = true, Group = "string-group")] public string ShortAndLong { get; set; } [Option('x', HelpText = "Define a boolean or switch value here.")] diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index a776a7ce..0941d7b5 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -1,16 +1,19 @@ -// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. +// 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.Globalization; -using System.Linq; using Microsoft.FSharp.Core; using CommandLine.Core; using CommandLine.Infrastructure; +using CommandLine.Tests.Fakes; using CSharpx; -using CommandLine.Tests.Fakes; + using FluentAssertions; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + using Xunit; using System.Reflection; @@ -1194,6 +1197,47 @@ public void Options_In_Group_With_Values_Does_Not_Generate_MissingGroupOptionErr // Teardown } + [Fact] + public void Options_In_Group_WithRequired_Does_Not_Generate_RequiredError() + { + // Fixture setup + var optionNames = new List + { + new NameInfo("", "stingvalue"), + new NameInfo("s", "shortandlong") + }; + var expectedResult = new[] { new MissingGroupOptionError("string-group", optionNames) }; + + // Exercize system + var result = InvokeBuild(new string[] { "-x" }); + + // Verify outcome + result.Should().BeOfType>(); + var errors = ((NotParsed)result).Errors; + + errors.Should().HaveCount(1); + errors.Should().BeEquivalentTo(expectedResult); + } + + [Fact] + public void Options_In_Group_Ignore_Option_Group_If_Option_Group_Name_Empty() + { + var expectedResult = new[] + { + new MissingRequiredOptionError(new NameInfo("", "stringvalue")), + new MissingRequiredOptionError(new NameInfo("s", "shortandlong")) + }; + + // Exercize system + var result = InvokeBuild(new string[] { "-x" }); + + // Verify outcome + result.Should().BeOfType>(); + var errors = ((NotParsed)result).Errors; + + errors.Should().BeEquivalentTo(expectedResult); + } + private class ValueWithNoSetterOptions { [Value(0, MetaName = "Test", Default = 0)] From 4274c25b981e84d66fd1ed54c627e0b69d4d3a4f Mon Sep 17 00:00:00 2001 From: Vladislav Hadzhiyski Date: Sun, 15 Dec 2019 22:14:10 +0200 Subject: [PATCH 13/14] Delete useless 'teardown' comment + fix formatting --- .../Unit/Core/InstanceBuilderTests.cs | 163 ++++-------------- 1 file changed, 32 insertions(+), 131 deletions(-) diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index 0941d7b5..d4a4fd12 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -78,12 +78,10 @@ public void Explicit_help_request_generates_help_requested_error() // Verify outcome result.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Theory] - [InlineData(new[] {"-123"}, -123L)] + [InlineData(new[] { "-123" }, -123L)] [InlineData(new[] { "-1" }, -1L)] [InlineData(new[] { "-9223372036854775807" }, -9223372036854775807)] // long.MaxValue * -1 public void Parse_negative_long_value(string[] arguments, long expected) @@ -96,8 +94,6 @@ public void Parse_negative_long_value(string[] arguments, long expected) // Verify outcome ((Parsed)result).Value.LongValue.Should().Be(expected); - - // Teardown } [Theory] @@ -116,8 +112,6 @@ public void Parse_double_value(string[] arguments, double expected) // Verify outcome ((Parsed)result).Value.DoubleValue.Should().Be(expected); - - // Teardown } [Theory] @@ -137,8 +131,6 @@ public void Parse_int_sequence(string[] arguments, int[] expected) // Verify outcome ((Parsed)result).Value.IntSequence.Should().BeEquivalentTo(expected); - - // Teardown } [Theory] @@ -156,14 +148,12 @@ public void Parse_int_sequence_with_range(string[] arguments, int[] expected) // Verify outcome ((Parsed)result).Value.IntSequence.Should().BeEquivalentTo(expected); - - // Teardown } [Theory] - [InlineData(new[] {"-s", "just-one"}, new[] {"just-one"})] - [InlineData(new[] {"-sjust-one-samearg"}, new[] {"just-one-samearg"})] - [InlineData(new[] {"-s", "also-two", "are-ok" }, new[] { "also-two", "are-ok" })] + [InlineData(new[] { "-s", "just-one" }, new[] { "just-one" })] + [InlineData(new[] { "-sjust-one-samearg" }, new[] { "just-one-samearg" })] + [InlineData(new[] { "-s", "also-two", "are-ok" }, new[] { "also-two", "are-ok" })] [InlineData(new[] { "--string-seq", "one", "two", "three" }, new[] { "one", "two", "three" })] [InlineData(new[] { "--string-seq=one", "two", "three", "4" }, new[] { "one", "two", "three", "4" })] public void Parse_string_sequence_with_only_min_constraint(string[] arguments, string[] expected) @@ -176,8 +166,6 @@ public void Parse_string_sequence_with_only_min_constraint(string[] arguments, s // Verify outcome ((Parsed)result).Value.StringSequence.Should().BeEquivalentTo(expected); - - // Teardown } [Theory] @@ -195,8 +183,6 @@ public void Parse_string_sequence_with_only_max_constraint(string[] arguments, s // Verify outcome ((Parsed)result).Value.StringSequence.Should().BeEquivalentTo(expected); - - // Teardown } [Fact] @@ -211,8 +197,6 @@ public void Breaking_min_constraint_in_string_sequence_gererates_MissingValueOpt // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -227,8 +211,6 @@ public void Breaking_min_constraint_in_string_sequence_as_value_gererates_Sequen // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -243,8 +225,6 @@ public void Breaking_max_constraint_in_string_sequence_gererates_SequenceOutOfRa // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -259,8 +239,6 @@ public void Breaking_max_constraint_in_string_sequence_as_value_gererates_Sequen // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Theory] @@ -280,8 +258,6 @@ public void Parse_enum_value(string[] arguments, Colors expected) // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.Colors); - - // Teardown } [Theory] @@ -301,8 +277,6 @@ public void Parse_enum_value_ignore_case(string[] arguments, Colors expected) // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.Colors); - - // Teardown } [Fact] @@ -317,8 +291,6 @@ public void Parse_enum_value_with_wrong_index_generates_BadFormatConversionError // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -333,8 +305,6 @@ public void Parse_enum_value_with_wrong_item_name_generates_BadFormatConversionE // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -349,8 +319,6 @@ public void Parse_enum_value_with_wrong_item_name_case_generates_BadFormatConver // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -358,12 +326,12 @@ public void Parse_values_partitioned_between_sequence_and_scalar() { // Fixture setup var expectedResult = new Simple_Options_With_Values - { - StringValue = string.Empty, - LongValue = 10L, - StringSequence = new[] { "a", "b", "c" }, - IntValue = 20 - }; + { + StringValue = string.Empty, + LongValue = 10L, + StringSequence = new[] { "a", "b", "c" }, + IntValue = 20 + }; // Exercize system var result = InvokeBuild( @@ -371,8 +339,6 @@ public void Parse_values_partitioned_between_sequence_and_scalar() // Verify outcome expectedResult.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Theory] @@ -391,8 +357,6 @@ public void Parse_sequence_value_without_range_constraints(string[] arguments, l // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.LongSequence); - - // Teardown } [Theory] @@ -410,8 +374,6 @@ public void Parse_long_sequence_with_separator(string[] arguments, long[] expect // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.LongSequence); - - // Teardown } [Theory] @@ -429,8 +391,6 @@ public void Parse_string_sequence_with_separator(string[] arguments, string[] ex // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.StringSequence); - - // Teardown } /// @@ -441,12 +401,12 @@ public void Double_dash_force_subsequent_arguments_as_values() { // Fixture setup var expectedResult = new Simple_Options_With_Values - { - StringValue = "str1", - LongValue = 10L, - StringSequence = new[] { "-a", "--bee", "-c" }, - IntValue = 20 - }; + { + StringValue = "str1", + LongValue = 10L, + StringSequence = new[] { "-a", "--bee", "-c" }, + IntValue = 20 + }; var arguments = new[] { "--stringvalue", "str1", "--", "10", "-a", "--bee", "-c", "20" }; // Exercize system @@ -465,8 +425,6 @@ public void Double_dash_force_subsequent_arguments_as_values() // Verify outcome expectedResult.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Fact] @@ -485,14 +443,14 @@ public void Parse_option_from_different_sets_gererates_MutuallyExclusiveSetError // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] - public void Two_required_options_at_the_same_set_and_both_are_true() { + public void Two_required_options_at_the_same_set_and_both_are_true() + { // Fixture setup - var expectedResult = new Options_With_Required_Set_To_True_Within_Same_Set { + var expectedResult = new Options_With_Required_Set_To_True_Within_Same_Set + { FtpUrl = "str1", WebUrl = "str2" }; @@ -506,7 +464,8 @@ public void Two_required_options_at_the_same_set_and_both_are_true() { } [Fact] - public void Two_required_options_at_the_same_set_and_none_are_true() { + public void Two_required_options_at_the_same_set_and_none_are_true() + { // Fixture setup var expectedResult = new[] { @@ -519,8 +478,6 @@ public void Two_required_options_at_the_same_set_and_none_are_true() { // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -535,8 +492,6 @@ public void Omitting_required_option_gererates_MissingRequiredOptionError() // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -551,8 +506,6 @@ public void Wrong_range_in_sequence_gererates_SequenceOutOfRangeError() // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -567,8 +520,6 @@ public void Parse_unknown_long_option_gererates_UnknownOptionError() // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -583,8 +534,6 @@ public void Parse_unknown_short_option_gererates_UnknownOptionError() // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -599,13 +548,11 @@ public void Parse_unknown_short_option_in_option_group_gererates_UnknownOptionEr // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Theory] - [InlineData(new[] {"--stringvalue", "this-value"}, "this-value")] - [InlineData(new[] {"--stringvalue=this-other"}, "this-other")] + [InlineData(new[] { "--stringvalue", "this-value" }, "this-value")] + [InlineData(new[] { "--stringvalue=this-other" }, "this-other")] public void Omitting_names_assumes_identifier_as_long_name(string[] arguments, string expected) { // Fixture setup in attributes @@ -616,8 +563,6 @@ public void Omitting_names_assumes_identifier_as_long_name(string[] arguments, s // Verify outcome ((Parsed)result).Value.StringValue.Should().BeEquivalentTo(expected); - - // Teardown } [Fact] @@ -632,8 +577,6 @@ public void Breaking_required_constraint_in_string_scalar_as_value_generates_Mis // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Theory] @@ -651,8 +594,6 @@ public void Parse_utf8_string_correctly(string[] arguments, string expected) // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.StringValue); - - // Teardown } [Fact] @@ -667,8 +608,6 @@ public void Breaking_equal_min_max_constraint_in_string_sequence_as_value_gerera // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Theory] @@ -686,8 +625,6 @@ public void Parse_nullable_int(string[] arguments, int? expected) // Verify outcome expected.Should().Be(((Parsed)result).Value.NullableInt); - - // Teardown } [Theory] @@ -705,8 +642,6 @@ public void Parse_nullable_long(string[] arguments, long? expected) // Verify outcome expected.Should().Be(((Parsed)result).Value.NullableLong); - - // Teardown } #if !SKIP_FSHARP @@ -727,8 +662,6 @@ public void Parse_fsharp_option_string(string[] arguments, string expectedValue, expectedValue.Should().BeEquivalentTo(((Parsed)result).Value.FileName.Value); } expectedSome.Should().Be(FSharpOption.get_IsSome(((Parsed)result).Value.FileName)); - - // Teardown } [Theory] @@ -748,8 +681,6 @@ public void Parse_fsharp_option_int(string[] arguments, int expectedValue, bool expectedValue.Should().Be(((Parsed)result).Value.Offset.Value); } expectedSome.Should().Be(FSharpOption.get_IsSome(((Parsed)result).Value.Offset)); - - // Teardown } #endif @@ -788,7 +719,7 @@ public void Min_and_max_constraint_set_to_zero_throws_exception() } [Theory] - [InlineData(new[] {"--weburl", "value.com", "--verbose"}, ParserResultType.Parsed, 0)] + [InlineData(new[] { "--weburl", "value.com", "--verbose" }, ParserResultType.Parsed, 0)] [InlineData(new[] { "--ftpurl", "value.org", "--interactive" }, ParserResultType.Parsed, 0)] [InlineData(new[] { "--weburl", "value.com", "--verbose", "--interactive" }, ParserResultType.Parsed, 0)] [InlineData(new[] { "--ftpurl=fvalue", "--weburl=wvalue" }, ParserResultType.NotParsed, 2)] @@ -908,8 +839,6 @@ public void Parse_string_scalar_with_required_constraint_as_value(string[] argum // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Theory] @@ -924,15 +853,13 @@ public void Parse_string_scalar_and_sequence_adjacent(string[] arguments, Option // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Fact] public void Parse_to_mutable() { // Fixture setup - var expectedResult = new Simple_Options { StringValue="strval0", IntSequence=new[] { 9, 7, 8 }, BoolValue = true, LongValue = 9876543210L }; + var expectedResult = new Simple_Options { StringValue = "strval0", IntSequence = new[] { 9, 7, 8 }, BoolValue = true, LongValue = 9876543210L }; // Exercize system var result = InvokeBuild( @@ -940,17 +867,15 @@ public void Parse_to_mutable() // Verify outcome expectedResult.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Theory] [InlineData(new string[] { }, 2)] - [InlineData(new [] { "--str=val0" }, 1)] - [InlineData(new [] { "--long=9" }, 1)] - [InlineData(new [] { "--int=7" }, 2)] - [InlineData(new [] { "--str", "val1", "--int=3" }, 1)] - [InlineData(new [] { "--long", "9", "--int=11" }, 1)] + [InlineData(new[] { "--str=val0" }, 1)] + [InlineData(new[] { "--long=9" }, 1)] + [InlineData(new[] { "--int=7" }, 2)] + [InlineData(new[] { "--str", "val1", "--int=3" }, 1)] + [InlineData(new[] { "--long", "9", "--int=11" }, 1)] public void Breaking_required_constraint_generate_MissingRequiredOptionError(string[] arguments, int expected) { // Exercize system @@ -974,8 +899,6 @@ public void Parse_to_immutable_instance(string[] arguments, Immutable_Simple_Opt // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Fact] @@ -990,8 +913,6 @@ public void Parse_to_type_with_single_string_ctor_builds_up_correct_instance() // Verify outcome expectedResult.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Fact] @@ -1006,8 +927,6 @@ public void Parse_option_with_exception_thrown_from_setter_generates_SetValueExc // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Fact] @@ -1046,8 +965,6 @@ public void Parse_string_with_dashes_except_in_beginning(string[] arguments, str // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value.StringValue); - - // Teardown } [Theory] @@ -1063,8 +980,6 @@ public void Parse_without_auto_help_should_not_recognize_help_option(string[] ar result.Should().BeOfType>() .Which.Errors.Should().ContainSingle() .Which.Tag.Should().Be(errorType); - - // Teardown } [Theory] @@ -1081,8 +996,6 @@ public void Parse_with_custom_help_option(string[] arguments, bool isHelp) // Verify outcome result.Should().BeOfType>() .Which.Value.Help.Should().Be(isHelp); - - // Teardown } [Theory] @@ -1098,8 +1011,6 @@ public void Parse_without_auto_version_should_not_recognize_version_option(strin result.Should().BeOfType>() .Which.Errors.Should().ContainSingle() .Which.Tag.Should().Be(errorType); - - // Teardown } [Theory] @@ -1116,8 +1027,6 @@ public void Parse_with_custom_version_option(string[] arguments, bool isVersion) // Verify outcome result.Should().BeOfType>() .Which.Value.MyVersion.Should().Be(isVersion); - - // Teardown } [Theory] @@ -1132,8 +1041,6 @@ public void Parse_Guid(string[] arguments, Options_With_Guid expected) // Verify outcome expected.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } [Fact] @@ -1148,8 +1055,6 @@ public void Parse_TimeSpan() // Verify outcome expectedResult.Should().BeEquivalentTo(((Parsed)result).Value); - - // Teardown } @@ -1178,8 +1083,6 @@ public void Options_In_Group_With_No_Values_Generates_MissingGroupOptionError() // Verify outcome ((NotParsed)result).Errors.Should().BeEquivalentTo(expectedResult); - - // Teardown } [Theory] @@ -1193,8 +1096,6 @@ public void Options_In_Group_With_Values_Does_Not_Generate_MissingGroupOptionErr // Verify outcome result.Should().BeOfType>(); - - // Teardown } [Fact] @@ -1261,7 +1162,7 @@ public static IEnumerable ScalarSequenceStringAdjacentData { get { - yield return new object[] { new[] { "to-value" }, new Options_With_Scalar_Value_And_Adjacent_SequenceString { StringValueWithIndexZero = "to-value", StringOptionSequence = new string[] {} } }; + yield return new object[] { new[] { "to-value" }, new Options_With_Scalar_Value_And_Adjacent_SequenceString { StringValueWithIndexZero = "to-value", StringOptionSequence = new string[] { } } }; yield return new object[] { new[] { "to-value", "-s", "to-seq-0" }, new Options_With_Scalar_Value_And_Adjacent_SequenceString { StringValueWithIndexZero = "to-value", StringOptionSequence = new[] { "to-seq-0" } } }; yield return new object[] { new[] { "to-value", "-s", "to-seq-0", "to-seq-1" }, new Options_With_Scalar_Value_And_Adjacent_SequenceString { StringValueWithIndexZero = "to-value", StringOptionSequence = new[] { "to-seq-0", "to-seq-1" } } }; yield return new object[] { new[] { "-s", "cant-capture", "value-anymore" }, new Options_With_Scalar_Value_And_Adjacent_SequenceString { StringOptionSequence = new[] { "cant-capture", "value-anymore" } } }; From ff61b69a19a870b3ec1e905eee4c2e319ae00d0a Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Tue, 17 Dec 2019 21:29:43 +0200 Subject: [PATCH 14/14] Modify type of Group to string instead of `Mayb' --- src/CommandLine/Core/OptionSpecification.cs | 13 +++++++------ src/CommandLine/Core/SpecificationPropertyRules.cs | 8 ++++---- src/CommandLine/OptionAttribute.cs | 2 +- src/CommandLine/Text/HelpText.cs | 9 +++++++-- .../CommandLine.Tests/Unit/Core/NameLookupTests.cs | 4 ++-- .../Unit/Core/OptionMapperTests.cs | 2 +- .../Unit/Core/TokenPartitionerTests.cs | 8 ++++---- tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs | 4 ++-- 8 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/CommandLine/Core/OptionSpecification.cs b/src/CommandLine/Core/OptionSpecification.cs index 45dc2f58..77e7977f 100644 --- a/src/CommandLine/Core/OptionSpecification.cs +++ b/src/CommandLine/Core/OptionSpecification.cs @@ -13,12 +13,13 @@ sealed class OptionSpecification : Specification private readonly string longName; private readonly char separator; private readonly string setName; - private readonly Maybe group; + private readonly string group; public OptionSpecification(string shortName, string longName, bool required, string setName, Maybe min, Maybe max, char separator, Maybe defaultValue, string helpText, string metaValue, IEnumerable enumValues, - Type conversionType, TargetType targetType, Maybe group, bool hidden = false) - : base(SpecificationType.Option, required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden) + Type conversionType, TargetType targetType, string group, bool hidden = false) + : base(SpecificationType.Option, + required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, targetType, hidden) { this.shortName = shortName; this.longName = longName; @@ -43,14 +44,14 @@ public static OptionSpecification FromAttribute(OptionAttribute attribute, Type enumValues, conversionType, conversionType.ToTargetType(), - string.IsNullOrWhiteSpace(attribute.Group) ? Maybe.Nothing() : Maybe.Just(attribute.Group), + attribute.Group, attribute.Hidden); } public static OptionSpecification NewSwitch(string shortName, string longName, bool required, string helpText, string metaValue, bool hidden = false) { return new OptionSpecification(shortName, longName, required, string.Empty, Maybe.Nothing(), Maybe.Nothing(), - '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, Maybe.Nothing(), hidden); + '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, string.Empty, hidden); } public string ShortName @@ -73,7 +74,7 @@ public string SetName get { return setName; } } - public Maybe Group + public string Group { get { return group; } } diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index 31a20027..9122ee3a 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -32,7 +32,7 @@ private static Func, IEnumerable> Enfo from sp in specProps where sp.Specification.IsOption() let o = (OptionSpecification)sp.Specification - where o.Group.IsJust() + where o.Group.Length > 0 select new { Option = o, @@ -40,7 +40,7 @@ where o.Group.IsJust() }; var groups = from o in optionsValues - group o by o.Option.Group.GetValueOrDefault(null) into g + group o by o.Option.Group into g select g; var errorGroups = groups.Where(gr => gr.All(g => g.Value.IsNothing())); @@ -101,7 +101,7 @@ where sp.Specification.Required where sp.Value.IsNothing() let o = (OptionSpecification)sp.Specification where o.SetName.Length > 0 - where o.Group.IsNothing() + where o.Group.Length == 0 where setWithRequiredValue.ContainsIfNotEmpty(o.SetName) select sp.Specification; var missing = @@ -114,7 +114,7 @@ where sp.Specification.Required where sp.Value.IsNothing() let o = (OptionSpecification)sp.Specification where o.SetName.Length == 0 - where o.Group.IsNothing() + where o.Group.Length == 0 select sp.Specification) .Concat( from sp in specProps diff --git a/src/CommandLine/OptionAttribute.cs b/src/CommandLine/OptionAttribute.cs index 4a38a4da..7448b697 100644 --- a/src/CommandLine/OptionAttribute.cs +++ b/src/CommandLine/OptionAttribute.cs @@ -16,7 +16,7 @@ public sealed class OptionAttribute : BaseAttribute private readonly string shortName; private string setName; private char separator; - private string group; + private string group=string.Empty; private OptionAttribute(string shortName, string longName) : base() { diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 949e6876..319a8740 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -875,7 +875,12 @@ private HelpText AddOption(string requiredWord, string optionGroupWord, int maxL { OptionSpecification GetOptionGroupSpecification() { - if (specification.Tag == SpecificationType.Option && specification is OptionSpecification optionSpecification && optionSpecification.Group.IsJust()) + if (specification.Tag == SpecificationType.Option && + specification is OptionSpecification optionSpecification && + optionSpecification.Group.Length > 0 + ) + + { return optionSpecification; } @@ -912,7 +917,7 @@ OptionSpecification GetOptionGroupSpecification() if (optionGroupSpecification != null) { - optionHelpText = "({0}: {1}) ".FormatInvariant(optionGroupWord, optionGroupSpecification.Group.GetValueOrDefault(null)) + optionHelpText; + optionHelpText = "({0}: {1}) ".FormatInvariant(optionGroupWord, optionGroupSpecification.Group) + optionHelpText; } //note that we need to indent trim the start of the string because it's going to be diff --git a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs index caa0a386..785b1fe5 100644 --- a/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/NameLookupTests.cs @@ -21,7 +21,7 @@ public void Lookup_name_of_sequence_option_with_separator() // Fixture setup var expected = Maybe.Just("."); var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing())}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; // Exercize system var result = NameLookup.HavingSeparator("string-seq", specs, StringComparer.Ordinal); @@ -39,7 +39,7 @@ public void Get_name_from_option_specification() // Fixture setup var expected = new NameInfo(ShortName, LongName); - var spec = new OptionSpecification(ShortName, LongName, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing()); + var spec = new OptionSpecification(ShortName, LongName, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '.', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty); // Exercize system var result = spec.FromOptionSpecification(); diff --git a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs index 41b93bf5..75ddade7 100644 --- a/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/OptionMapperTests.cs @@ -32,7 +32,7 @@ public void Map_boolean_switch_creates_boolean_value() var specProps = new[] { SpecificationProperty.Create( - new OptionSpecification("x", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(bool), TargetType.Switch, Maybe.Nothing()), + new OptionSpecification("x", string.Empty, false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', Maybe.Nothing(), string.Empty, string.Empty, new List(), typeof(bool), TargetType.Switch, string.Empty), typeof(Simple_Options).GetProperties().Single(p => p.Name.Equals("BoolValue", StringComparison.Ordinal)), Maybe.Nothing()) }; diff --git a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs index 4266b2d4..11a9ffdd 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenPartitionerTests.cs @@ -24,8 +24,8 @@ public void Partition_sequence_returns_sequence() }; var specs = new[] { - new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, Maybe.Nothing()), - new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing()) + new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, string.Empty), + new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty) }; // Exercize system @@ -51,8 +51,8 @@ public void Partition_sequence_returns_sequence_with_duplicates() }; var specs = new[] { - new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, Maybe.Nothing()), - new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing()) + new OptionSpecification(string.Empty, "stringvalue", false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), '\0', null, string.Empty, string.Empty, new List(), typeof(string), TargetType.Scalar, string.Empty), + new OptionSpecification("i", string.Empty, false, string.Empty, Maybe.Just(3), Maybe.Just(4), '\0', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty) }; // Exercize system diff --git a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs index ac037cde..3bd95891 100644 --- a/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/TokenizerTests.cs @@ -26,7 +26,7 @@ public void Explode_scalar_with_separator_in_odd_args_input_returns_sequence() var expectedTokens = new[] { Token.Name("i"), Token.Value("10"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), Token.Value("cccc"), Token.Name("switch") }; var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing())}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; // Exercize system var result = @@ -49,7 +49,7 @@ public void Explode_scalar_with_separator_in_even_args_input_returns_sequence() var expectedTokens = new[] { Token.Name("x"), Token.Name("string-seq"), Token.Value("aaa"), Token.Value("bb"), Token.Value("cccc"), Token.Name("switch") }; var specs = new[] { new OptionSpecification(string.Empty, "string-seq", - false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, Maybe.Nothing())}; + false, string.Empty, Maybe.Nothing(), Maybe.Nothing(), ',', null, string.Empty, string.Empty, new List(), typeof(IEnumerable), TargetType.Sequence, string.Empty)}; // Exercize system var result =