mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-08-02 18:17:30 +08:00
Allow custom help providers (#1259)
Allow custom help providers * Version option will show in help even with a default command * Reserve `-v` and `--version` as special Spectre.Console command line arguments (nb. breaking change for Spectre.Console users who have a default command with a settings class that uses either of these switches). * Help writer correctly determines if trailing commands exist and whether to display them as optional or mandatory in the usage statement. * Ability to control the number of indirect commands to display in the help text when the command itself doesn't have any examples of its own. Defaults to 5 (for backward compatibility) but can be set to any integer or zero to disable completely. * Significant increase in unit test coverage for the help writer. * Minor grammatical improvements to website documentation.
This commit is contained in:
@ -5,7 +5,42 @@ namespace Spectre.Console.Cli;
|
||||
/// and <see cref="IConfigurator{TSettings}"/>.
|
||||
/// </summary>
|
||||
public static class ConfiguratorExtensions
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the help provider for the application.
|
||||
/// </summary>
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <param name="helpProvider">The help provider to use.</param>
|
||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
||||
public static IConfigurator SetHelpProvider(this IConfigurator configurator, IHelpProvider helpProvider)
|
||||
{
|
||||
if (configurator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurator));
|
||||
}
|
||||
|
||||
configurator.SetHelpProvider(helpProvider);
|
||||
return configurator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the help provider for the application.
|
||||
/// </summary>
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
|
||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
||||
public static IConfigurator SetHelpProvider<T>(this IConfigurator configurator)
|
||||
where T : IHelpProvider
|
||||
{
|
||||
if (configurator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurator));
|
||||
}
|
||||
|
||||
configurator.SetHelpProvider<T>();
|
||||
return configurator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the name of the application.
|
||||
/// </summary>
|
||||
|
@ -1,7 +1,28 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
internal static class HelpWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// The help provider for Spectre.Console.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Other IHelpProvider implementations can be injected into the CommandApp, if desired.
|
||||
/// </remarks>
|
||||
public class HelpProvider : IHelpProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating how many examples from direct children to show in the help text.
|
||||
/// </summary>
|
||||
protected virtual int MaximumIndirectExamples { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether any default values for command options are shown in the help text.
|
||||
/// </summary>
|
||||
protected virtual bool ShowOptionDefaultValues { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a trailing period of a command description is trimmed in the help text.
|
||||
/// </summary>
|
||||
protected virtual bool TrimTrailingPeriod { get; }
|
||||
|
||||
private sealed class HelpArgument
|
||||
{
|
||||
public string Name { get; }
|
||||
@ -17,10 +38,10 @@ internal static class HelpWriter
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<HelpArgument> Get(CommandInfo? command)
|
||||
public static IReadOnlyList<HelpArgument> Get(ICommandInfo? command)
|
||||
{
|
||||
var arguments = new List<HelpArgument>();
|
||||
arguments.AddRange(command?.Parameters?.OfType<CommandArgument>()?.Select(
|
||||
arguments.AddRange(command?.Parameters?.OfType<ICommandArgument>()?.Select(
|
||||
x => new HelpArgument(x.Value, x.Position, x.Required, x.Description))
|
||||
?? Array.Empty<HelpArgument>());
|
||||
return arguments;
|
||||
@ -46,49 +67,75 @@ internal static class HelpWriter
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<HelpOption> Get(CommandModel model, CommandInfo? command)
|
||||
public static IReadOnlyList<HelpOption> Get(ICommandInfo? command)
|
||||
{
|
||||
var parameters = new List<HelpOption>();
|
||||
parameters.Add(new HelpOption("h", "help", null, null, "Prints help information", null));
|
||||
|
||||
// At the root and no default command?
|
||||
if (command == null && model?.DefaultCommand == null)
|
||||
{
|
||||
parameters.Add(new HelpOption("v", "version", null, null, "Prints version information", null));
|
||||
parameters.Add(new HelpOption("h", "help", null, null, "Prints help information", null));
|
||||
|
||||
// Version information applies to the entire application
|
||||
// Include the "-v" option in the help when at the root of the command line application
|
||||
// Don't allow the "-v" option if users have specified one or more sub-commands
|
||||
if ((command == null || command?.Parent == null) && !(command?.IsBranch ?? false))
|
||||
{
|
||||
parameters.Add(new HelpOption("v", "version", null, null, "Prints version information", null));
|
||||
}
|
||||
|
||||
parameters.AddRange(command?.Parameters.OfType<CommandOption>().Where(o => !o.IsHidden).Select(o =>
|
||||
parameters.AddRange(command?.Parameters.OfType<ICommandOption>().Where(o => !o.IsHidden).Select(o =>
|
||||
new HelpOption(
|
||||
o.ShortNames.FirstOrDefault(), o.LongNames.FirstOrDefault(),
|
||||
o.ValueName, o.ValueIsOptional, o.Description,
|
||||
o.ParameterKind == ParameterKind.Flag && o.DefaultValue?.Value is false ? null : o.DefaultValue?.Value))
|
||||
o.IsFlag && o.DefaultValue?.Value is false ? null : o.DefaultValue?.Value))
|
||||
?? Array.Empty<HelpOption>());
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<IRenderable> Write(CommandModel model, bool writeOptionsDefaultValues)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HelpProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="settings">The command line application settings used for configuration.</param>
|
||||
public HelpProvider(ICommandAppSettings settings)
|
||||
{
|
||||
this.ShowOptionDefaultValues = settings.ShowOptionDefaultValues;
|
||||
this.MaximumIndirectExamples = settings.MaximumIndirectExamples;
|
||||
this.TrimTrailingPeriod = settings.TrimTrailingPeriod;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
return WriteCommand(model, null, writeOptionsDefaultValues);
|
||||
}
|
||||
|
||||
public static IEnumerable<IRenderable> WriteCommand(CommandModel model, CommandInfo? command, bool writeOptionsDefaultValues)
|
||||
{
|
||||
var container = command as ICommandContainer ?? model;
|
||||
var isDefaultCommand = command?.IsDefaultCommand ?? false;
|
||||
|
||||
var result = new List<IRenderable>();
|
||||
result.AddRange(GetDescription(command));
|
||||
var result = new List<IRenderable>();
|
||||
|
||||
result.AddRange(GetHeader(model, command));
|
||||
result.AddRange(GetDescription(model, command));
|
||||
result.AddRange(GetUsage(model, command));
|
||||
result.AddRange(GetExamples(model, command));
|
||||
result.AddRange(GetArguments(command));
|
||||
result.AddRange(GetOptions(model, command, writeOptionsDefaultValues));
|
||||
result.AddRange(GetCommands(model, container, isDefaultCommand));
|
||||
result.AddRange(GetArguments(model, command));
|
||||
result.AddRange(GetOptions(model, command));
|
||||
result.AddRange(GetCommands(model, command));
|
||||
result.AddRange(GetFooter(model, command));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IEnumerable<IRenderable> GetDescription(CommandInfo? command)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the header for the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description section of the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetDescription(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
if (command?.Description == null)
|
||||
{
|
||||
@ -99,13 +146,19 @@ internal static class HelpWriter
|
||||
composer.Style("yellow", "DESCRIPTION:").LineBreak();
|
||||
composer.Text(command.Description).LineBreak();
|
||||
yield return composer.LineBreak();
|
||||
}
|
||||
|
||||
private static IEnumerable<IRenderable> GetUsage(CommandModel model, CommandInfo? command)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the usage section of the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
var composer = new Composer();
|
||||
composer.Style("yellow", "USAGE:").LineBreak();
|
||||
composer.Tab().Text(model.GetApplicationName());
|
||||
composer.Tab().Text(model.ApplicationName);
|
||||
|
||||
var parameters = new List<string>();
|
||||
|
||||
@ -132,18 +185,18 @@ internal static class HelpWriter
|
||||
}
|
||||
}
|
||||
|
||||
if (current.Parameters.OfType<CommandArgument>().Any())
|
||||
if (current.Parameters.OfType<ICommandArgument>().Any())
|
||||
{
|
||||
if (isCurrent)
|
||||
{
|
||||
foreach (var argument in current.Parameters.OfType<CommandArgument>()
|
||||
foreach (var argument in current.Parameters.OfType<ICommandArgument>()
|
||||
.Where(a => a.Required).OrderBy(a => a.Position).ToArray())
|
||||
{
|
||||
parameters.Add($"[aqua]<{argument.Value.EscapeMarkup()}>[/]");
|
||||
}
|
||||
}
|
||||
|
||||
var optionalArguments = current.Parameters.OfType<CommandArgument>().Where(x => !x.Required).ToArray();
|
||||
var optionalArguments = current.Parameters.OfType<ICommandArgument>().Where(x => !x.Required).ToArray();
|
||||
if (optionalArguments.Length > 0 || !isCurrent)
|
||||
{
|
||||
foreach (var optionalArgument in optionalArguments)
|
||||
@ -159,9 +212,27 @@ internal static class HelpWriter
|
||||
}
|
||||
}
|
||||
|
||||
if (command.IsBranch)
|
||||
{
|
||||
if (command.IsBranch && command.DefaultCommand == null)
|
||||
{
|
||||
// The user must specify the command
|
||||
parameters.Add("[aqua]<COMMAND>[/]");
|
||||
}
|
||||
else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0)
|
||||
{
|
||||
// We are on a branch with a default command
|
||||
// The user can optionally specify the command
|
||||
parameters.Add("[aqua][[COMMAND]][/]");
|
||||
}
|
||||
else if (command.IsDefaultCommand)
|
||||
{
|
||||
var commands = model.Commands.Where(x => !x.IsHidden && !x.IsDefaultCommand).ToList();
|
||||
|
||||
if (commands.Count > 0)
|
||||
{
|
||||
// Commands other than the default are present
|
||||
// So make these optional in the usage statement
|
||||
parameters.Add("[aqua][[COMMAND]][/]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,37 +243,48 @@ internal static class HelpWriter
|
||||
{
|
||||
composer,
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<IRenderable> GetExamples(CommandModel model, CommandInfo? command)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the examples section of the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
/// <remarks>
|
||||
/// Examples from the command's direct children are used
|
||||
/// if no examples have been set on the specified command or model.
|
||||
/// </remarks>
|
||||
public virtual IEnumerable<IRenderable> GetExamples(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
var maxExamples = int.MaxValue;
|
||||
|
||||
var examples = command?.Examples ?? model.Examples ?? new List<string[]>();
|
||||
var examples = command?.Examples?.ToList() ?? model.Examples?.ToList() ?? new List<string[]>();
|
||||
if (examples.Count == 0)
|
||||
{
|
||||
// Since we're not checking direct examples,
|
||||
// make sure that we limit the number of examples.
|
||||
maxExamples = 5;
|
||||
maxExamples = MaximumIndirectExamples;
|
||||
|
||||
// Get the current root command.
|
||||
var root = command ?? (ICommandContainer)model;
|
||||
var queue = new Queue<ICommandContainer>(new[] { root });
|
||||
// Start at the current command (if exists)
|
||||
// or alternatively commence at the model.
|
||||
var commandContainer = command ?? (ICommandContainer)model;
|
||||
var queue = new Queue<ICommandContainer>(new[] { commandContainer });
|
||||
|
||||
// Traverse the command tree and look for examples.
|
||||
// Traverse the command tree and look for examples.
|
||||
// As soon as a node contains commands, bail.
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var current = queue.Dequeue();
|
||||
|
||||
foreach (var cmd in current.Commands.Where(x => !x.IsHidden))
|
||||
foreach (var child in current.Commands.Where(x => !x.IsHidden))
|
||||
{
|
||||
if (cmd.Examples.Count > 0)
|
||||
if (child.Examples.Count > 0)
|
||||
{
|
||||
examples.AddRange(cmd.Examples);
|
||||
examples.AddRange(child.Examples);
|
||||
}
|
||||
|
||||
queue.Enqueue(cmd);
|
||||
queue.Enqueue(child);
|
||||
}
|
||||
|
||||
if (examples.Count >= maxExamples)
|
||||
@ -212,7 +294,7 @@ internal static class HelpWriter
|
||||
}
|
||||
}
|
||||
|
||||
if (examples.Count > 0)
|
||||
if (Math.Min(maxExamples, examples.Count) > 0)
|
||||
{
|
||||
var composer = new Composer();
|
||||
composer.LineBreak();
|
||||
@ -221,7 +303,7 @@ internal static class HelpWriter
|
||||
for (var index = 0; index < Math.Min(maxExamples, examples.Count); index++)
|
||||
{
|
||||
var args = string.Join(" ", examples[index]);
|
||||
composer.Tab().Text(model.GetApplicationName()).Space().Style("grey", args);
|
||||
composer.Tab().Text(model.ApplicationName).Space().Style("grey", args);
|
||||
composer.LineBreak();
|
||||
}
|
||||
|
||||
@ -229,9 +311,15 @@ internal static class HelpWriter
|
||||
}
|
||||
|
||||
return Array.Empty<IRenderable>();
|
||||
}
|
||||
|
||||
private static IEnumerable<IRenderable> GetArguments(CommandInfo? command)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the arguments section of the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetArguments(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
var arguments = HelpArgument.Get(command);
|
||||
if (arguments.Count == 0)
|
||||
@ -267,12 +355,18 @@ internal static class HelpWriter
|
||||
result.Add(grid);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IEnumerable<IRenderable> GetOptions(CommandModel model, CommandInfo? command, bool writeDefaultValues)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the options section of the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetOptions(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
// Collect all options into a single structure.
|
||||
var parameters = HelpOption.Get(model, command);
|
||||
var parameters = HelpOption.Get(command);
|
||||
if (parameters.Count == 0)
|
||||
{
|
||||
return Array.Empty<IRenderable>();
|
||||
@ -286,7 +380,7 @@ internal static class HelpWriter
|
||||
};
|
||||
|
||||
var helpOptions = parameters.ToArray();
|
||||
var defaultValueColumn = writeDefaultValues && helpOptions.Any(e => e.DefaultValue != null);
|
||||
var defaultValueColumn = ShowOptionDefaultValues && helpOptions.Any(e => e.DefaultValue != null);
|
||||
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
|
||||
@ -369,14 +463,20 @@ internal static class HelpWriter
|
||||
result.Add(grid);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the commands section of the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetCommands(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
var commandContainer = command ?? (ICommandContainer)model;
|
||||
bool isDefaultCommand = command?.IsDefaultCommand ?? false;
|
||||
|
||||
private static IEnumerable<IRenderable> GetCommands(
|
||||
CommandModel model,
|
||||
ICommandContainer command,
|
||||
bool isDefaultCommand)
|
||||
{
|
||||
var commands = isDefaultCommand ? model.Commands : command.Commands;
|
||||
var commands = isDefaultCommand ? model.Commands : commandContainer.Commands;
|
||||
commands = commands.Where(x => !x.IsHidden).ToList();
|
||||
|
||||
if (commands.Count == 0)
|
||||
@ -407,7 +507,7 @@ internal static class HelpWriter
|
||||
arguments.Space();
|
||||
}
|
||||
|
||||
if (model.TrimTrailingPeriod)
|
||||
if (TrimTrailingPeriod)
|
||||
{
|
||||
grid.AddRow(
|
||||
arguments.ToString().TrimEnd(),
|
||||
@ -424,5 +524,16 @@ internal static class HelpWriter
|
||||
result.Add(grid);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the footer for the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetFooter(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
17
src/Spectre.Console.Cli/Help/ICommandArgument.cs
Normal file
17
src/Spectre.Console.Cli/Help/ICommandArgument.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a command argument.
|
||||
/// </summary>
|
||||
public interface ICommandArgument : ICommandParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the value of the argument.
|
||||
/// </summary>
|
||||
string Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the argument.
|
||||
/// </summary>
|
||||
int Position { get; }
|
||||
}
|
25
src/Spectre.Console.Cli/Help/ICommandContainer.cs
Normal file
25
src/Spectre.Console.Cli/Help/ICommandContainer.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a command container.
|
||||
/// </summary>
|
||||
public interface ICommandContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all the examples for the container.
|
||||
/// </summary>
|
||||
IReadOnlyList<string[]> Examples { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all commands in the container.
|
||||
/// </summary>
|
||||
IReadOnlyList<ICommandInfo> Commands { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default command for the container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns null if a default command has not been set.
|
||||
/// </remarks>
|
||||
ICommandInfo? DefaultCommand { get; }
|
||||
}
|
42
src/Spectre.Console.Cli/Help/ICommandInfo.cs
Normal file
42
src/Spectre.Console.Cli/Help/ICommandInfo.cs
Normal file
@ -0,0 +1,42 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an executable command.
|
||||
/// </summary>
|
||||
public interface ICommandInfo : ICommandContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the command.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the command.
|
||||
/// </summary>
|
||||
string? Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the command is a branch.
|
||||
/// </summary>
|
||||
bool IsBranch { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the command is the default command within its container.
|
||||
/// </summary>
|
||||
bool IsDefaultCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the command is hidden.
|
||||
/// </summary>
|
||||
bool IsHidden { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameters associated with the command.
|
||||
/// </summary>
|
||||
IReadOnlyList<ICommandParameter> Parameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent command, if any.
|
||||
/// </summary>
|
||||
ICommandInfo? Parent { get; }
|
||||
}
|
23
src/Spectre.Console.Cli/Help/ICommandInfoExtensions.cs
Normal file
23
src/Spectre.Console.Cli/Help/ICommandInfoExtensions.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
internal static class ICommandInfoExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Walks up the command.Parent tree, adding each command into a list as it goes.
|
||||
/// </summary>
|
||||
/// <remarks>The first command added to the list is the current (ie. this one).</remarks>
|
||||
/// <returns>The list of commands from current to root, as traversed by <see cref="CommandInfo.Parent"/>.</returns>
|
||||
public static List<ICommandInfo> Flatten(this ICommandInfo commandInfo)
|
||||
{
|
||||
var result = new Stack<Help.ICommandInfo>();
|
||||
|
||||
var current = commandInfo;
|
||||
while (current != null)
|
||||
{
|
||||
result.Push(current);
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
}
|
12
src/Spectre.Console.Cli/Help/ICommandModel.cs
Normal file
12
src/Spectre.Console.Cli/Help/ICommandModel.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a command model.
|
||||
/// </summary>
|
||||
public interface ICommandModel : ICommandContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the application.
|
||||
/// </summary>
|
||||
string ApplicationName { get; }
|
||||
}
|
27
src/Spectre.Console.Cli/Help/ICommandOption.cs
Normal file
27
src/Spectre.Console.Cli/Help/ICommandOption.cs
Normal file
@ -0,0 +1,27 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a command option.
|
||||
/// </summary>
|
||||
public interface ICommandOption : ICommandParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the long names of the option.
|
||||
/// </summary>
|
||||
IReadOnlyList<string> LongNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the short names of the option.
|
||||
/// </summary>
|
||||
IReadOnlyList<string> ShortNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value name of the option, if applicable.
|
||||
/// </summary>
|
||||
string? ValueName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the option value is optional.
|
||||
/// </summary>
|
||||
bool ValueIsOptional { get; }
|
||||
}
|
32
src/Spectre.Console.Cli/Help/ICommandParameter.cs
Normal file
32
src/Spectre.Console.Cli/Help/ICommandParameter.cs
Normal file
@ -0,0 +1,32 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a command parameter.
|
||||
/// </summary>
|
||||
public interface ICommandParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the parameter is a flag.
|
||||
/// </summary>
|
||||
bool IsFlag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the parameter is required.
|
||||
/// </summary>
|
||||
bool Required { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the parameter.
|
||||
/// </summary>
|
||||
string? Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default value of the parameter, if specified.
|
||||
/// </summary>
|
||||
DefaultValueAttribute? DefaultValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the parameter is hidden.
|
||||
/// </summary>
|
||||
bool IsHidden { get; }
|
||||
}
|
20
src/Spectre.Console.Cli/Help/IHelpProvider.cs
Normal file
20
src/Spectre.Console.Cli/Help/IHelpProvider.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// The help provider interface for Spectre.Console.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implementations of this interface are responsbile
|
||||
/// for writing command help to the terminal when the
|
||||
/// `-h` or `--help` has been specified on the command line.
|
||||
/// </remarks>
|
||||
public interface IHelpProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes help information for the application.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects representing the help information.</returns>
|
||||
IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command);
|
||||
}
|
@ -13,12 +13,22 @@ public interface ICommandAppSettings
|
||||
/// <summary>
|
||||
/// Gets or sets the application version (use it to override auto-detected value).
|
||||
/// </summary>
|
||||
string? ApplicationVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether any default values for command options are shown in the help text.
|
||||
/// </summary>
|
||||
bool ShowOptionDefaultValues { get; set; }
|
||||
string? ApplicationVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating how many examples from direct children to show in the help text.
|
||||
/// </summary>
|
||||
int MaximumIndirectExamples { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether any default values for command options are shown in the help text.
|
||||
/// </summary>
|
||||
bool ShowOptionDefaultValues { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a trailing period of a command description is trimmed in the help text.
|
||||
/// </summary>
|
||||
bool TrimTrailingPeriod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IAnsiConsole"/>.
|
||||
@ -41,11 +51,6 @@ public interface ICommandAppSettings
|
||||
/// </summary>
|
||||
CaseSensitivity CaseSensitivity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether trailing period of a description is trimmed.
|
||||
/// </summary>
|
||||
bool TrimTrailingPeriod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not parsing is strict.
|
||||
/// </summary>
|
||||
|
@ -4,7 +4,20 @@ namespace Spectre.Console.Cli;
|
||||
/// Represents a configurator.
|
||||
/// </summary>
|
||||
public interface IConfigurator
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the help provider for the application.
|
||||
/// </summary>
|
||||
/// <param name="helpProvider">The help provider to use.</param>
|
||||
public void SetHelpProvider(IHelpProvider helpProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the help provider for the application.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
|
||||
public void SetHelpProvider<T>()
|
||||
where T : IHelpProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command app settings.
|
||||
/// </summary>
|
||||
@ -53,5 +66,5 @@ public interface IConfigurator
|
||||
/// <param name="action">The command branch configurator.</param>
|
||||
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
|
||||
IBranchConfigurator AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
|
||||
where TSettings : CommandSettings;
|
||||
where TSettings : CommandSettings;
|
||||
}
|
@ -17,7 +17,7 @@ public interface IConfigurator<in TSettings>
|
||||
/// Adds an example of how to use the branch.
|
||||
/// </summary>
|
||||
/// <param name="args">The example arguments.</param>
|
||||
void AddExample(string[] args);
|
||||
void AddExample(params string[] args);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a default command.
|
||||
|
@ -8,85 +8,87 @@ internal sealed class CommandExecutor
|
||||
{
|
||||
_registrar = registrar ?? throw new ArgumentNullException(nameof(registrar));
|
||||
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
|
||||
{
|
||||
if (configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
|
||||
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
|
||||
}
|
||||
|
||||
args ??= new List<string>();
|
||||
|
||||
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
|
||||
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
|
||||
|
||||
// Register the help provider
|
||||
var defaultHelpProvider = new HelpProvider(configuration.Settings);
|
||||
_registrar.RegisterInstance(typeof(IHelpProvider), defaultHelpProvider);
|
||||
|
||||
// Create the command model.
|
||||
var model = CommandModelBuilder.Build(configuration);
|
||||
_registrar.RegisterInstance(typeof(CommandModel), model);
|
||||
_registrar.RegisterDependencies(model);
|
||||
|
||||
// No default command?
|
||||
if (model.DefaultCommand == null)
|
||||
{
|
||||
// Got at least one argument?
|
||||
var firstArgument = args.FirstOrDefault();
|
||||
if (firstArgument != null)
|
||||
{
|
||||
// Asking for version? Kind of a hack, but it's alright.
|
||||
// We should probably make this a bit better in the future.
|
||||
if (firstArgument.Equals("--version", StringComparison.OrdinalIgnoreCase) ||
|
||||
firstArgument.Equals("-v", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var console = configuration.Settings.Console.GetConsole();
|
||||
console.WriteLine(ResolveApplicationVersion(configuration));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
_registrar.RegisterDependencies(model);
|
||||
|
||||
// Asking for version? Kind of a hack, but it's alright.
|
||||
// We should probably make this a bit better in the future.
|
||||
if (args.Contains("-v") || args.Contains("--version"))
|
||||
{
|
||||
var console = configuration.Settings.Console.GetConsole();
|
||||
console.WriteLine(ResolveApplicationVersion(configuration));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Parse and map the model against the arguments.
|
||||
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);
|
||||
|
||||
// Currently the root?
|
||||
if (parsedResult?.Tree == null)
|
||||
{
|
||||
// Display help.
|
||||
configuration.Settings.Console.SafeRender(HelpWriter.Write(model, configuration.Settings.ShowOptionDefaultValues));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the command to execute.
|
||||
var leaf = parsedResult.Tree.GetLeafCommand();
|
||||
if (leaf.Command.IsBranch || leaf.ShowHelp)
|
||||
{
|
||||
// Branches can't be executed. Show help.
|
||||
configuration.Settings.Console.SafeRender(HelpWriter.WriteCommand(model, leaf.Command, configuration.Settings.ShowOptionDefaultValues));
|
||||
return leaf.ShowHelp ? 0 : 1;
|
||||
}
|
||||
|
||||
// Is this the default and is it called without arguments when there are required arguments?
|
||||
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
|
||||
{
|
||||
// Display help for default command.
|
||||
configuration.Settings.Console.SafeRender(HelpWriter.WriteCommand(model, leaf.Command, configuration.Settings.ShowOptionDefaultValues));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Register the arguments with the container.
|
||||
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);
|
||||
|
||||
// Register the arguments with the container.
|
||||
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
|
||||
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
|
||||
|
||||
// Create the resolver and the context.
|
||||
// Create the resolver.
|
||||
using (var resolver = new TypeResolverAdapter(_registrar.Build()))
|
||||
{
|
||||
{
|
||||
// Get the registered help provider, falling back to the default provider
|
||||
// registered above if no custom implementations have been registered.
|
||||
var helpProvider = resolver.Resolve(typeof(IHelpProvider)) as IHelpProvider ?? defaultHelpProvider;
|
||||
|
||||
// Currently the root?
|
||||
if (parsedResult?.Tree == null)
|
||||
{
|
||||
// Display help.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, null));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the command to execute.
|
||||
var leaf = parsedResult.Tree.GetLeafCommand();
|
||||
if (leaf.Command.IsBranch || leaf.ShowHelp)
|
||||
{
|
||||
// Branches can't be executed. Show help.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||
return leaf.ShowHelp ? 0 : 1;
|
||||
}
|
||||
|
||||
// Is this the default and is it called without arguments when there are required arguments?
|
||||
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
|
||||
{
|
||||
// Display help for default command.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create the content.
|
||||
var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data);
|
||||
|
||||
// Execute the command tree.
|
||||
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CommandTreeParserResult? ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
|
||||
{
|
||||
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);
|
||||
|
||||
@ -113,7 +115,8 @@ internal sealed class CommandExecutor
|
||||
|
||||
return parsedResult;
|
||||
}
|
||||
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
|
||||
private static string ResolveApplicationVersion(IConfiguration configuration)
|
||||
{
|
||||
return
|
||||
|
@ -35,11 +35,11 @@ internal sealed class ComponentRegistry : IDisposable
|
||||
foreach (var type in new HashSet<Type>(registration.RegistrationTypes))
|
||||
{
|
||||
if (!_registrations.ContainsKey(type))
|
||||
{
|
||||
_registrations.Add(type, new HashSet<ComponentRegistration>());
|
||||
{
|
||||
// Only add each registration type once.
|
||||
_registrations.Add(type, new HashSet<ComponentRegistration>());
|
||||
_registrations[type].Add(registration);
|
||||
}
|
||||
|
||||
_registrations[type].Add(registration);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,8 @@ internal sealed class CommandAppSettings : ICommandAppSettings
|
||||
{
|
||||
public string? ApplicationName { get; set; }
|
||||
public string? ApplicationVersion { get; set; }
|
||||
public bool ShowOptionDefaultValues { get; set; }
|
||||
public int MaximumIndirectExamples { get; set; }
|
||||
public bool ShowOptionDefaultValues { get; set; }
|
||||
public IAnsiConsole? Console { get; set; }
|
||||
public ICommandInterceptor? Interceptor { get; set; }
|
||||
public ITypeRegistrarFrontend Registrar { get; set; }
|
||||
@ -24,7 +25,8 @@ internal sealed class CommandAppSettings : ICommandAppSettings
|
||||
{
|
||||
Registrar = new TypeRegistrar(registrar);
|
||||
CaseSensitivity = CaseSensitivity.All;
|
||||
ShowOptionDefaultValues = true;
|
||||
ShowOptionDefaultValues = true;
|
||||
MaximumIndirectExamples = 5;
|
||||
}
|
||||
|
||||
public bool IsTrue(Func<CommandAppSettings, bool> func, string environmentVariableName)
|
||||
|
@ -19,6 +19,19 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig
|
||||
Settings = new CommandAppSettings(registrar);
|
||||
Examples = new List<string[]>();
|
||||
}
|
||||
|
||||
public void SetHelpProvider(IHelpProvider helpProvider)
|
||||
{
|
||||
// Register the help provider
|
||||
_registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
|
||||
}
|
||||
|
||||
public void SetHelpProvider<T>()
|
||||
where T : IHelpProvider
|
||||
{
|
||||
// Register the help provider
|
||||
_registrar.Register(typeof(IHelpProvider), typeof(T));
|
||||
}
|
||||
|
||||
public void AddExample(params string[] args)
|
||||
{
|
||||
|
@ -17,7 +17,7 @@ internal sealed class Configurator<TSettings> : IUnsafeBranchConfigurator, IConf
|
||||
_command.Description = description;
|
||||
}
|
||||
|
||||
public void AddExample(string[] args)
|
||||
public void AddExample(params string[] args)
|
||||
{
|
||||
_command.Examples.Add(args);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal sealed class CommandArgument : CommandParameter
|
||||
internal sealed class CommandArgument : CommandParameter, ICommandArgument
|
||||
{
|
||||
public string Value { get; }
|
||||
public int Position { get; set; }
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal sealed class CommandInfo : ICommandContainer
|
||||
|
||||
internal sealed class CommandInfo : ICommandContainer, ICommandInfo
|
||||
{
|
||||
public string Name { get; }
|
||||
public HashSet<string> Aliases { get; }
|
||||
@ -20,8 +20,14 @@ internal sealed class CommandInfo : ICommandContainer
|
||||
|
||||
// only branches can have a default command
|
||||
public CommandInfo? DefaultCommand => IsBranch ? Children.FirstOrDefault(c => c.IsDefaultCommand) : null;
|
||||
public bool IsHidden { get; }
|
||||
|
||||
public bool IsHidden { get; }
|
||||
|
||||
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Children.Cast<ICommandInfo>().ToList();
|
||||
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
|
||||
IReadOnlyList<ICommandParameter> ICommandInfo.Parameters => Parameters.Cast<ICommandParameter>().ToList();
|
||||
ICommandInfo? ICommandInfo.Parent => Parent;
|
||||
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
|
||||
|
||||
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
|
||||
{
|
||||
Parent = parent;
|
||||
@ -48,19 +54,5 @@ internal sealed class CommandInfo : ICommandContainer
|
||||
Description = description.Description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<CommandInfo> Flatten()
|
||||
{
|
||||
var result = new Stack<CommandInfo>();
|
||||
|
||||
var current = this;
|
||||
while (current != null)
|
||||
{
|
||||
result.Push(current);
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,18 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal sealed class CommandModel : ICommandContainer
|
||||
|
||||
internal sealed class CommandModel : ICommandContainer, ICommandModel
|
||||
{
|
||||
public string? ApplicationName { get; }
|
||||
public ParsingMode ParsingMode { get; }
|
||||
public IList<CommandInfo> Commands { get; }
|
||||
public IList<string[]> Examples { get; }
|
||||
public bool TrimTrailingPeriod { get; }
|
||||
|
||||
public CommandInfo? DefaultCommand => Commands.FirstOrDefault(c => c.IsDefaultCommand);
|
||||
public CommandInfo? DefaultCommand => Commands.FirstOrDefault(c => c.IsDefaultCommand);
|
||||
|
||||
string ICommandModel.ApplicationName => GetApplicationName(ApplicationName);
|
||||
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Commands.Cast<ICommandInfo>().ToList();
|
||||
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
|
||||
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
|
||||
|
||||
public CommandModel(
|
||||
CommandAppSettings settings,
|
||||
@ -17,22 +21,32 @@ internal sealed class CommandModel : ICommandContainer
|
||||
{
|
||||
ApplicationName = settings.ApplicationName;
|
||||
ParsingMode = settings.ParsingMode;
|
||||
TrimTrailingPeriod = settings.TrimTrailingPeriod;
|
||||
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
|
||||
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
|
||||
}
|
||||
|
||||
public string GetApplicationName()
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the application.
|
||||
/// If the provided <paramref name="applicationName"/> is not null or empty,
|
||||
/// it is returned. Otherwise the name of the current application
|
||||
/// is determined based on the executable file's name.
|
||||
/// </summary>
|
||||
/// <param name="applicationName">The optional name of the application.</param>
|
||||
/// <returns>
|
||||
/// The name of the application, or a default value of "?" if no valid application name can be determined.
|
||||
/// </returns>
|
||||
private static string GetApplicationName(string? applicationName)
|
||||
{
|
||||
return
|
||||
ApplicationName ??
|
||||
applicationName ??
|
||||
Path.GetFileName(GetApplicationFile()) ?? // null is propagated by GetFileName
|
||||
"?";
|
||||
}
|
||||
|
||||
private static string? GetApplicationFile()
|
||||
{
|
||||
var location = Assembly.GetEntryAssembly()?.Location;
|
||||
var location = Assembly.GetEntryAssembly()?.Location;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(location))
|
||||
{
|
||||
// this is special case for single file executable
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal sealed class CommandOption : CommandParameter
|
||||
internal sealed class CommandOption : CommandParameter, ICommandOption
|
||||
{
|
||||
public IReadOnlyList<string> LongNames { get; }
|
||||
public IReadOnlyList<string> ShortNames { get; }
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal abstract class CommandParameter : ICommandParameterInfo
|
||||
|
||||
internal abstract class CommandParameter : ICommandParameterInfo, ICommandParameter
|
||||
{
|
||||
public Guid Id { get; }
|
||||
public Type ParameterType { get; }
|
||||
@ -17,8 +17,10 @@ internal abstract class CommandParameter : ICommandParameterInfo
|
||||
public string PropertyName => Property.Name;
|
||||
|
||||
public virtual bool WantRawValue => ParameterType.IsPairDeconstructable()
|
||||
&& (PairDeconstructor != null || Converter == null);
|
||||
|
||||
&& (PairDeconstructor != null || Converter == null);
|
||||
|
||||
public bool IsFlag => ParameterKind == ParameterKind.Flag;
|
||||
|
||||
protected CommandParameter(
|
||||
Type parameterType, ParameterKind parameterKind, PropertyInfo property,
|
||||
string? description, TypeConverterAttribute? converter,
|
||||
|
@ -10,6 +10,7 @@ global using System.Linq;
|
||||
global using System.Reflection;
|
||||
global using System.Text;
|
||||
global using System.Threading.Tasks;
|
||||
global using System.Xml;
|
||||
global using System.Xml;
|
||||
global using Spectre.Console.Cli.Help;
|
||||
global using Spectre.Console.Cli.Unsafe;
|
||||
global using Spectre.Console.Rendering;
|
||||
global using Spectre.Console.Rendering;
|
@ -35,10 +35,12 @@ public sealed class FakeTypeResolver : ITypeResolver
|
||||
}
|
||||
|
||||
if (_registrations.TryGetValue(type, out var registrations))
|
||||
{
|
||||
{
|
||||
// The type might be an interface, but the registration should be a class.
|
||||
// So call CreateInstance on the first registration rather than the type.
|
||||
return registrations.Count == 0
|
||||
? null
|
||||
: Activator.CreateInstance(type);
|
||||
? null
|
||||
: Activator.CreateInstance(registrations[0]);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
Reference in New Issue
Block a user