11using System . CodeDom . Compiler ;
22using System . Collections . Immutable ;
33using System . Text ;
4+ using System . Xml . Linq ;
45using Clap . Net . Extensions ;
56using Clap . Net . Models ;
67using Microsoft . CodeAnalysis ;
78using Microsoft . CodeAnalysis . CSharp . Syntax ;
89using Microsoft . CodeAnalysis . Text ;
10+ // ReSharper disable LoopCanBeConvertedToQuery
911
1012namespace 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}
0 commit comments