Skip to content

Commit 98dc935

Browse files
author
Simon Curtis
committed
Better handling of comments/about sections
1 parent c42b666 commit 98dc935

File tree

2 files changed

+78
-11
lines changed

2 files changed

+78
-11
lines changed

Clap.Net/ClapGenerator.cs

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
using System.CodeDom.Compiler;
22
using System.Collections.Immutable;
33
using System.Text;
4+
using System.Xml.Linq;
45
using Clap.Net.Extensions;
56
using Clap.Net.Models;
67
using Microsoft.CodeAnalysis;
78
using Microsoft.CodeAnalysis.CSharp.Syntax;
89
using Microsoft.CodeAnalysis.Text;
10+
// ReSharper disable LoopCanBeConvertedToQuery
911

1012
namespace Clap.Net;
1113

@@ -156,12 +158,8 @@ IPropertySymbol prop when (PropertyDeclarationSyntax)prop.DeclaringSyntaxReferen
156158
_ => false
157159
};
158160

159-
if (attributes.TryGetValue(nameof(CommandAttribute), out var command))
161+
if (attributes.TryGetValue(nameof(CommandAttribute), out _))
160162
{
161-
var name = command?.NamedArguments
162-
.FirstOrDefault(a => a.Key is nameof(CommandAttribute.Name))
163-
.Value.Value?.ToString() ?? member.Name;
164-
165163
subCommandArgumentModel = new SubCommandArgumentModel(
166164
member,
167165
memberType,
@@ -170,15 +168,18 @@ IPropertySymbol prop when (PropertyDeclarationSyntax)prop.DeclaringSyntaxReferen
170168
}
171169

172170
var variableName = GetVariableName(member.Name.ToCamelCase());
171+
var commentXml = ExtractSummary(member.GetDocumentationCommentXml());
173172

174173
if (!attributes.TryGetValue(nameof(ArgAttribute), out var argAttribute))
175174
{
175+
// Get the comment from the arg
176176
arguments.Add(
177177
new PositionalArgumentModel(
178178
member,
179179
memberType,
180180
variableName,
181181
defaultValue,
182+
commentXml,
182183
isRequired,
183184
positionalIndex++));
184185
continue;
@@ -195,21 +196,23 @@ IPropertySymbol prop when (PropertyDeclarationSyntax)prop.DeclaringSyntaxReferen
195196
_ => ArgAction.Set
196197
};
197198

199+
var helpText = argNamedArguments.GetOrDefault(nameof(ArgAttribute.Help)) as string ?? commentXml;
200+
198201
ArgumentModel argument = @short is not null || @long is not null
199202
? new NamedArgumentModel(
200203
member,
201204
memberType,
202205
variableName,
203206
defaultValue,
204-
Help: argNamedArguments.GetOrDefault(nameof(ArgAttribute.Help)) as string,
207+
Help: helpText,
205208
Short: @short,
206209
Long: @long,
207210
Env: argNamedArguments.GetOrDefault(nameof(ArgAttribute.Env)) as string,
208211
Negation: argNamedArguments.GetOrDefault(nameof(ArgAttribute.Negation)) as bool?,
209212
argAction,
210213
isRequired)
211214
: new PositionalArgumentModel(
212-
member, memberType, variableName, defaultValue, isRequired, positionalIndex++);
215+
member, memberType, variableName, defaultValue, helpText, isRequired, positionalIndex++);
213216

214217
arguments.Add(argument);
215218
}
@@ -218,7 +221,7 @@ IPropertySymbol prop when (PropertyDeclarationSyntax)prop.DeclaringSyntaxReferen
218221
.ToDictionary(a => a.Key, a => a.Value.Value);
219222

220223
var commentary = typeDeclarationSyntax is not null
221-
? ExtractSummary(typeDeclarationSyntax.ToFullString().AsSpan())
224+
? ExtractSummary(typeDeclarationSyntax)
222225
: null;
223226

