Add an Examples string array property to CommandAttribute so command authors can document example invocations. Each example contains only the arguments that follow the command name; the executable and command name are prepended automatically when the example is rendered. Examples are rendered as extra lines in the USAGE section of the help text. The source generator resolves the values from the attribute and emits them into the command descriptor consumed at runtime.
92 lines
3.1 KiB
C#
92 lines
3.1 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using CliFx.Generators.Utils;
|
|
using CliFx.Generators.Utils.Extensions;
|
|
using Microsoft.CodeAnalysis;
|
|
using PowerKit.Extensions;
|
|
|
|
namespace CliFx.Generators.Binding;
|
|
|
|
internal partial record CommandSymbol(
|
|
INamedTypeSymbol Type,
|
|
string? Name,
|
|
string? Description,
|
|
IReadOnlyList<string> Examples,
|
|
IReadOnlyList<CommandInputSymbol> Inputs
|
|
)
|
|
{
|
|
public bool IsDefault => string.IsNullOrWhiteSpace(Name);
|
|
|
|
public IReadOnlyList<CommandParameterSymbol> Parameters { get; } =
|
|
Inputs.OfType<CommandParameterSymbol>().ToArray();
|
|
|
|
public IReadOnlyList<CommandOptionSymbol> Options { get; } =
|
|
Inputs.OfType<CommandOptionSymbol>().ToArray();
|
|
|
|
public override string ToString() => !string.IsNullOrWhiteSpace(Name) ? Name : "<default>";
|
|
}
|
|
|
|
internal partial record CommandSymbol
|
|
{
|
|
internal static CommandSymbol? TryResolve(INamedTypeSymbol type, DiagnosticReporter diagnostics)
|
|
{
|
|
// Must have the [Command] attribute
|
|
var attribute = type.TryGetAttribute("CliFx.Binding.CommandAttribute");
|
|
if (attribute is null)
|
|
{
|
|
// Shouldn't happen when called by the generator since it filters types by attribute,
|
|
// so not worth the effort to produce a diagnostic.
|
|
return null;
|
|
}
|
|
|
|
// Must be partial by itself, along with all containing types
|
|
foreach (var declaration in type.GetDeclarations().ToArray())
|
|
{
|
|
if (!declaration.IsFullyPartial())
|
|
{
|
|
diagnostics.Report(
|
|
DiagnosticDescriptors.CommandTypeMustBePartial,
|
|
declaration.Identifier.GetLocation(),
|
|
type.Name
|
|
);
|
|
}
|
|
}
|
|
|
|
// Must implement ICommand
|
|
if (!type.Implements("CliFx.ICommand"))
|
|
{
|
|
diagnostics.Report(
|
|
DiagnosticDescriptors.CommandTypeMustImplementICommand,
|
|
type.Locations.FirstOrDefault(),
|
|
type.Name
|
|
);
|
|
}
|
|
|
|
var name =
|
|
attribute?.NamedArguments.FirstOrDefault(a => a.Key == "Name").Value.Value as string
|
|
?? attribute
|
|
?.ConstructorArguments.Where(a => a.Type?.SpecialType == SpecialType.System_String)
|
|
.Select(a => a.Value as string)
|
|
.FirstOrDefault();
|
|
|
|
var description =
|
|
attribute?.NamedArguments.FirstOrDefault(a => a.Key == "Description").Value.Value
|
|
as string;
|
|
|
|
var examples =
|
|
attribute
|
|
?.NamedArguments.Where(a => a.Key == "Examples")
|
|
.SelectMany(a => a.Value.Values)
|
|
.Select(v => v.Value as string)
|
|
.WhereNotNull()
|
|
.ToArray()
|
|
?? [];
|
|
|
|
var properties = type.GetProperties().ToArray();
|
|
var parameters = CommandParameterSymbol.Resolve(properties, diagnostics);
|
|
var options = CommandOptionSymbol.Resolve(properties, diagnostics);
|
|
|
|
return new CommandSymbol(type, name, description, examples, [.. parameters, .. options]);
|
|
}
|
|
}
|