Upstream Tyrrrz/CliFx state of the touched files before 9ef5dcb635
This commit is contained in:
commit
e3e64a40e4
6 files changed
+2165
No files matched your search
@@ -0,0 +1,432 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using CliFx.Binding;
|
||||
using CliFx.Infrastructure;
|
||||
|
||||
namespace CliFx.Help;
|
||||
|
||||
internal class HelpWriter(HelpContext context, ConsoleWriter consoleWriter)
|
||||
: FormattedConsoleWriter(consoleWriter)
|
||||
{
|
||||
private void WriteHeader(string text)
|
||||
{
|
||||
Write(ConsoleColor.White, text.ToUpperInvariant());
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
private void WriteCommandInvocation()
|
||||
{
|
||||
Write(context.Metadata.ExecutableName);
|
||||
|
||||
// Command name
|
||||
if (!string.IsNullOrWhiteSpace(context.Command.Name))
|
||||
{
|
||||
Write(' ');
|
||||
Write(ConsoleColor.Cyan, context.Command.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCommandUsage()
|
||||
{
|
||||
if (!IsEmpty)
|
||||
WriteVerticalMargin();
|
||||
|
||||
WriteHeader("Usage");
|
||||
|
||||
// Current command
|
||||
{
|
||||
WriteHorizontalMargin();
|
||||
|
||||
WriteCommandInvocation();
|
||||
Write(' ');
|
||||
|
||||
// Parameters
|
||||
foreach (var parameter in context.Command.Parameters.OrderBy(p => p.Order))
|
||||
{
|
||||
Write(ConsoleColor.DarkCyan, parameter.ToString(includeKind: false));
|
||||
Write(' ');
|
||||
}
|
||||
|
||||
// Required options
|
||||
foreach (var option in context.Command.Options.Where(o => o.IsRequired))
|
||||
{
|
||||
Write(
|
||||
ConsoleColor.Yellow,
|
||||
option.ToString(
|
||||
includeKind: false,
|
||||
includeBothIdentifiers: false,
|
||||
includeValuePlaceholder: true
|
||||
)
|
||||
);
|
||||
Write(' ');
|
||||
}
|
||||
|
||||
// Placeholder for non-required options
|
||||
if (context.Command.Options.Any(o => !o.IsRequired))
|
||||
{
|
||||
Write(ConsoleColor.Yellow, "[options]");
|
||||
}
|
||||
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
// Child commands
|
||||
if (context.Root.GetChildCommands(context.Command.Name).Any())
|
||||
{
|
||||
WriteHorizontalMargin();
|
||||
|
||||
WriteCommandInvocation();
|
||||
Write(' ');
|
||||
|
||||
// Placeholder for child command
|
||||
Write(ConsoleColor.Cyan, "[command]");
|
||||
Write(' ');
|
||||
|
||||
// Placeholder for other arguments
|
||||
Write("...");
|
||||
|
||||
WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCommandDescription()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(context.Command.Description))
|
||||
return;
|
||||
|
||||
if (!IsEmpty)
|
||||
WriteVerticalMargin();
|
||||
|
||||
WriteHeader("Description");
|
||||
|
||||
WriteHorizontalMargin();
|
||||
|
||||
Write(context.Command.Description);
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
private void WriteCommandInputDefaultValue(CommandInputDescriptor input)
|
||||
{
|
||||
var defaultValue = context.CommandDefaultValues.GetValueOrDefault(input);
|
||||
if (defaultValue is null)
|
||||
return;
|
||||
|
||||
// Normalize to an array to process both scalar and sequence default values uniformly
|
||||
var defaultValues =
|
||||
defaultValue is not string && defaultValue is IEnumerable defaultValueAsEnumerable
|
||||
? defaultValueAsEnumerable.Cast<object>().ToArray()
|
||||
: [defaultValue];
|
||||
|
||||
// Only strings, chars, bools, and types that implement IFormattable have
|
||||
// meaningful ToString() representations.
|
||||
if (!defaultValues.All(v => v is string or char or bool or IFormattable))
|
||||
return;
|
||||
|
||||
var isFirst = true;
|
||||
|
||||
foreach (var value in defaultValues)
|
||||
{
|
||||
if (isFirst)
|
||||
{
|
||||
Write(ConsoleColor.White, "Default: ");
|
||||
isFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(", ");
|
||||
}
|
||||
|
||||
Write(ConsoleColor.DarkGray, '<');
|
||||
Write(
|
||||
value switch
|
||||
{
|
||||
// Lowercase bools look better
|
||||
bool boolValue => boolValue.ToString().ToLowerInvariant(),
|
||||
IFormattable formattable => formattable.ToString(
|
||||
null,
|
||||
CultureInfo.InvariantCulture
|
||||
),
|
||||
_ => value.ToString(),
|
||||
}
|
||||
);
|
||||
Write(ConsoleColor.DarkGray, '>');
|
||||
}
|
||||
|
||||
if (!isFirst)
|
||||
Write('.');
|
||||
}
|
||||
|
||||
private void WriteCommandParameters()
|
||||
{
|
||||
if (!context.Command.Parameters.Any())
|
||||
return;
|
||||
|
||||
if (!IsEmpty)
|
||||
WriteVerticalMargin();
|
||||
|
||||
WriteHeader("Parameters");
|
||||
|
||||
foreach (var parameter in context.Command.Parameters.OrderBy(p => p.Order))
|
||||
{
|
||||
if (parameter.IsRequired)
|
||||
{
|
||||
Write(ConsoleColor.Red, "* ");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteHorizontalMargin();
|
||||
}
|
||||
|
||||
Write(ConsoleColor.DarkCyan, $"{parameter.Name}");
|
||||
|
||||
WriteColumnMargin();
|
||||
|
||||
// Description
|
||||
if (!string.IsNullOrWhiteSpace(parameter.Description))
|
||||
{
|
||||
Write(parameter.Description);
|
||||
Write(' ');
|
||||
}
|
||||
|
||||
// Valid values
|
||||
var validValues = parameter.Property.TryGetValidValues() ?? [];
|
||||
if (validValues.Any())
|
||||
{
|
||||
Write(ConsoleColor.White, "Choices: ");
|
||||
|
||||
var isFirst = true;
|
||||
foreach (var validValue in validValues)
|
||||
{
|
||||
if (validValue is null)
|
||||
continue;
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(", ");
|
||||
}
|
||||
|
||||
Write('"');
|
||||
Write(validValue.ToString());
|
||||
Write('"');
|
||||
}
|
||||
|
||||
Write('.');
|
||||
Write(' ');
|
||||
}
|
||||
|
||||
// Default value
|
||||
if (!parameter.IsRequired)
|
||||
{
|
||||
WriteCommandInputDefaultValue(parameter);
|
||||
}
|
||||
|
||||
WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCommandOptions()
|
||||
{
|
||||
if (!IsEmpty)
|
||||
WriteVerticalMargin();
|
||||
|
||||
WriteHeader("Options");
|
||||
|
||||
foreach (var option in context.Command.Options.OrderByDescending(o => o.IsRequired))
|
||||
{
|
||||
if (option.IsRequired)
|
||||
{
|
||||
Write(ConsoleColor.Red, "* ");
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteHorizontalMargin();
|
||||
}
|
||||
|
||||
// Short name
|
||||
if (option.ShortName is not null)
|
||||
{
|
||||
Write(ConsoleColor.Yellow, $"-{option.ShortName}");
|
||||
}
|
||||
|
||||
// Separator
|
||||
if (!string.IsNullOrWhiteSpace(option.Name) && option.ShortName is not null)
|
||||
{
|
||||
Write('|');
|
||||
}
|
||||
|
||||
// Name
|
||||
if (!string.IsNullOrWhiteSpace(option.Name))
|
||||
{
|
||||
Write(ConsoleColor.Yellow, $"--{option.Name}");
|
||||
}
|
||||
|
||||
WriteColumnMargin();
|
||||
|
||||
// Description
|
||||
if (!string.IsNullOrWhiteSpace(option.Description))
|
||||
{
|
||||
Write(option.Description);
|
||||
Write(' ');
|
||||
}
|
||||
|
||||
// Valid values
|
||||
var validValues = option.Property.TryGetValidValues() ?? [];
|
||||
if (validValues.Any())
|
||||
{
|
||||
Write(ConsoleColor.White, "Choices: ");
|
||||
|
||||
var isFirst = true;
|
||||
foreach (var validValue in validValues)
|
||||
{
|
||||
if (validValue is null)
|
||||
continue;
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(", ");
|
||||
}
|
||||
|
||||
Write('"');
|
||||
Write(validValue.ToString());
|
||||
Write('"');
|
||||
}
|
||||
|
||||
Write('.');
|
||||
Write(' ');
|
||||
}
|
||||
|
||||
// Environment variable
|
||||
if (!string.IsNullOrWhiteSpace(option.EnvironmentVariable))
|
||||
{
|
||||
Write(ConsoleColor.White, "Environment variable: ");
|
||||
Write(option.EnvironmentVariable);
|
||||
Write('.');
|
||||
Write(' ');
|
||||
}
|
||||
|
||||
// Default value
|
||||
if (!option.IsRequired)
|
||||
{
|
||||
WriteCommandInputDefaultValue(option);
|
||||
}
|
||||
|
||||
WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteCommandChildren()
|
||||
{
|
||||
var childCommands = context
|
||||
.Root.GetChildCommands(context.Command.Name)
|
||||
.OrderBy(a => a.Name, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
if (!childCommands.Any())
|
||||
return;
|
||||
|
||||
if (!IsEmpty)
|
||||
WriteVerticalMargin();
|
||||
|
||||
WriteHeader("Commands");
|
||||
|
||||
foreach (var childCommand in childCommands)
|
||||
{
|
||||
// Name
|
||||
WriteHorizontalMargin();
|
||||
Write(
|
||||
ConsoleColor.Cyan,
|
||||
// Trim the name so it's relative to the current command
|
||||
childCommand.Name?.Substring(context.Command.Name?.Length ?? 0).Trim()
|
||||
);
|
||||
|
||||
WriteColumnMargin();
|
||||
|
||||
// Description
|
||||
if (!string.IsNullOrWhiteSpace(childCommand.Description))
|
||||
{
|
||||
Write(childCommand.Description);
|
||||
Write(' ');
|
||||
}
|
||||
|
||||
// Grand-child commands
|
||||
var grandChildCommands = context
|
||||
.Root.GetChildCommands(childCommand.Name)
|
||||
.OrderBy(c => c.Name, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
if (grandChildCommands.Any())
|
||||
{
|
||||
Write(ConsoleColor.White, "Subcommands: ");
|
||||
|
||||
var isFirst = true;
|
||||
foreach (var grandChildCommand in grandChildCommands)
|
||||
{
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(", ");
|
||||
}
|
||||
|
||||
Write(
|
||||
ConsoleColor.Cyan,
|
||||
// Trim the name so it's relative to the current command
|
||||
grandChildCommand.Name?.Substring(context.Command.Name?.Length ?? 0).Trim()
|
||||
);
|
||||
}
|
||||
|
||||
Write('.');
|
||||
}
|
||||
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
// Child command help tip
|
||||
WriteVerticalMargin();
|
||||
Write("You can run \"");
|
||||
WriteCommandInvocation();
|
||||
Write(' ');
|
||||
Write(ConsoleColor.Cyan, "[command]");
|
||||
Write(' ');
|
||||
Write(ConsoleColor.White, "--help");
|
||||
Write("\" to show help for a specific command.");
|
||||
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
public void WriteHelpText()
|
||||
{
|
||||
// Application info
|
||||
Write(ConsoleColor.White, context.Metadata.Title);
|
||||
Write(' ');
|
||||
Write(ConsoleColor.Yellow, context.Metadata.Version);
|
||||
WriteLine();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(context.Metadata.Description))
|
||||
{
|
||||
WriteHorizontalMargin();
|
||||
Write(context.Metadata.Description);
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
// Command info
|
||||
WriteCommandUsage();
|
||||
WriteCommandDescription();
|
||||
WriteCommandParameters();
|
||||
WriteCommandOptions();
|
||||
WriteCommandChildren();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user