Skip to content

Commit 9f162f8

Browse files
committed
Add a SectionAttribute to split the help screen into sections for better readability.
1 parent 860df77 commit 9f162f8

File tree

3 files changed

+49
-48
lines changed

3 files changed

+49
-48
lines changed

Src/Attributes.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using RT.Util;
1+
using RT.Util;
22
using RT.Util.Consoles;
33

44
namespace RT.CommandLine;
@@ -102,7 +102,7 @@ public class DocumentationLiteralAttribute(string documentation) : Documentation
102102
public class DocumentationEggsMLAttribute(string documentation) : DocumentationAttribute(documentation)
103103
{
104104
/// <summary>Gets a string describing the documentation format to the programmer (not seen by the users).</summary>
105-
public override string OriginalFormat { get { return "EggsML"; } }
105+
public override string OriginalFormat => "EggsML";
106106
/// <summary>
107107
/// Gets the console-colored documentation string. Note that this property may throw if the text couldn't be parsed
108108
/// where applicable.</summary>
@@ -119,7 +119,7 @@ public class DocumentationEggsMLAttribute(string documentation) : DocumentationA
119119
public class DocumentationRhoMLAttribute(string documentation) : DocumentationAttribute(documentation)
120120
{
121121
/// <summary>Gets a string describing the documentation format to the programmer (not seen by the users).</summary>
122-
public override string OriginalFormat { get { return "RhoML"; } }
122+
public override string OriginalFormat => "RhoML";
123123
/// <summary>
124124
/// Gets the console-colored documentation string. Note that this property may throw if the text couldn't be parsed
125125
/// where applicable.</summary>
@@ -131,8 +131,14 @@ public class DocumentationRhoMLAttribute(string documentation) : DocumentationAt
131131
/// Specifies that a specific command-line option should not be printed in help pages, i.e. the option should explicitly
132132
/// be undocumented.</summary>
133133
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
134-
public sealed class UndocumentedAttribute : Attribute
134+
public sealed class UndocumentedAttribute() : Attribute
135135
{
136-
/// <summary>Constructor.</summary>
137-
public UndocumentedAttribute() { }
136+
}
137+
138+
/// <summary>Adds a section header on the help screen above the option or parameter that has this attribute.</summary>
139+
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = true)]
140+
public sealed class SectionAttribute(string heading) : Attribute
141+
{
142+
/// <summary>Specifies the section heading.</summary>
143+
public string Heading { get; private set; } = heading;
138144
}

