Skip to content

Commit f956dd7

Browse files
b3b00moh-hassan
authored andcommitted
issue #482: reorder options in auto help text (#484)
* issue #482 : fix * Revert "issue #482 : fix" This reverts commit 1811ce4. * #482 rework * HelpText fluent API, initial * fluent API for help message options ordering * cleaning * correct UT * Revert "HelpText fluent API, initial" This reverts commit 113a21c. * correction after Pull-Request remarks * set comparison on error action * fix * cleaning
1 parent 2580024 commit f956dd7

File tree

3 files changed

+331
-5
lines changed

3 files changed

+331
-5
lines changed

src/CommandLine/Text/HelpText.cs

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,72 @@ namespace CommandLine.Text
1717
/// Provides means to format an help screen.
1818
/// You can assign it in place of a <see cref="System.String"/> instance.
1919
/// </summary>
20+
21+
22+
23+
public struct ComparableOption
24+
{
25+
public bool Required;
26+
public bool IsOption;
27+
public bool IsValue;
28+
public string LongName;
29+
public string ShortName;
30+
public int Index;
31+
}
32+
2033
public class HelpText
2134
{
35+
36+
#region ordering
37+
38+
ComparableOption ToComparableOption(Specification spec, int index)
39+
{
40+
OptionSpecification option = spec as OptionSpecification;
41+
ValueSpecification value = spec as ValueSpecification;
42+
bool required = option?.Required ?? false;
43+
44+
return new ComparableOption()
45+
{
46+
Required = required,
47+
IsOption = option != null,
48+
IsValue = value != null,
49+
LongName = option?.LongName ?? value?.MetaName,
50+
ShortName = option?.ShortName,
51+
Index = index
52+
};
53+
}
54+
55+
56+
public Comparison<ComparableOption> OptionComparison { get; set; } = null;
57+
58+
public static Comparison<ComparableOption> RequiredThenAlphaComparison = (ComparableOption attr1, ComparableOption attr2) =>
59+
{
60+
if (attr1.IsOption && attr2.IsOption)
61+
{
62+
if (attr1.Required && !attr2.Required)
63+
{
64+
return -1;
65+
}
66+
else if (!attr1.Required && attr2.Required)
67+
{
68+
return 1;
69+
}
70+
71+
return String.Compare(attr1.LongName, attr2.LongName, StringComparison.Ordinal);
72+
73+
}
74+
else if (attr1.IsOption && attr2.IsValue)
75+
{
76+
return -1;
77+
}
78+
else
79+
{
80+
return 1;
81+
}
82+
};
83+
84+
#endregion
85+
2286
private const int BuilderCapacity = 128;
2387
private const int DefaultMaximumLength = 80; // default console width
2488
/// <summary>
@@ -240,6 +304,7 @@ public SentenceBuilder SentenceBuilder
240304
/// <param name='onExample'>A delegate used to customize <see cref="CommandLine.Text.Example"/> model used to render text block of usage examples.</param>
241305
/// <param name="verbsIndex">If true the output style is consistent with verb commands (no dashes), otherwise it outputs options.</param>
242306
/// <param name="maxDisplayWidth">The maximum width of the display.</param>
307+
/// <param name="comparison">a comparison lambda to order options in help text</param>
243308
/// <remarks>The parameter <paramref name="verbsIndex"/> is not ontly a metter of formatting, it controls whether to handle verbs or options.</remarks>
244309
public static HelpText AutoBuild<T>(
245310
ParserResult<T> parserResult,
@@ -736,14 +801,37 @@ private HelpText AddOptionsImpl(
736801
int maximumLength)
737802
{
738803
var maxLength = GetMaxLength(specifications);
804+
805+
739806

740807
optionsHelp = new StringBuilder(BuilderCapacity);
741808

742809
var remainingSpace = maximumLength - (maxLength + TotalOptionPadding);
743810

744-
specifications.ForEach(
745-
option =>
746-
AddOption(requiredWord, maxLength, option, remainingSpace));
811+
if (OptionComparison != null)
812+
{
813+
int i = -1;
814+
var comparables = specifications.ToList().Select(s =>
815+
{
816+
i++;
817+
return ToComparableOption(s, i);
818+
}).ToList();
819+
comparables.Sort(OptionComparison);
820+
821+
822+
foreach (var comparable in comparables)
823+
{
824+
Specification spec = specifications.ElementAt(comparable.Index);
825+
AddOption(requiredWord, maxLength, spec, remainingSpace);
826+
}
827+
}
828+
else
829+
{
830+
specifications.ForEach(
831+
option =>
832+
AddOption(requiredWord, maxLength, option, remainingSpace));
833+
834+
}
747835

748836
return this;
749837
}
@@ -953,5 +1041,3 @@ private static string FormatDefaultValue<T>(T value)
9531041

9541042
}
9551043
}
956-
957-
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2005-2015 Giacomo Stelluti Scala & Contributors. All rights reserved. See License.md in the project root for license information.
2+
3+
using System.Collections.Generic;
4+
using System.Runtime.CompilerServices;
5+
6+
namespace CommandLine.Tests.Fakes
7+
{
8+
9+
[Verb("verb1")]
10+
class Options_HelpText_Ordering_Verb1
11+
{
12+
[Option('a', "alpha", Required = true)]
13+
public string alphaOption { get; set; }
14+
15+
[Option('b', "alpha2", Required = true)]
16+
public string alphaTwoOption { get; set; }
17+
18+
[Option('d', "charlie", Required = false)]
19+
public string deltaOption { get; set; }
20+
21+
[Option('c', "bravo", Required = false)]
22+
public string charlieOption { get; set; }
23+
24+
[Option('f', "foxtrot", Required = false)]
25+
public string foxOption { get; set; }
26+
27+
[Option('e', "echo", Required = false)]
28+
public string echoOption { get; set; }
29+
30+
[Value(0)] public string someExtraOption { get; set; }
31+
}
32+
33+
[Verb("verb2")]
34+
class Options_HelpText_Ordering_Verb2
35+
{
36+
[Option('a', "alpha", Required = true)]
37+
public string alphaOption { get; set; }
38+
39+
[Option('b', "alpha2", Required = true)]
40+
public string alphaTwoOption { get; set; }
41+
42+
[Option('c', "bravo", Required = false)]
43+
public string charlieOption { get; set; }
44+
45+
[Option('d', "charlie", Required = false)]
46+
public string deltaOption { get; set; }
47+
}
48+
}
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using CommandLine.Core;
5+
using System.Linq;
6+
using System.Reflection;
7+
using CommandLine.Infrastructure;
8+
using CommandLine.Tests.Fakes;
9+
using CommandLine.Text;
10+
using FluentAssertions;
11+
using Xunit;
12+
using System.Text;
13+
using Xunit.Sdk;
14+
15+
namespace CommandLine.Tests.Unit
16+
{
17+
public class Issue482Tests
18+
{
19+
[Fact]
20+
public void AutoBuild_without_ordering()
21+
{
22+
string expectedCompany = "Company";
23+
24+
25+
var parser = Parser.Default;
26+
var parseResult = parser.ParseArguments<Options_HelpText_Ordering_Verb1, Options_HelpText_Ordering_Verb2>(
27+
new[] { "verb1", "--help" })
28+
.WithNotParsed(errors => { ; })
29+
.WithParsed(args => {; });
30+
31+
var message = HelpText.AutoBuild(parseResult,
32+
error =>error,
33+
ex => ex
34+
);
35+
36+
string helpMessage = message.ToString();
37+
var helps = helpMessage.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(2).ToList<string>();
38+
List<string> expected = new List<string>()
39+
{
40+
" -a, --alpha Required.",
41+
" -b, --alpha2 Required.",
42+
" -d, --charlie",
43+
" -c, --bravo",
44+
"-f, --foxtrot",
45+
"-e, --echo",
46+
"--help Display this help screen.",
47+
"--version Display version information.",
48+
"value pos. 0"
49+
};
50+
Assert.Equal(expected.Count, helps.Count);
51+
int i = 0;
52+
foreach (var expect in expected)
53+
{
54+
Assert.Equal(expect.Trim(), helps[i].Trim());
55+
i++;
56+
}
57+
58+
;
59+
}
60+
61+
[Fact]
62+
public void AutoBuild_with_ordering()
63+
{
64+
string expectedCompany = "Company";
65+
66+
67+
var parser = Parser.Default;
68+
var parseResult = parser.ParseArguments<Options_HelpText_Ordering_Verb1, Options_HelpText_Ordering_Verb2>(
69+
new[] { "verb1", "--help" })
70+
.WithNotParsed(errors => { ; })
71+
.WithParsed(args => {; });
72+
73+
Comparison<ComparableOption> comparison = HelpText.RequiredThenAlphaComparison;
74+
75+
string message = HelpText.AutoBuild(parseResult,
76+
error =>
77+
{
78+
error.OptionComparison = HelpText.RequiredThenAlphaComparison;
79+
return error;
80+
},
81+
ex => ex);
82+
83+
84+
string helpMessage = message.ToString();
85+
var helps = helpMessage.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(2).ToList<string>();
86+
List<string> expected = new List<string>()
87+
{
88+
" -a, --alpha Required.",
89+
" -b, --alpha2 Required.",
90+
" -c, --bravo",
91+
" -d, --charlie",
92+
"-e, --echo",
93+
"-f, --foxtrot",
94+
"--help Display this help screen.",
95+
"--version Display version information.",
96+
"value pos. 0"
97+
};
98+
Assert.Equal(expected.Count, helps.Count);
99+
int i = 0;
100+
foreach (var expect in expected)
101+
{
102+
Assert.Equal(expect.Trim(), helps[i].Trim());
103+
i++;
104+
}
105+
106+
;
107+
}
108+
109+
[Fact]
110+
public void AutoBuild_with_ordering_on_shortName()
111+
{
112+
string expectedCompany = "Company";
113+
114+
115+
var parser = Parser.Default;
116+
var parseResult = parser.ParseArguments<Options_HelpText_Ordering_Verb1, Options_HelpText_Ordering_Verb2>(
117+
new[] { "verb1", "--help" })
118+
.WithNotParsed(errors => { ; })
119+
.WithParsed(args => {; });
120+
121+
Comparison<ComparableOption> orderOnShortName = (ComparableOption attr1, ComparableOption attr2) =>
122+
{
123+
if (attr1.IsOption && attr2.IsOption)
124+
{
125+
if (attr1.Required && !attr2.Required)
126+
{
127+
return -1;
128+
}
129+
else if (!attr1.Required && attr2.Required)
130+
{
131+
return 1;
132+
}
133+
else
134+
{
135+
if (string.IsNullOrEmpty(attr1.ShortName) && !string.IsNullOrEmpty(attr2.ShortName))
136+
{
137+
return 1;
138+
}
139+
else if (!string.IsNullOrEmpty(attr1.ShortName) && string.IsNullOrEmpty(attr2.ShortName))
140+
{
141+
return -1;
142+
}
143+
return String.Compare(attr1.ShortName, attr2.ShortName, StringComparison.Ordinal);
144+
}
145+
}
146+
else if (attr1.IsOption && attr2.IsValue)
147+
{
148+
return -1;
149+
}
150+
else
151+
{
152+
return 1;
153+
}
154+
};
155+
156+
string message = HelpText.AutoBuild(parseResult,
157+
error =>
158+
{
159+
error.OptionComparison = orderOnShortName;
160+
return error;
161+
},
162+
ex => ex,
163+
false,
164+
80
165+
);
166+
167+
168+
var helps = message.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(2).ToList<string>();
169+
List<string> expected = new List<string>()
170+
{
171+
" -a, --alpha Required.",
172+
" -b, --alpha2 Required.",
173+
" -c, --bravo",
174+
" -d, --charlie",
175+
"-e, --echo",
176+
"-f, --foxtrot",
177+
"--help Display this help screen.",
178+
"--version Display version information.",
179+
"value pos. 0"
180+
};
181+
Assert.Equal(expected.Count, helps.Count);
182+
int i = 0;
183+
foreach (var expect in expected)
184+
{
185+
Assert.Equal(expect.Trim(), helps[i].Trim());
186+
i++;
187+
}
188+
}
189+
190+
191+
}
192+
}

0 commit comments

Comments
 (0)