diff --git a/src/CommandLine/Text/HelpText.cs b/src/CommandLine/Text/HelpText.cs index 1306e969..b287a6e8 100644 --- a/src/CommandLine/Text/HelpText.cs +++ b/src/CommandLine/Text/HelpText.cs @@ -17,8 +17,72 @@ 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; + public bool IsOption; + public bool IsValue; + public string LongName; + public string ShortName; + public int Index; + } + public class HelpText { + + #region ordering + + ComparableOption ToComparableOption(Specification spec, int index) + { + OptionSpecification option = spec as OptionSpecification; + ValueSpecification value = spec as ValueSpecification; + bool required = option?.Required ?? false; + + return new ComparableOption() + { + Required = required, + IsOption = option != null, + IsValue = value != null, + LongName = option?.LongName ?? value?.MetaName, + ShortName = option?.ShortName, + Index = 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; + } + }; + + #endregion + private const int BuilderCapacity = 128; private const int DefaultMaximumLength = 80; // default console width /// @@ -240,6 +304,7 @@ 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, @@ -736,14 +801,37 @@ private HelpText AddOptionsImpl( int maximumLength) { var maxLength = GetMaxLength(specifications); + + optionsHelp = new StringBuilder(BuilderCapacity); var remainingSpace = maximumLength - (maxLength + TotalOptionPadding); - specifications.ForEach( - option => - AddOption(requiredWord, maxLength, option, remainingSpace)); + if (OptionComparison != null) + { + int i = -1; + var comparables = specifications.ToList().Select(s => + { + i++; + return ToComparableOption(s, i); + }).ToList(); + comparables.Sort(OptionComparison); + + + foreach (var comparable in comparables) + { + Specification spec = specifications.ElementAt(comparable.Index); + AddOption(requiredWord, maxLength, spec, remainingSpace); + } + } + else + { + specifications.ForEach( + option => + AddOption(requiredWord, maxLength, option, remainingSpace)); + + } return this; } @@ -953,5 +1041,3 @@ private static string FormatDefaultValue(T value) } } - - diff --git a/tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs b/tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs new file mode 100644 index 00000000..3896ab67 --- /dev/null +++ b/tests/CommandLine.Tests/Fakes/OPtions_HelpText_Ordering.cs @@ -0,0 +1,48 @@ +// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information. + +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace CommandLine.Tests.Fakes +{ + + [Verb("verb1")] + class Options_HelpText_Ordering_Verb1 + { + [Option('a', "alpha", Required = true)] + public string alphaOption { get; set; } + + [Option('b', "alpha2", Required = true)] + public string alphaTwoOption { get; set; } + + [Option('d', "charlie", Required = false)] + public string deltaOption { get; set; } + + [Option('c', "bravo", Required = false)] + public string charlieOption { get; set; } + + [Option('f', "foxtrot", Required = false)] + public string foxOption { get; set; } + + [Option('e', "echo", Required = false)] + public string echoOption { get; set; } + + [Value(0)] public string someExtraOption { get; set; } + } + + [Verb("verb2")] + class Options_HelpText_Ordering_Verb2 + { + [Option('a', "alpha", Required = true)] + public string alphaOption { get; set; } + + [Option('b', "alpha2", Required = true)] + public string alphaTwoOption { get; set; } + + [Option('c', "bravo", Required = false)] + public string charlieOption { get; set; } + + [Option('d', "charlie", Required = false)] + public string deltaOption { get; set; } + } +} diff --git a/tests/CommandLine.Tests/Unit/Issue482Tests.cs b/tests/CommandLine.Tests/Unit/Issue482Tests.cs new file mode 100644 index 00000000..61bc1f25 --- /dev/null +++ b/tests/CommandLine.Tests/Unit/Issue482Tests.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using CommandLine.Core; +using System.Linq; +using System.Reflection; +using CommandLine.Infrastructure; +using CommandLine.Tests.Fakes; +using CommandLine.Text; +using FluentAssertions; +using Xunit; +using System.Text; +using Xunit.Sdk; + +namespace CommandLine.Tests.Unit +{ + public class Issue482Tests + { + [Fact] + public void AutoBuild_without_ordering() + { + string expectedCompany = "Company"; + + + var parser = Parser.Default; + var parseResult = parser.ParseArguments( + new[] { "verb1", "--help" }) + .WithNotParsed(errors => { ; }) + .WithParsed(args => {; }); + + var message = HelpText.AutoBuild(parseResult, + error =>error, + ex => ex + ); + + string helpMessage = message.ToString(); + var helps = helpMessage.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(2).ToList(); + List expected = new List() + { + " -a, --alpha Required.", + " -b, --alpha2 Required.", + " -d, --charlie", + " -c, --bravo", + "-f, --foxtrot", + "-e, --echo", + "--help Display this help screen.", + "--version Display version information.", + "value pos. 0" + }; + Assert.Equal(expected.Count, helps.Count); + int i = 0; + foreach (var expect in expected) + { + Assert.Equal(expect.Trim(), helps[i].Trim()); + i++; + } + + ; + } + + [Fact] + public void AutoBuild_with_ordering() + { + string expectedCompany = "Company"; + + + var parser = Parser.Default; + var parseResult = parser.ParseArguments( + new[] { "verb1", "--help" }) + .WithNotParsed(errors => { ; }) + .WithParsed(args => {; }); + + Comparison comparison = HelpText.RequiredThenAlphaComparison; + + string message = HelpText.AutoBuild(parseResult, + error => + { + error.OptionComparison = HelpText.RequiredThenAlphaComparison; + return error; + }, + ex => ex); + + + string helpMessage = message.ToString(); + var helps = helpMessage.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(2).ToList(); + List expected = new List() + { + " -a, --alpha Required.", + " -b, --alpha2 Required.", + " -c, --bravo", + " -d, --charlie", + "-e, --echo", + "-f, --foxtrot", + "--help Display this help screen.", + "--version Display version information.", + "value pos. 0" + }; + Assert.Equal(expected.Count, helps.Count); + int i = 0; + foreach (var expect in expected) + { + Assert.Equal(expect.Trim(), helps[i].Trim()); + i++; + } + + ; + } + + [Fact] + public void AutoBuild_with_ordering_on_shortName() + { + string expectedCompany = "Company"; + + + var parser = Parser.Default; + var parseResult = parser.ParseArguments( + new[] { "verb1", "--help" }) + .WithNotParsed(errors => { ; }) + .WithParsed(args => {; }); + + Comparison orderOnShortName = (ComparableOption attr1, ComparableOption attr2) => + { + if (attr1.IsOption && attr2.IsOption) + { + if (attr1.Required && !attr2.Required) + { + return -1; + } + else if (!attr1.Required && attr2.Required) + { + return 1; + } + else + { + if (string.IsNullOrEmpty(attr1.ShortName) && !string.IsNullOrEmpty(attr2.ShortName)) + { + return 1; + } + else if (!string.IsNullOrEmpty(attr1.ShortName) && string.IsNullOrEmpty(attr2.ShortName)) + { + return -1; + } + return String.Compare(attr1.ShortName, attr2.ShortName, StringComparison.Ordinal); + } + } + else if (attr1.IsOption && attr2.IsValue) + { + return -1; + } + else + { + return 1; + } + }; + + string message = HelpText.AutoBuild(parseResult, + error => + { + error.OptionComparison = orderOnShortName; + return error; + }, + ex => ex, + false, + 80 + ); + + + var helps = message.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(2).ToList(); + List expected = new List() + { + " -a, --alpha Required.", + " -b, --alpha2 Required.", + " -c, --bravo", + " -d, --charlie", + "-e, --echo", + "-f, --foxtrot", + "--help Display this help screen.", + "--version Display version information.", + "value pos. 0" + }; + Assert.Equal(expected.Count, helps.Count); + int i = 0; + foreach (var expect in expected) + { + Assert.Equal(expect.Trim(), helps[i].Trim()); + i++; + } + } + + + } +}