224227
return new CommandModel(
@@ -1010,12 +1013,30 @@ private static string GenerateHelpMessage(
10101013
if (namedArguments.Any())
10111014
sb.Append(" [OPTIONS]");
10121015

1013-
foreach (var positional in commandModel.Arguments.OfType<PositionalArgumentModel>())
1014-
sb.Append($" <{positional.Symbol.Name.ToSnakeCase()}:{positional.MemberType.ToDisplayString()}>");
1016+
var positionalArgs = commandModel.Arguments
1017+
.OfType<PositionalArgumentModel>().
1018+
ToArray();
1019+
1020+
foreach (var positional in positionalArgs)
1021+
sb.Append($" <{positional.Symbol.Name}>");
10151022
sb.AppendLine();
10161023

10171024
var table = new List<string?[]>();
10181025

1026+
if (positionalArgs.Length > 0)
1027+
{
1028+
sb.AppendLine();
1029+
sb.AppendLine("Arguments:");
1030+
foreach (var positional in positionalArgs)
1031+
table.Add([$"<{positional.Symbol.Name}>", positional.Help]);
1032+
1033+
var maxArgLength = table.Max(o => o[0]?.Length ?? 0);
1034+
foreach (var row in table)
1035+
sb.AppendLine($" {row[0]?.PadRight(maxArgLength)} {row[1]}");
1036+
1037+
table.Clear();
1038+
}
1039+
10191040
foreach (var option in namedArguments)
10201041
{
10211042
var names = option switch
@@ -1037,10 +1058,10 @@ private static string GenerateHelpMessage(
10371058
sb.AppendLine("Options:");
10381059
foreach (var row in table)
10391060
sb.AppendLine($" {row[0]?.PadRight(maxColumnLength)} {row[1]}");
1061+
table.Clear();
10401062

10411063
if (subCommand is { Commands.Length: > 0 })
10421064
{
1043-
table.Clear();
10441065
foreach (var command in subCommand.Commands)
10451066
table.Add([command.Name, command.About]);
10461067

@@ -1086,6 +1107,42 @@ private static string GetFormattedHelpMessage()
10861107

10871108
private static readonly char[] AllowedCharacters = [' ', '\t', '\n', '\r', '&'];
10881109

1110+
private static (string About, string? LongAbout)? ExtractSummary(TypeDeclarationSyntax typeDecl)
1111+
{
1112+
// Get the XML doc trivia attached to the type
1113+
var trivia = typeDecl.GetLeadingTrivia()
1114+
.Select(t => t.GetStructure())
1115+
.OfType<DocumentationCommentTriviaSyntax>()
1116+
.FirstOrDefault();
1117+
1118+
if (trivia is null)
1119+
return null;
1120+
1121+
// Find the <summary> element
1122+
var summaryElement = trivia.Content
1123+
.OfType<XmlElementSyntax>()
1124+
.FirstOrDefault(e => e.StartTag.Name.LocalName.Text == "summary");
1125+
1126+
if (summaryElement is null)
1127+
return null;
1128+
1129+
// Extract the text inside <summary>
1130+
var lines = summaryElement.Content
1131+
.OfType<XmlTextSyntax>()
1132+
.SelectMany(x => x.TextTokens)
1133+
.Select(t => t.Text.Trim())
1134+
.Where(t => t.Length > 0)
1135+
.ToList();
1136+
1137+
if (lines.Count == 0)
1138+
return null;
1139+
1140+
return (
1141+
About: lines[0],
1142+
LongAbout: lines.Count == 1 ? null : string.Join("\n", lines)
1143+
);
1144+
}
1145+
10891146
private static (string About, string? LongAbout)? ExtractSummary(ReadOnlySpan<char> source)
10901147
{
10911148
const string summaryStartTag = "<summary>";
@@ -1159,4 +1216,13 @@ private static string GetHeader(CommandModel commandModel)
11591216
*/
11601217
""";
11611218
}
1219+
1220+
private static string? ExtractSummary(string? xmlDocs)
1221+
{
1222+
if (string.IsNullOrWhiteSpace(xmlDocs))
1223+
return null;
1224+
1225+
var doc = XDocument.Parse(xmlDocs);
1226+
return doc.Root?.Element("summary")?.Value.Trim();
1227+
}
11621228
}

Clap.Net/Models/PositionalArgumentModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ internal record PositionalArgumentModel(
77
ITypeSymbol MemberType,
88
string VariableName,
99
string? DefaultValue,
10+
string? Help,
1011
bool Required,
1112
int Index
1213
) : ArgumentModel(Symbol, MemberType, VariableName, Required, DefaultValue);

0 commit comments

Comments
 (0)