Src/CommandLineParser.cs

Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Diagnostics;
1+
using System.Diagnostics;
22
using System.Reflection;
33
using RT.Internal;
44
using RT.PostBuild;
@@ -116,23 +116,19 @@ namespace RT.CommandLine;
116116
/// the values in the enum type must also have documentation or <see cref="UndocumentedAttribute"/>, except
117117
/// for the enum value that corresponds to the field’s default value if the field is not mandatory.</para>
118118
/// <para>
119-
/// Documentation is provided in one of the following ways:</para>
119+
/// Documentation can be provided in one of the following ways:</para>
120120
/// <list type="bullet">
121121
/// <item><description>
122-
/// Monolingual, translation-agnostic (unlocalisable) applications use the <see
123-
/// cref="DocumentationAttribute"/> to specify documentation directly.</description></item>
122+
/// Use <see cref="DocumentationAttribute"/> to specify unformatted text directly.</description></item>
124123
/// <item><description>
125-
/// <para>
126-
/// Translatable applications must declare methods with the following signature:</para>
127-
/// <code>
128-
/// static string FieldNameDoc(Translation)</code>
129-
/// <para>
130-
/// The first parameter must be of the same type as the object passed in for the <c>applicationTr</c>
131-
/// parameter of <see cref="Parse"/>. The name of the method is the name of the field or enum value
132-
/// followed by <c>Doc</c>. The return value is the translated string.</para></description></item></list></description></item>
124+
/// Use <see cref="DocumentationEggsMLAttribute"/> or <see cref="DocumentationRhoMLAttribute"/> to format
125+
/// your text with coloration using one of the two markup systems.</description></item></list></description></item>
133126
/// <item><description>
134127
/// <see cref="IsPositionalAttribute"/> and <see cref="IsMandatoryAttribute"/> can be used together. However, a
135-
/// positional field can only be made mandatory if all the positional fields preceding it are also mandatory.</description></item></list></remarks>
128+
/// positional field can only be made mandatory if all the positional fields preceding it are also mandatory.</description></item></list>
129+
/// <para>
130+
/// <see cref="SectionAttribute"/> can be used to split the help screen into sections with headers for better
131+
/// readability.</para></remarks>
136132
public static class CommandLineParser
137133
{
138134
/// <summary>
@@ -589,21 +585,6 @@ private static Func<int, ConsoleColoredString> getHelpGenerator(Type type, Func<
589585
}
590586
helpString.Add(ConsoleColoredString.NewLine);
591587

592-
//
593-
// ## CONSTRUCT THE TABLES
594-
//
595-
596-
var anyCommandsWithSuboptions = false;
597-
var requiredParamsTable = new TextTable { MaxWidth = wrapWidth - leftMargin, ColumnSpacing = 3, RowSpacing = 1, LeftMargin = leftMargin };
598-
int requiredRow = 0;
599-
foreach (var f in mandatoryPositional.Select(fld => new { Positional = true, Field = fld }).Concat(mandatoryOptions.Select(fld => new { Positional = false, Field = fld })))
600-
anyCommandsWithSuboptions |= createParameterHelpRow(ref requiredRow, requiredParamsTable, f.Field, f.Positional, helpProcessor);
601-
602-
var optionalParamsTable = new TextTable { MaxWidth = wrapWidth - leftMargin, ColumnSpacing = 3, RowSpacing = 1, LeftMargin = leftMargin };
603-
int optionalRow = 0;
604-
foreach (var f in optionalPositional.Select(fld => new { Positional = true, Field = fld }).Concat(optionalOptions.Select(fld => new { Positional = false, Field = fld })))
605-
anyCommandsWithSuboptions |= createParameterHelpRow(ref optionalRow, optionalParamsTable, f.Field, f.Positional, helpProcessor);
606-
607588
// Word-wrap the documentation for the command (if any)
608589
var doc = getDocumentation(type, helpProcessor);
609590
foreach (var line in doc.WordWrap(wrapWidth))
@@ -612,26 +593,40 @@ private static Func<int, ConsoleColoredString> getHelpGenerator(Type type, Func<
612593
helpString.Add(ConsoleColoredString.NewLine);
613594
}
614595

615-
// Table of required parameters
616-
if (mandatoryOptions.Count > 0 || mandatoryPositional.Count > 0)
596+
597+
//
598+
// ## CONSTRUCT THE TABLES
599+
//
600+
601+
var anyCommandsWithSuboptions = false;
602+
var paramsTables = new List<(string heading, TextTable table)>();
603+
TextTable curTable = null;
604+
var curRow = 0;
605+
var lastMandatory = false;
606+
foreach (var (mandatory, positional, field) in mandatoryPositional.Select(fld => (mandatory: true, positional: true, field: fld))
607+
.Concat(mandatoryOptions.Select(fld => (mandatory: true, positional: false, field: fld)))
608+
.Concat(optionalPositional.Select(fld => (mandatory: false, positional: true, field: fld)))
609+
.Concat(optionalOptions.Select(fld => (mandatory: false, positional: false, field: fld))))
617610
{
618-
helpString.Add(ConsoleColoredString.NewLine);
619-
helpString.Add(new ConsoleColoredString("Required parameters:", CmdLineColor.HelpHeading));
620-
helpString.Add(ConsoleColoredString.NewLine);
621-
helpString.Add(ConsoleColoredString.NewLine);
622-
requiredParamsTable.RemoveEmptyColumns();
623-
helpString.Add(requiredParamsTable.ToColoredString());
611+
var section = field.GetCustomAttribute<SectionAttribute>();
612+
if (curTable == null || lastMandatory != mandatory || section != null)
613+
{
614+
curTable = new TextTable { MaxWidth = wrapWidth - leftMargin, ColumnSpacing = 3, RowSpacing = 1, LeftMargin = leftMargin };
615+
paramsTables.Add((section?.Heading ?? $"{(mandatory ? "Required" : "Optional")} parameters:", curTable));
616+
curRow = 0;
617+
}
618+
anyCommandsWithSuboptions |= createParameterHelpRow(ref curRow, curTable, field, positional, helpProcessor);
619+
lastMandatory = mandatory;
624620
}
625621

626-
// Table of optional parameters
627-
if (optionalOptions.Count > 0 || optionalPositional.Count > 0)
622+
foreach (var (heading, table) in paramsTables)
628623
{
629624
helpString.Add(ConsoleColoredString.NewLine);
630-
helpString.Add(new ConsoleColoredString("Optional parameters:", CmdLineColor.HelpHeading));
625+
helpString.Add(new ConsoleColoredString(heading, CmdLineColor.HelpHeading));
631626
helpString.Add(ConsoleColoredString.NewLine);
632627
helpString.Add(ConsoleColoredString.NewLine);
633-
optionalParamsTable.RemoveEmptyColumns();
634-
helpString.Add(optionalParamsTable.ToColoredString());
628+
table.RemoveEmptyColumns();
629+
helpString.Add(table.ToColoredString());
635630
}
636631

637632
// “This command accepts further arguments on the command line.”

0 commit comments

Comments
 (0)