diff --git a/src/CommandLine/Core/InstanceBuilder.cs b/src/CommandLine/Core/InstanceBuilder.cs index f48127b1..2dcc35a8 100644 --- a/src/CommandLine/Core/InstanceBuilder.cs +++ b/src/CommandLine/Core/InstanceBuilder.cs @@ -100,7 +100,7 @@ public static ParserResult Build( var missingValueErrors = from token in errorsPartition select new MissingValueOptionError( - optionSpecs.Single(o => token.Text.MatchName(o.ShortName, o.LongName, nameComparer)) + optionSpecs.Single(o => token.Text.MatchName(o.ShortName, o.LongNames, nameComparer)) .FromOptionSpecification()); var specPropsWithValue = diff --git a/src/CommandLine/Core/NameExtensions.cs b/src/CommandLine/Core/NameExtensions.cs index 405e44f5..3165a3ca 100644 --- a/src/CommandLine/Core/NameExtensions.cs +++ b/src/CommandLine/Core/NameExtensions.cs @@ -1,23 +1,24 @@ // Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. using System; +using System.Linq; namespace CommandLine.Core { static class NameExtensions { - public static bool MatchName(this string value, string shortName, string longName, StringComparer comparer) + public static bool MatchName(this string value, string shortName, string[] longNames, StringComparer comparer) { return value.Length == 1 ? comparer.Equals(value, shortName) - : comparer.Equals(value, longName); + : longNames.Any(longName => comparer.Equals(value, longName)); } public static NameInfo FromOptionSpecification(this OptionSpecification specification) { return new NameInfo( specification.ShortName, - specification.LongName); + specification.LongNames); } public static NameInfo FromSpecification(this Specification specification) diff --git a/src/CommandLine/Core/NameLookup.cs b/src/CommandLine/Core/NameLookup.cs index ccb24ea5..aa5c8839 100644 --- a/src/CommandLine/Core/NameLookup.cs +++ b/src/CommandLine/Core/NameLookup.cs @@ -18,7 +18,7 @@ static class NameLookup { public static NameLookupResult Contains(string name, IEnumerable specifications, StringComparer comparer) { - var option = specifications.FirstOrDefault(a => name.MatchName(a.ShortName, a.LongName, comparer)); + var option = specifications.FirstOrDefault(a => name.MatchName(a.ShortName, a.LongNames, comparer)); if (option == null) return NameLookupResult.NoOptionFound; return option.ConversionType == typeof(bool) || (option.ConversionType == typeof(int) && option.FlagCounter) ? NameLookupResult.BooleanOptionFound @@ -29,7 +29,7 @@ public static Maybe HavingSeparator(string name, IEnumerable name.MatchName(a.ShortName, a.LongName, comparer) && a.Separator != '\0') + a => name.MatchName(a.ShortName, a.LongNames, comparer) && a.Separator != '\0') .ToMaybe() .MapValueOrDefault(spec => Maybe.Just(spec.Separator), Maybe.Nothing()); } diff --git a/src/CommandLine/Core/OptionMapper.cs b/src/CommandLine/Core/OptionMapper.cs index ded42c4f..0eb984d7 100644 --- a/src/CommandLine/Core/OptionMapper.cs +++ b/src/CommandLine/Core/OptionMapper.cs @@ -23,7 +23,7 @@ public static Result< pt => { var matched = options.Where(s => - s.Key.MatchName(((OptionSpecification)pt.Specification).ShortName, ((OptionSpecification)pt.Specification).LongName, comparer)).ToMaybe(); + s.Key.MatchName(((OptionSpecification)pt.Specification).ShortName, ((OptionSpecification)pt.Specification).LongNames, comparer)).ToMaybe(); if (matched.IsJust()) { var matches = matched.GetValueOrDefault(Enumerable.Empty>>()); diff --git a/src/CommandLine/Core/OptionSpecification.cs b/src/CommandLine/Core/OptionSpecification.cs index 1c2e4f88..87e1199a 100644 --- a/src/CommandLine/Core/OptionSpecification.cs +++ b/src/CommandLine/Core/OptionSpecification.cs @@ -10,7 +10,7 @@ namespace CommandLine.Core sealed class OptionSpecification : Specification { private readonly string shortName; - private readonly string longName; + private readonly string[] longNames; private readonly char separator; private readonly string setName; private readonly string group; @@ -23,7 +23,21 @@ public OptionSpecification(string shortName, string longName, bool required, str required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, conversionType == typeof(int) && flagCounter ? TargetType.Switch : targetType, hidden) { this.shortName = shortName; - this.longName = longName; + this.longNames = new [] { longName }; + this.separator = separator; + this.setName = setName; + this.group = group; + this.flagCounter = flagCounter; + } + + public OptionSpecification(string shortName, string[] longNames, bool required, string setName, Maybe min, Maybe max, + char separator, Maybe defaultValue, string helpText, string metaValue, IEnumerable enumValues, + Type conversionType, TargetType targetType, string group, bool flagCounter = false, bool hidden = false) + : base(SpecificationType.Option, + required, min, max, defaultValue, helpText, metaValue, enumValues, conversionType, conversionType == typeof(int) && flagCounter ? TargetType.Switch : targetType, hidden) + { + this.shortName = shortName; + this.longNames = longNames; this.separator = separator; this.setName = setName; this.group = group; @@ -34,7 +48,7 @@ public static OptionSpecification FromAttribute(OptionAttribute attribute, Type { return new OptionSpecification( attribute.ShortName, - attribute.LongName, + attribute.LongNames, attribute.Required, attribute.SetName, attribute.Min == -1 ? Maybe.Nothing() : Maybe.Just(attribute.Min), @@ -57,14 +71,20 @@ public static OptionSpecification NewSwitch(string shortName, string longName, b '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, string.Empty, false, hidden); } + public static OptionSpecification NewSwitch(string shortName, string[] longNames, bool required, string helpText, string metaValue, bool hidden = false) + { + return new OptionSpecification(shortName, longNames, required, string.Empty, Maybe.Nothing(), Maybe.Nothing(), + '\0', Maybe.Nothing(), helpText, metaValue, Enumerable.Empty(), typeof(bool), TargetType.Switch, string.Empty, false, hidden); + } + public string ShortName { get { return shortName; } } - public string LongName + public string[] LongNames { - get { return longName; } + get { return longNames; } } public char Separator diff --git a/src/CommandLine/Core/Specification.cs b/src/CommandLine/Core/Specification.cs index b95b998c..3fc12ca7 100644 --- a/src/CommandLine/Core/Specification.cs +++ b/src/CommandLine/Core/Specification.cs @@ -118,7 +118,7 @@ public static Specification FromProperty(PropertyInfo property) var spec = OptionSpecification.FromAttribute(oa.Single(), property.PropertyType, ReflectionHelper.GetNamesOfEnum(property.PropertyType)); - if (spec.ShortName.Length == 0 && spec.LongName.Length == 0) + if (spec.ShortName.Length == 0 && spec.LongNames.Length == 0) { return spec.WithLongName(property.Name.ToLowerInvariant()); } diff --git a/src/CommandLine/Core/SpecificationExtensions.cs b/src/CommandLine/Core/SpecificationExtensions.cs index c080e983..e7c796d8 100644 --- a/src/CommandLine/Core/SpecificationExtensions.cs +++ b/src/CommandLine/Core/SpecificationExtensions.cs @@ -22,7 +22,7 @@ public static OptionSpecification WithLongName(this OptionSpecification specific { return new OptionSpecification( specification.ShortName, - newLongName, + new [] { newLongName }, specification.Required, specification.SetName, specification.Min, @@ -41,7 +41,7 @@ public static OptionSpecification WithLongName(this OptionSpecification specific public static string UniqueName(this OptionSpecification specification) { - return specification.ShortName.Length > 0 ? specification.ShortName : specification.LongName; + return specification.ShortName.Length > 0 ? specification.ShortName : specification.LongNames[0]; } public static IEnumerable ThrowingValidate(this IEnumerable specifications, IEnumerable, string>> guardsLookup) diff --git a/src/CommandLine/Core/SpecificationGuards.cs b/src/CommandLine/Core/SpecificationGuards.cs index b6d7d122..f1fbd1f0 100644 --- a/src/CommandLine/Core/SpecificationGuards.cs +++ b/src/CommandLine/Core/SpecificationGuards.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using CSharpx; namespace CommandLine.Core @@ -30,7 +31,11 @@ private static Func GuardAgainstSequenceWithWrongRange() private static Func GuardAgainstOneCharLongName() { - return spec => spec.IsOption() && ((OptionSpecification)spec).LongName.Length == 1; + return spec => + { + var optionSpecification = spec as OptionSpecification; + return spec.IsOption() && (optionSpecification?.LongNames.Any(x => x.Length == 1) ?? false); + }; } private static Func GuardAgainstSequenceWithZeroRange() diff --git a/src/CommandLine/Core/SpecificationPropertyRules.cs b/src/CommandLine/Core/SpecificationPropertyRules.cs index 4f8b78a9..fcf268bd 100644 --- a/src/CommandLine/Core/SpecificationPropertyRules.cs +++ b/src/CommandLine/Core/SpecificationPropertyRules.cs @@ -48,7 +48,7 @@ where o.Group.Length > 0 if (options.Any()) { return from o in options - select new GroupOptionAmbiguityError(new NameInfo(o.ShortName, o.LongName)); + select new GroupOptionAmbiguityError(new NameInfo(o.ShortName, o.LongNames)); } return Enumerable.Empty(); @@ -79,7 +79,7 @@ group o by o.Option.Group into g if (errorGroups.Any()) { - return errorGroups.Select(gr => new MissingGroupOptionError(gr.Key, gr.Select(g => new NameInfo(g.Option.ShortName, g.Option.LongName)))); + return errorGroups.Select(gr => new MissingGroupOptionError(gr.Key, gr.Select(g => new NameInfo(g.Option.ShortName, g.Option.LongNames)))); } return Enumerable.Empty(); @@ -199,20 +199,19 @@ where t.IsName() join o in specs on t.Text equals o.ShortName into to from o in to.DefaultIfEmpty() where o != null - select new { o.ShortName, o.LongName }; + select new { o.ShortName, o.LongNames }; 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 }; + from o in specs + where o.LongNames.Contains(t.Text) + select new { o.ShortName, o.LongNames }; var groups = from x in shortOptions.Concat(longOptions) group x by x into g let count = g.Count() select new { Value = g.Key, Count = count }; var errors = from y in groups where y.Count > 1 - select new RepeatedOptionError(new NameInfo(y.Value.ShortName, y.Value.LongName)); + select new RepeatedOptionError(new NameInfo(y.Value.ShortName, y.Value.LongNames)); return errors; }; } diff --git a/src/CommandLine/Core/TypeLookup.cs b/src/CommandLine/Core/TypeLookup.cs index 24515a4f..d78bdc48 100644 --- a/src/CommandLine/Core/TypeLookup.cs +++ b/src/CommandLine/Core/TypeLookup.cs @@ -15,7 +15,7 @@ public static Maybe FindTypeDescriptorAndSibling( StringComparer comparer) { var info = - specifications.SingleOrDefault(a => name.MatchName(a.ShortName, a.LongName, comparer)) + specifications.SingleOrDefault(a => name.MatchName(a.ShortName, a.LongNames, comparer)) .ToMaybe() .Map( first => @@ -31,4 +31,4 @@ public static Maybe FindTypeDescriptorAndSibling( } } -} \ No newline at end of file +} diff --git a/src/CommandLine/NameInfo.cs b/src/CommandLine/NameInfo.cs index baf259ef..9725406d 100644 --- a/src/CommandLine/NameInfo.cs +++ b/src/CommandLine/NameInfo.cs @@ -1,7 +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 CommandLine.Core; +using System.Linq; namespace CommandLine { @@ -14,16 +14,40 @@ public sealed class NameInfo : IEquatable /// Represents an empty name information. Used when are tied to values, /// rather than options. /// - public static readonly NameInfo EmptyName = new NameInfo(string.Empty, string.Empty); - private readonly string longName; + public static readonly NameInfo EmptyName = new NameInfo(string.Empty, new string[0]); + private readonly string[] longNames; private readonly string shortName; + internal NameInfo(string shortName) + { + if (shortName == null) throw new ArgumentNullException("shortName"); + + this.longNames = new string[0]; + this.shortName = shortName; + } + internal NameInfo(string shortName, string longName) { if (shortName == null) throw new ArgumentNullException("shortName"); if (longName == null) throw new ArgumentNullException("longName"); + if (longName == string.Empty) + { + this.longNames = new string[0]; + } + else + { + this.longNames = new [] { longName }; + } - this.longName = longName; + this.shortName = shortName; + } + + internal NameInfo(string shortName, string[] longNames) + { + if (shortName == null) throw new ArgumentNullException("shortName"); + if (longNames == null) throw new ArgumentNullException("longNames"); + if (longNames.Any(x => x == null)) throw new ArgumentNullException("longNames"); + this.longNames = longNames; this.shortName = shortName; } @@ -38,9 +62,9 @@ public string ShortName /// /// Gets the long name of the name information. /// - public string LongName + public string[] LongNames { - get { return longName; } + get { return longNames; } } /// @@ -50,11 +74,11 @@ public string NameText { get { - return ShortName.Length > 0 && LongName.Length > 0 - ? ShortName + ", " + LongName + return ShortName.Length > 0 && LongNames.Length > 0 + ? ShortName + ", " + string.Join(", ", LongNames) : ShortName.Length > 0 ? ShortName - : LongName; + : string.Join(", ", LongNames); } } @@ -80,7 +104,7 @@ public override bool Equals(object obj) /// A hash code for the current . public override int GetHashCode() { - return new { ShortName, LongName }.GetHashCode(); + return CSharpx.EnumerableExtensions.Prepend(LongNames, ShortName).ToArray().GetHashCode(); } /// @@ -95,7 +119,7 @@ public bool Equals(NameInfo other) return false; } - return ShortName.Equals(other.ShortName) && LongName.Equals(other.LongName); + return ShortName.Equals(other.ShortName) && LongNames.SequenceEqual(other.LongNames); } } -} \ No newline at end of file +} diff --git a/src/CommandLine/OptionAttribute.cs b/src/CommandLine/OptionAttribute.cs index 6ae51dac..b7f6d22d 100644 --- a/src/CommandLine/OptionAttribute.cs +++ b/src/CommandLine/OptionAttribute.cs @@ -12,20 +12,20 @@ namespace CommandLine [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class OptionAttribute : BaseAttribute { - private readonly string longName; + private readonly string[] longNames; private readonly string shortName; private string setName; private bool flagCounter; private char separator; private string group=string.Empty; - private OptionAttribute(string shortName, string longName) : base() + private OptionAttribute(string shortName, string[] longNames) : base() { if (shortName == null) throw new ArgumentNullException("shortName"); - if (longName == null) throw new ArgumentNullException("longName"); + if (longNames == null) throw new ArgumentNullException("longNames"); this.shortName = shortName; - this.longName = longName; + this.longNames = longNames; setName = string.Empty; separator = '\0'; } @@ -35,7 +35,7 @@ private OptionAttribute(string shortName, string longName) : base() /// The default long name will be inferred from target property. /// public OptionAttribute() - : this(string.Empty, string.Empty) + : this(string.Empty, new string[0]) { } @@ -44,17 +44,35 @@ public OptionAttribute() /// /// The long name of the option. public OptionAttribute(string longName) - : this(string.Empty, longName) + : this(string.Empty, new []{ longName }) { } + /// + /// Initializes a new instance of the class. + /// + /// The long name of the option. + public OptionAttribute(string[] longNames) + : this(string.Empty, longNames) + { + } /// /// Initializes a new instance of the class. /// /// The short name of the option. /// The long name of the option or null if not used. public OptionAttribute(char shortName, string longName) - : this(shortName.ToOneCharString(), longName) + : this(shortName.ToOneCharString(), new []{ longName }) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The short name of the option. + /// The long name of the option or null if not used. + public OptionAttribute(char shortName, string[] longNames) + : this(shortName.ToOneCharString(), longNames) { } @@ -63,16 +81,16 @@ public OptionAttribute(char shortName, string longName) /// /// The short name of the option.. public OptionAttribute(char shortName) - : this(shortName.ToOneCharString(), string.Empty) + : this(shortName.ToOneCharString(), new string[0]) { } /// /// Gets long name of this command line option. This name is usually a single english word. /// - public string LongName + public string[] LongNames { - get { return longName; } + get { return longNames; } } /// diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index f5e9a7b9..18c5c102 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -27,7 +27,7 @@ public struct ComparableOption public bool Required; public bool IsOption; public bool IsValue; - public string LongName; + public string[] LongNames; public string ShortName; public int Index; } @@ -48,7 +48,7 @@ ComparableOption ToComparableOption(Specification spec, int index) Required = required, IsOption = option != null, IsValue = value != null, - LongName = option?.LongName ?? value?.MetaName, + LongNames = option?.LongNames ?? new [] { value?.MetaName }, ShortName = option?.ShortName, Index = index }; @@ -70,8 +70,18 @@ ComparableOption ToComparableOption(Specification spec, int index) return 1; } - return String.Compare(attr1.LongName, attr2.LongName, StringComparison.Ordinal); + if (attr1.LongNames.Length != attr2.LongNames.Length) + { + return attr1.LongNames.Length.CompareTo(attr2.LongNames.Length); + } + for (int i = 0; i < attr1.LongNames.Length; i++) + { + var cmp = String.Compare(attr1.LongNames[i], attr2.LongNames[i], StringComparison.Ordinal); + if (cmp != 0) return cmp; + } + + return 0; } else if (attr1.IsOption && attr2.IsValue) { @@ -95,6 +105,8 @@ ComparableOption ToComparableOption(Specification spec, int index) /// The width of the option prefix (either "--" or " " /// private const int OptionPrefixWidth = 2; + + private const string CommaSeparation = ", "; /// /// The total amount of extra space that needs to accounted for when indenting Option help text /// @@ -852,7 +864,7 @@ private IEnumerable AdaptVerbsToSpecifications(IEnumerable select OptionSpecification.NewSwitch( string.Empty, - verbTuple.Item1.Name.Concat(verbTuple.Item1.Aliases).ToDelimitedString(", "), + verbTuple.Item1.Name.Concat(verbTuple.Item1.Aliases).ToDelimitedString(CommaSeparation), false, verbTuple.Item1.IsDefault ? "(Default Verb) " + verbTuple.Item1.HelpText : verbTuple.Item1.HelpText, //Default verb string.Empty, @@ -910,7 +922,7 @@ private OptionSpecification MakeHelpEntry() { return OptionSpecification.NewSwitch( string.Empty, - "help", + new [] { "help" }, false, sentenceBuilder.HelpCommandText(AddDashesToOption), string.Empty, @@ -921,7 +933,7 @@ private OptionSpecification MakeVersionEntry() { return OptionSpecification.NewSwitch( string.Empty, - "version", + new [] { "version" }, false, sentenceBuilder.VersionCommandText(AddDashesToOption), string.Empty, @@ -969,7 +981,7 @@ specification is OptionSpecification optionSpecification && var optionHelpText = specification.HelpText; if (addEnumValuesToHelpText && specification.EnumValues.Any()) - optionHelpText += " Valid values: " + string.Join(", ", specification.EnumValues); + optionHelpText += " Valid values: " + string.Join(CommaSeparation, specification.EnumValues); specification.DefaultValue.Do( defaultValue => optionHelpText = "(Default: {0}) ".FormatInvariant(FormatDefaultValue(defaultValue)) + optionHelpText); @@ -1006,13 +1018,25 @@ private string AddOptionName(int maxLength, OptionSpecification specification) .AppendWhen(addDashesToOption, '-') .AppendFormat("{0}", specification.ShortName) .AppendFormatWhen(specification.MetaValue.Length > 0, " {0}", specification.MetaValue) - .AppendWhen(specification.LongName.Length > 0, ", ")) + .AppendWhen(specification.LongNames.Length > 0, CommaSeparation)) .MapIf( - specification.LongName.Length > 0, - it => it + specification.LongNames.Length > 0, + it => + { + it .AppendWhen(addDashesToOption, "--") - .AppendFormat("{0}", specification.LongName) - .AppendFormatWhen(specification.MetaValue.Length > 0, "={0}", specification.MetaValue)) + .Append(specification.LongNames[0]) + .AppendFormatWhen(specification.MetaValue.Length > 0, "={0}", specification.MetaValue); + foreach (var longName in specification.LongNames.Skip(1)) + { + it + .Append(CommaSeparation) + .AppendWhen(addDashesToOption, "--") + .AppendFormat("{0}", longName) + .AppendFormatWhen(specification.MetaValue.Length > 0, "={0}", specification.MetaValue); + } + return it; + }) .ToString(); } @@ -1056,7 +1080,7 @@ private int GetMaxOptionLength(OptionSpecification spec) var specLength = 0; var hasShort = spec.ShortName.Length > 0; - var hasLong = spec.LongName.Length > 0; + var hasLong = spec.LongNames.Length > 0; var metaLength = 0; if (spec.MetaValue.Length > 0) @@ -1073,15 +1097,18 @@ private int GetMaxOptionLength(OptionSpecification spec) if (hasLong) { - specLength += spec.LongName.Length; + specLength += spec.LongNames.Sum(x => x.Length); if (AddDashesToOption) - specLength += OptionPrefixWidth; + specLength += OptionPrefixWidth * spec.LongNames.Length; - specLength += metaLength; + if (spec.LongNames.Length > 1) + specLength += CommaSeparation.Length * (spec.LongNames.Length - 1); + + specLength += metaLength * spec.LongNames.Length; } if (hasShort && hasLong) - specLength += OptionPrefixWidth; + specLength += CommaSeparation.Length; return specLength; } diff --git a/src/CommandLine/UnParserExtensions.cs b/src/CommandLine/UnParserExtensions.cs index e823a7fa..3dbe008e 100644 --- a/src/CommandLine/UnParserExtensions.cs +++ b/src/CommandLine/UnParserExtensions.cs @@ -266,12 +266,12 @@ private static string FormatName(this OptionSpecification optionSpec, object val { // 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.LongNames.Length > 0 && !settings.PreferShortName) || optionSpec.ShortName.Length == 0; var formattedName = new StringBuilder(longName - ? "--".JoinTo(optionSpec.LongName) + ? "--".JoinTo(optionSpec.LongNames[0]) : "-".JoinTo(optionSpec.ShortName)) .AppendWhen(optionSpec.TargetType != TargetType.Switch, longName && settings.UseEqualToken ? "=" : " ") .ToString(); diff --git a/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs b/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs index 9710d0de..e824745b 100644 --- a/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs +++ b/tests/CommandLine.Tests/Fakes/Verb_Fakes.cs @@ -35,7 +35,7 @@ public class Add_Verb_As_Default [Verb("commit", HelpText = "Record changes to the repository.")] public class Commit_Verb { - [Option('p', "patch", + [Option('p', new[] { "patch" }, HelpText = "Use the interactive patch selection interface to chose which changes to commit.")] public bool Patch { get; set; } @@ -119,4 +119,16 @@ class Default_Verb_With_Empty_Name [Option('t', "test")] public bool TestValue { get; set; } } + [Verb("multilong")] + public class Verb_With_Option_With_Several_Long_Names + { + [Option('d', new [] { "downloadfiles", "dlf", "df" }, HelpText = "Downloads files.")] + public bool DownloadFiles { get; set; } + + [Option(new [] { "rooturl", "ru" }, HelpText = "Root URL.")] + public string RootUrl { get; set; } + + [Option(new [] { "wm", "withmeta" }, HelpText = "With Meta.", MetaValue = "NUM")] + public string WithMeta { get; set; } + } } diff --git a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs index 2f8d02b7..b56f066a 100644 --- a/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs +++ b/tests/CommandLine.Tests/Unit/Core/InstanceBuilderTests.cs @@ -1264,6 +1264,21 @@ public void Parse_int_sequence_with_multi_instance() ((Parsed)result).Value.IntSequence.Should().BeEquivalentTo(expected); } + [Theory] + [InlineData(new[] { "--wm", "qwer" }, "qwer")] + [InlineData(new[] { "--withmeta", "qwer" }, "qwer")] + public void Parse_string_with_multiple_long_args(string[] arguments, string expected) + { + // Fixture setup in attributes + + // Exercize system + var result = InvokeBuild( + arguments); + + // Verify outcome + expected.Should().BeEquivalentTo(((Parsed)result).Value.WithMeta); + } + #region custom types diff --git a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs index 9811f7be..e943a16e 100644 --- a/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs +++ b/tests/CommandLine.Tests/Unit/Text/HelpTextTests.cs @@ -911,6 +911,36 @@ public void Options_Should_Render_Multiple_OptionGroups_When_Available() lines[7].Should().BeEquivalentTo("--version Display version information."); } + [Fact] + public void Invoke_AutoBuild_for_Verbs_with_multiple_long_names() + { + // Fixture setup + var fakeResult = new NotParsed( + TypeInfo.Create(typeof(NullInstance)), + new Error[] + { + new HelpVerbRequestedError("multilong", typeof(Verb_With_Option_With_Several_Long_Names), true) + }); + + // Exercize system + var helpText = HelpText.AutoBuild(fakeResult); + + // Verify outcome + var lines = helpText.ToString().ToLines().TrimStringArray(); + + lines[0].Should().Be(HeadingInfo.Default.ToString()); + lines[1].Should().Be(CopyrightInfo.Default.ToString()); + lines[2].Should().BeEmpty(); + lines[3].Should().BeEquivalentTo("-d, --downloadfiles, --dlf, --df Downloads files."); + lines[4].Should().BeEmpty(); + lines[5].Should().BeEquivalentTo("--rooturl, --ru Root URL."); + lines[6].Should().BeEmpty(); + lines[7].Should().BeEquivalentTo("--wm=NUM, --withmeta=NUM With Meta."); + lines[8].Should().BeEmpty(); + lines[9].Should().BeEquivalentTo("--help Display this help screen."); + // Teardown + } + #region Custom Help [Fact] diff --git a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs index 7e878f32..69c59b8d 100644 --- a/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs +++ b/tests/CommandLine.Tests/Unit/UnParserExtensionsTests.cs @@ -364,6 +364,57 @@ class Options_DateTimeOffset public DateTimeOffset Start { get; set; } } #endregion + + + #region Issue 885 + [Theory] + [MemberData(nameof(UnParseMultiLongArgsVerbs))] + public static void UnParsing_instance_with_multiple_long_args_returns_with_first_long_arg(Verb_With_Option_With_Several_Long_Names verb, string result) + { + new Parser() + .FormatCommandLine(verb) + .Should().BeEquivalentTo(result); + } + + [Theory] + [MemberData(nameof(UnParseMultiLongArgsVerbsPreferShort))] + public static void UnParsing_instance_with_multiple_long_args_returns_with_first_long_arg_prefer_short_name( + Verb_With_Option_With_Several_Long_Names verb, string result) + { + new Parser() + .FormatCommandLine(verb, settings => settings.PreferShortName = true) + .Should().BeEquivalentTo(result); + } + + public static IEnumerable UnParseMultiLongArgsVerbs + { + get + { + yield return new object[] { new Verb_With_Option_With_Several_Long_Names(), "multilong" }; + yield return new object[] { new Verb_With_Option_With_Several_Long_Names { DownloadFiles = true }, "multilong --downloadfiles" }; + yield return new object[] + { + new Verb_With_Option_With_Several_Long_Names { DownloadFiles = true, WithMeta = "qwert" }, + "multilong --downloadfiles --wm qwert" + }; + } + } + + public static IEnumerable UnParseMultiLongArgsVerbsPreferShort + { + get + { + yield return new object[] { new Verb_With_Option_With_Several_Long_Names(), "multilong" }; + yield return new object[] { new Verb_With_Option_With_Several_Long_Names { DownloadFiles = true }, "multilong -d" }; + yield return new object[] + { + new Verb_With_Option_With_Several_Long_Names { DownloadFiles = true, WithMeta = "qwert" }, + "multilong -d --wm qwert" + }; + } + } + + #endregion public static IEnumerable UnParseData { get