mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-14 16:02:50 +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:
parent
813a53cdfa
commit
131b37fff8
47
docs/input/cli/command-help.md
Normal file
47
docs/input/cli/command-help.md
Normal file
@ -0,0 +1,47 @@
|
||||
Title: Command Help
|
||||
Order: 13
|
||||
Description: "Console applications built with *Spectre.Console.Cli* include automatically generated help command line help."
|
||||
---
|
||||
|
||||
Console applications built with `Spectre.Console.Cli` include automatically generated help which is displayed when `-h` or `--help` has been specified on the command line.
|
||||
|
||||
The automatically generated help is derived from the configured commands and their command settings.
|
||||
|
||||
The help is also context aware and tailored depending on what has been specified on the command line before it. For example,
|
||||
|
||||
1. When `-h` or `--help` appears immediately after the application name (eg. `application.exe --help`), then the help displayed is a high-level summary of the application, including any command line examples and a listing of all possible commands the user can execute.
|
||||
|
||||
2. When `-h` or `--help` appears immediately after a command has been specified (eg. `application.exe command --help`), then the help displayed is specific to the command and includes information about command specific switches and any default values.
|
||||
|
||||
`HelpProvider` is the `Spectre.Console` class responsible for determining context and preparing the help text to write to the console. It is an implementation of the public interface `IHelpProvider`.
|
||||
|
||||
## Custom help providers
|
||||
|
||||
Whilst it shouldn't be common place to implement your own help provider, it is however possible.
|
||||
|
||||
You are able to implement your own `IHelpProvider` and configure a `CommandApp` to use that instead of the Spectre.Console help provider.
|
||||
|
||||
```csharp
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Help;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
var app = new CommandApp<DefaultCommand>();
|
||||
|
||||
app.Configure(config =>
|
||||
{
|
||||
// Register the custom help provider
|
||||
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
|
||||
});
|
||||
|
||||
return app.Run(args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There is a working [example of a custom help provider](https://github.com/spectreconsole/spectre.console/tree/main/examples/Cli/Help) demonstrating this.
|
||||
|
@ -43,7 +43,7 @@ For more complex command hierarchical configurations, they can also be composed
|
||||
|
||||
## Customizing Command Configurations
|
||||
|
||||
The `Configure` method is also used to change how help for the commands is generated. This configuration will give our command an additional alias of `file-size` and a description to be used when displaying the help. Additional, an example is specified that will be parsed and displayed for users asking for help. Multiple examples can be provided. Commands can also be marked as hidden. With this option they are still executable, but will not be displayed in help screens.
|
||||
The `Configure` method is also used to change how help for the commands is generated. This configuration will give our command an additional alias of `file-size` and a description to be used when displaying the help. Additionally, an example is specified that will be parsed and displayed for users asking for help. Multiple examples can be provided. Commands can also be marked as hidden. With this option they are still executable, but will not be displayed in help screens.
|
||||
|
||||
``` csharp
|
||||
var app = new CommandApp();
|
||||
|
@ -26,7 +26,7 @@ This setting file tells `Spectre.Console.Cli` that our command has two parameter
|
||||
|
||||
## CommandArgument
|
||||
|
||||
Arguments have a position and a name. The name is not only used for generating help, but its formatting is used to determine whether or not the argument is optional. The name must either be surrounded by square brackets (e.g. `[name]`) or angle brackets (e.g. `<name>`). Angle brackets denote required whereas square brackets denote optional. If neither are specified an exception will be thrown.
|
||||
Arguments have a position and a name. The name is not only used for generating help, but its formatting is used to determine whether or not the argument is optional. Angle brackets denote a required argument (e.g. `<name>`) whereas square brackets denote an optional argument (e.g. `[name]`). If neither are specified an exception will be thrown.
|
||||
|
||||
The position is used for scenarios where there could be more than one argument.
|
||||
|
||||
|
@ -15,25 +15,25 @@ public static class Program
|
||||
{
|
||||
config.SetApplicationName("fake-dotnet");
|
||||
config.ValidateExamples();
|
||||
config.AddExample(new[] { "run", "--no-build" });
|
||||
|
||||
// Run
|
||||
config.AddCommand<RunCommand>("run");
|
||||
|
||||
// Add
|
||||
config.AddBranch<AddSettings>("add", add =>
|
||||
{
|
||||
add.SetDescription("Add a package or reference to a .NET project");
|
||||
add.AddCommand<AddPackageCommand>("package");
|
||||
add.AddCommand<AddReferenceCommand>("reference");
|
||||
config.AddExample("run", "--no-build");
|
||||
|
||||
// Run
|
||||
config.AddCommand<RunCommand>("run");
|
||||
|
||||
// Add
|
||||
config.AddBranch<AddSettings>("add", add =>
|
||||
{
|
||||
add.SetDescription("Add a package or reference to a .NET project");
|
||||
add.AddCommand<AddPackageCommand>("package");
|
||||
add.AddCommand<AddReferenceCommand>("reference");
|
||||
});
|
||||
|
||||
// Serve
|
||||
config.AddCommand<ServeCommand>("serve")
|
||||
.WithExample("serve", "-o", "firefox")
|
||||
.WithExample("serve", "--port", "80", "-o", "firefox");
|
||||
});
|
||||
|
||||
// Serve
|
||||
config.AddCommand<ServeCommand>("serve")
|
||||
.WithExample(new[] { "serve", "-o", "firefox" })
|
||||
.WithExample(new[] { "serve", "--port", "80", "-o", "firefox" });
|
||||
});
|
||||
|
||||
return app.Run(args);
|
||||
}
|
||||
}
|
||||
|
30
examples/Cli/Help/CustomHelpProvider.cs
Normal file
30
examples/Cli/Help/CustomHelpProvider.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System.Linq;
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Cli;
|
||||
using Spectre.Console.Cli.Help;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Help;
|
||||
|
||||
/// <summary>
|
||||
/// Example showing how to extend the built-in Spectre.Console help provider
|
||||
/// by rendering a custom banner at the top of the help information
|
||||
/// </summary>
|
||||
internal class CustomHelpProvider : HelpProvider
|
||||
{
|
||||
public CustomHelpProvider(ICommandAppSettings settings)
|
||||
: base(settings)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new Text("--------------------------------------"), Text.NewLine,
|
||||
new Text("--- CUSTOM HELP PROVIDER ---"), Text.NewLine,
|
||||
new Text("--------------------------------------"), Text.NewLine,
|
||||
Text.NewLine,
|
||||
};
|
||||
}
|
||||
}
|
20
examples/Cli/Help/DefaultCommand.cs
Normal file
20
examples/Cli/Help/DefaultCommand.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Help;
|
||||
|
||||
public sealed class DefaultCommand : Command
|
||||
{
|
||||
private IAnsiConsole _console;
|
||||
|
||||
public DefaultCommand(IAnsiConsole console)
|
||||
{
|
||||
_console = console;
|
||||
}
|
||||
|
||||
public override int Execute(CommandContext context)
|
||||
{
|
||||
_console.WriteLine("Hello world");
|
||||
return 0;
|
||||
}
|
||||
}
|
18
examples/Cli/Help/Help.csproj
Normal file
18
examples/Cli/Help/Help.csproj
Normal file
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ExampleName>Help</ExampleName>
|
||||
<ExampleDescription>Demonstrates how to extend the built-in Spectre.Console help provider to render a custom banner at the top of the help information.</ExampleDescription>
|
||||
<ExampleGroup>Cli</ExampleGroup>
|
||||
<ExampleVisible>false</ExampleVisible>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
19
examples/Cli/Help/Program.cs
Normal file
19
examples/Cli/Help/Program.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Help;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
var app = new CommandApp<DefaultCommand>();
|
||||
|
||||
app.Configure(config =>
|
||||
{
|
||||
// Register the custom help provider
|
||||
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
|
||||
});
|
||||
|
||||
return app.Run(args);
|
||||
}
|
||||
}
|
@ -83,6 +83,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Json", "Console\Json\Json.c
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Json", "..\src\Spectre.Console.Json\Spectre.Console.Json.csproj", "{91A5637F-1F89-48B3-A0BA-6CC629807393}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Help", "Cli\Help\Help.csproj", "{BAB490D6-FF8D-462B-B2B0-933384D629DB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -549,6 +551,18 @@ Global
|
||||
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Release|x64.Build.0 = Release|Any CPU
|
||||
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{91A5637F-1F89-48B3-A0BA-6CC629807393}.Release|x86.Build.0 = Release|Any CPU
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Release|x64.Build.0 = Release|Any CPU
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -564,6 +578,7 @@ Global
|
||||
{A127CE7D-A5A7-4745-9809-EBD7CB12CEE7} = {2571F1BD-6556-4F96-B27B-B6190E1BF13A}
|
||||
{EFAADF6A-C77D-41EC-83F5-BBB4FFC5A6D7} = {2571F1BD-6556-4F96-B27B-B6190E1BF13A}
|
||||
{91A5637F-1F89-48B3-A0BA-6CC629807393} = {2571F1BD-6556-4F96-B27B-B6190E1BF13A}
|
||||
{BAB490D6-FF8D-462B-B2B0-933384D629DB} = {4682E9B7-B54C-419D-B92F-470DA4E5674C}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3EE724C5-CAB4-410D-AC63-8D4260EF83ED}
|
||||
|
@ -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;
|
||||
|
@ -0,0 +1,34 @@
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console.Cli.Tests.Data.Help;
|
||||
|
||||
internal class CustomHelpProvider : HelpProvider
|
||||
{
|
||||
private readonly string version;
|
||||
|
||||
public CustomHelpProvider(ICommandAppSettings settings, string version)
|
||||
: base(settings)
|
||||
{
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo command)
|
||||
{
|
||||
return new IRenderable[]
|
||||
{
|
||||
new Text("--------------------------------------"), Text.NewLine,
|
||||
new Text("--- CUSTOM HELP PROVIDER ---"), Text.NewLine,
|
||||
new Text("--------------------------------------"), Text.NewLine,
|
||||
Text.NewLine,
|
||||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<IRenderable> GetFooter(ICommandModel model, ICommandInfo command)
|
||||
{
|
||||
return new IRenderable[]
|
||||
{
|
||||
Text.NewLine,
|
||||
new Text($"Version {version}"),
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Spectre.Console.Cli.Tests.Data.Help;
|
||||
|
||||
internal class RedirectHelpProvider : IHelpProvider
|
||||
{
|
||||
public virtual IEnumerable<IRenderable> Write(ICommandModel model)
|
||||
{
|
||||
return Write(model, null);
|
||||
}
|
||||
#nullable enable
|
||||
public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command)
|
||||
#nullable disable
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new Text("Help has moved online. Please see: http://www.example.com"),
|
||||
Text.NewLine,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
namespace Spectre.Console.Tests.Data;
|
||||
|
||||
public sealed class EmptySettings : CommandSettings
|
||||
{
|
||||
}
|
@ -15,7 +15,7 @@ public sealed class OptionalArgumentWithPropertyInitializerSettings : CommandSet
|
||||
[CommandOption("-c")]
|
||||
public int Count { get; set; } = 1;
|
||||
|
||||
[CommandOption("-v")]
|
||||
[CommandOption("--value")]
|
||||
public int Value { get; set; } = 0;
|
||||
}
|
||||
|
||||
|
@ -9,4 +9,5 @@ ARGUMENTS:
|
||||
[QUX]
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
@ -0,0 +1,18 @@
|
||||
DESCRIPTION:
|
||||
Contains settings for a cat.
|
||||
|
||||
USAGE:
|
||||
myapp cat [LEGS] [OPTIONS] <COMMAND>
|
||||
|
||||
ARGUMENTS:
|
||||
[LEGS] The number of legs
|
||||
|
||||
OPTIONS:
|
||||
DEFAULT
|
||||
-h, --help Prints help information
|
||||
-a, --alive Indicates whether or not the animal is alive
|
||||
-n, --name <VALUE>
|
||||
--agility <VALUE> 10 The agility between 0 and 100
|
||||
|
||||
COMMANDS:
|
||||
lion <TEETH> The lion command
|
@ -0,0 +1,11 @@
|
||||
USAGE:
|
||||
myapp branch [GREETING] [OPTIONS] [COMMAND]
|
||||
|
||||
ARGUMENTS:
|
||||
[GREETING]
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
|
||||
COMMANDS:
|
||||
greeter
|
@ -0,0 +1,30 @@
|
||||
DESCRIPTION:
|
||||
The animal command.
|
||||
|
||||
USAGE:
|
||||
myapp animal [LEGS] [OPTIONS] <COMMAND>
|
||||
|
||||
EXAMPLES:
|
||||
myapp animal dog --name Rufus --age 12 --good-boy
|
||||
myapp animal dog --name Luna
|
||||
myapp animal dog --name Charlie
|
||||
myapp animal dog --name Bella
|
||||
myapp animal dog --name Daisy
|
||||
myapp animal dog --name Milo
|
||||
myapp animal horse --name Brutus
|
||||
myapp animal horse --name Sugar --IsAlive false
|
||||
myapp animal horse --name Cash
|
||||
myapp animal horse --name Dakota
|
||||
myapp animal horse --name Cisco
|
||||
myapp animal horse --name Spirit
|
||||
|
||||
ARGUMENTS:
|
||||
[LEGS] The number of legs
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-a, --alive Indicates whether or not the animal is alive
|
||||
|
||||
COMMANDS:
|
||||
dog <AGE> The dog command
|
||||
horse The horse command
|
@ -1,19 +0,0 @@
|
||||
DESCRIPTION:
|
||||
The animal command.
|
||||
|
||||
USAGE:
|
||||
myapp animal [LEGS] [OPTIONS] <COMMAND>
|
||||
|
||||
EXAMPLES:
|
||||
myapp animal --help
|
||||
|
||||
ARGUMENTS:
|
||||
[LEGS] The number of legs
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-a, --alive Indicates whether or not the animal is alive
|
||||
|
||||
COMMANDS:
|
||||
dog <AGE> The dog command
|
||||
horse The horse command
|
@ -0,0 +1,15 @@
|
||||
--------------------------------------
|
||||
--- CUSTOM HELP PROVIDER ---
|
||||
--------------------------------------
|
||||
|
||||
USAGE:
|
||||
myapp [OPTIONS] <COMMAND>
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
|
||||
COMMANDS:
|
||||
dog <AGE> The dog command
|
||||
|
||||
Version 1.0
|
@ -0,0 +1 @@
|
||||
Help has moved online. Please see: http://www.example.com
|
@ -0,0 +1,15 @@
|
||||
--------------------------------------
|
||||
--- CUSTOM HELP PROVIDER ---
|
||||
--------------------------------------
|
||||
|
||||
USAGE:
|
||||
myapp [OPTIONS] <COMMAND>
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
|
||||
COMMANDS:
|
||||
dog <AGE> The dog command
|
||||
|
||||
Version 1.0
|
@ -0,0 +1 @@
|
||||
Help has moved online. Please see: http://www.example.com
|
@ -1,4 +1,4 @@
|
||||
DESCRIPTION:
|
||||
DESCRIPTION:
|
||||
The lion command.
|
||||
|
||||
USAGE:
|
||||
@ -10,7 +10,8 @@ ARGUMENTS:
|
||||
|
||||
OPTIONS:
|
||||
DEFAULT
|
||||
-h, --help Prints help information
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
-a, --alive Indicates whether or not the animal is alive
|
||||
-n, --name <VALUE>
|
||||
--agility <VALUE> 10 The agility between 0 and 100
|
||||
|
@ -1,21 +0,0 @@
|
||||
DESCRIPTION:
|
||||
The lion command.
|
||||
|
||||
USAGE:
|
||||
myapp <TEETH> [LEGS] [OPTIONS]
|
||||
|
||||
EXAMPLES:
|
||||
myapp 12 -c 3
|
||||
|
||||
ARGUMENTS:
|
||||
<TEETH> The number of teeth the lion has
|
||||
[LEGS] The number of legs
|
||||
|
||||
OPTIONS:
|
||||
DEFAULT
|
||||
-h, --help Prints help information
|
||||
-a, --alive Indicates whether or not the animal is alive
|
||||
-n, --name <VALUE>
|
||||
--agility <VALUE> 10 The agility between 0 and 100
|
||||
-c <CHILDREN> The number of children the lion has
|
||||
-d <DAY> Monday, Thursday The days the lion goes hunting
|
@ -0,0 +1,15 @@
|
||||
--------------------------------------
|
||||
--- CUSTOM HELP PROVIDER ---
|
||||
--------------------------------------
|
||||
|
||||
USAGE:
|
||||
myapp [OPTIONS] <COMMAND>
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
|
||||
COMMANDS:
|
||||
dog <AGE> The dog command
|
||||
|
||||
Version 1.0
|
@ -0,0 +1,24 @@
|
||||
DESCRIPTION:
|
||||
The dog command.
|
||||
|
||||
USAGE:
|
||||
myapp <AGE> [LEGS] [OPTIONS]
|
||||
|
||||
EXAMPLES:
|
||||
myapp --name Rufus --age 12 --good-boy
|
||||
myapp --name Luna
|
||||
myapp --name Charlie
|
||||
myapp --name Bella
|
||||
myapp --name Daisy
|
||||
myapp --name Milo
|
||||
|
||||
ARGUMENTS:
|
||||
<AGE>
|
||||
[LEGS] The number of legs
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
-a, --alive Indicates whether or not the animal is alive
|
||||
-n, --name <VALUE>
|
||||
-g, --good-boy
|
@ -1,4 +1,4 @@
|
||||
DESCRIPTION:
|
||||
DESCRIPTION:
|
||||
The lion command.
|
||||
|
||||
USAGE:
|
||||
@ -10,7 +10,8 @@ ARGUMENTS:
|
||||
|
||||
OPTIONS:
|
||||
DEFAULT
|
||||
-h, --help Prints help information
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
-a, --alive Indicates whether or not the animal is alive
|
||||
-n, --name <VALUE>
|
||||
--agility <VALUE> 10 The agility between 0 and 100
|
||||
|
@ -1,8 +1,8 @@
|
||||
DESCRIPTION:
|
||||
DESCRIPTION:
|
||||
The lion command.
|
||||
|
||||
USAGE:
|
||||
myapp <TEETH> [LEGS] [OPTIONS]
|
||||
myapp <TEETH> [LEGS] [OPTIONS] [COMMAND]
|
||||
|
||||
ARGUMENTS:
|
||||
<TEETH> The number of teeth the lion has
|
||||
@ -10,7 +10,8 @@ ARGUMENTS:
|
||||
|
||||
OPTIONS:
|
||||
DEFAULT
|
||||
-h, --help Prints help information
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
-a, --alive Indicates whether or not the animal is alive
|
||||
-n, --name <VALUE>
|
||||
--agility <VALUE> 10 The agility between 0 and 100
|
||||
|
@ -5,5 +5,6 @@ ARGUMENTS:
|
||||
<FOO> Dummy argument FOO
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
--baz Dummy option BAZ
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
--baz Dummy option BAZ
|
@ -0,0 +1,24 @@
|
||||
USAGE:
|
||||
myapp [OPTIONS] <COMMAND>
|
||||
|
||||
EXAMPLES:
|
||||
myapp dog --name Rufus --age 12 --good-boy
|
||||
myapp dog --name Luna
|
||||
myapp dog --name Charlie
|
||||
myapp dog --name Bella
|
||||
myapp dog --name Daisy
|
||||
myapp dog --name Milo
|
||||
myapp horse --name Brutus
|
||||
myapp horse --name Sugar --IsAlive false
|
||||
myapp horse --name Cash
|
||||
myapp horse --name Dakota
|
||||
myapp horse --name Cisco
|
||||
myapp horse --name Spirit
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
|
||||
COMMANDS:
|
||||
dog <AGE> The dog command
|
||||
horse The horse command
|
@ -3,7 +3,10 @@ USAGE:
|
||||
|
||||
EXAMPLES:
|
||||
myapp dog --name Rufus --age 12 --good-boy
|
||||
myapp horse --name Brutus
|
||||
myapp dog --name Luna
|
||||
myapp dog --name Charlie
|
||||
myapp dog --name Bella
|
||||
myapp dog --name Daisy
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
@ -0,0 +1,20 @@
|
||||
USAGE:
|
||||
myapp [OPTIONS] <COMMAND>
|
||||
|
||||
EXAMPLES:
|
||||
myapp dog --name Rufus --age 12 --good-boy
|
||||
myapp dog --name Luna
|
||||
myapp dog --name Charlie
|
||||
myapp dog --name Bella
|
||||
myapp dog --name Daisy
|
||||
myapp dog --name Milo
|
||||
myapp horse --name Brutus
|
||||
myapp horse --name Sugar --IsAlive false
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
|
||||
COMMANDS:
|
||||
dog <AGE> The dog command
|
||||
horse The horse command
|
@ -1,14 +1,10 @@
|
||||
USAGE:
|
||||
myapp [OPTIONS] <COMMAND>
|
||||
|
||||
EXAMPLES:
|
||||
myapp dog --name Rufus --age 12 --good-boy
|
||||
myapp horse --name Brutus
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
|
||||
COMMANDS:
|
||||
dog <AGE> The dog command
|
||||
USAGE:
|
||||
myapp [OPTIONS] <COMMAND>
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
|
||||
COMMANDS:
|
||||
dog <AGE> The dog command
|
||||
horse The horse command
|
@ -0,0 +1,24 @@
|
||||
USAGE:
|
||||
myapp [OPTIONS] <COMMAND>
|
||||
|
||||
EXAMPLES:
|
||||
myapp dog --name Rufus --age 12 --good-boy
|
||||
myapp dog --name Luna
|
||||
myapp dog --name Charlie
|
||||
myapp dog --name Bella
|
||||
myapp dog --name Daisy
|
||||
myapp dog --name Milo
|
||||
myapp horse --name Brutus
|
||||
myapp horse --name Sugar --IsAlive false
|
||||
myapp horse --name Cash
|
||||
myapp horse --name Dakota
|
||||
myapp horse --name Cisco
|
||||
myapp horse --name Spirit
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
|
||||
COMMANDS:
|
||||
dog <AGE> The dog command
|
||||
horse The horse command
|
@ -3,7 +3,10 @@ USAGE:
|
||||
|
||||
EXAMPLES:
|
||||
myapp animal dog --name Rufus --age 12 --good-boy
|
||||
myapp animal horse --name Brutus
|
||||
myapp animal dog --name Luna
|
||||
myapp animal dog --name Charlie
|
||||
myapp animal dog --name Bella
|
||||
myapp animal dog --name Daisy
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
@ -0,0 +1,19 @@
|
||||
USAGE:
|
||||
myapp [OPTIONS] <COMMAND>
|
||||
|
||||
EXAMPLES:
|
||||
myapp animal dog --name Rufus --age 12 --good-boy
|
||||
myapp animal dog --name Luna
|
||||
myapp animal dog --name Charlie
|
||||
myapp animal dog --name Bella
|
||||
myapp animal dog --name Daisy
|
||||
myapp animal dog --name Milo
|
||||
myapp animal horse --name Brutus
|
||||
myapp animal horse --name Sugar --IsAlive false
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
|
||||
COMMANDS:
|
||||
animal The animal command
|
@ -0,0 +1,9 @@
|
||||
USAGE:
|
||||
myapp [OPTIONS] <COMMAND>
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
|
||||
COMMANDS:
|
||||
animal The animal command
|
@ -0,0 +1,23 @@
|
||||
USAGE:
|
||||
myapp [OPTIONS] <COMMAND>
|
||||
|
||||
EXAMPLES:
|
||||
myapp animal dog --name Rufus --age 12 --good-boy
|
||||
myapp animal dog --name Luna
|
||||
myapp animal dog --name Charlie
|
||||
myapp animal dog --name Bella
|
||||
myapp animal dog --name Daisy
|
||||
myapp animal dog --name Milo
|
||||
myapp animal horse --name Brutus
|
||||
myapp animal horse --name Sugar --IsAlive false
|
||||
myapp animal horse --name Cash
|
||||
myapp animal horse --name Dakota
|
||||
myapp animal horse --name Cisco
|
||||
myapp animal horse --name Spirit
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
-v, --version Prints version information
|
||||
|
||||
COMMANDS:
|
||||
animal The animal command
|
@ -7,7 +7,8 @@ global using System.Linq;
|
||||
global using System.Runtime.CompilerServices;
|
||||
global using System.Threading.Tasks;
|
||||
global using Shouldly;
|
||||
global using Spectre.Console.Cli;
|
||||
global using Spectre.Console.Cli;
|
||||
global using Spectre.Console.Cli.Help;
|
||||
global using Spectre.Console.Cli.Unsafe;
|
||||
global using Spectre.Console.Testing;
|
||||
global using Spectre.Console.Tests.Data;
|
||||
|
@ -34,7 +34,7 @@
|
||||
<ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0])</ParentFile>
|
||||
<DependentUpon>%(ParentFile).cs</DependentUpon>
|
||||
</None>
|
||||
<None Update="Expectations\Help\Greeter_Default.Output.verified.txt">
|
||||
<None Update="Expectations\Help\Default_Greeter.Output.verified.txt">
|
||||
<ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0])</ParentFile>
|
||||
<DependentUpon>%(ParentFile).cs</DependentUpon>
|
||||
</None>
|
||||
|
@ -1,3 +1,5 @@
|
||||
using Spectre.Console.Cli.Tests.Data.Help;
|
||||
|
||||
namespace Spectre.Console.Tests.Unit.Cli;
|
||||
|
||||
public sealed partial class CommandAppTests
|
||||
@ -75,8 +77,8 @@ public sealed partial class CommandAppTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Command")]
|
||||
public Task Should_Output_Command_Correctly()
|
||||
[Expectation("Branch")]
|
||||
public Task Should_Output_Branch_Correctly()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
@ -91,7 +93,53 @@ public sealed partial class CommandAppTests
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("cat", "--help");
|
||||
var result = fixture.Run("cat", "--help");
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Branch_Called_Without_Help")]
|
||||
public Task Should_Output_Branch_When_Called_Without_Help_Option()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddBranch<CatSettings>("cat", animal =>
|
||||
{
|
||||
animal.SetDescription("Contains settings for a cat.");
|
||||
animal.AddCommand<LionCommand>("lion");
|
||||
});
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("cat");
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Branch_Default_Greeter")]
|
||||
public Task Should_Output_Branch_With_Default_Correctly()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddBranch<OptionalArgumentWithDefaultValueSettings>("branch", animal =>
|
||||
{
|
||||
animal.SetDefaultCommand<GreeterCommand>();
|
||||
animal.AddCommand<GreeterCommand>("greeter");
|
||||
});
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("branch", "--help");
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
@ -138,7 +186,7 @@ public sealed partial class CommandAppTests
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("cat", "lion", "--help");
|
||||
var result = fixture.Run("cat", "lion", "--help");
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
@ -203,7 +251,7 @@ public sealed partial class CommandAppTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Greeter_Default")]
|
||||
[Expectation("Default_Greeter")]
|
||||
public Task Should_Not_Output_Default_Command_When_Command_Has_No_Required_Parameters_And_Is_Called_Without_Args()
|
||||
{
|
||||
// Given
|
||||
@ -219,19 +267,131 @@ public sealed partial class CommandAppTests
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Custom_Help_Registered_By_Instance")]
|
||||
public Task Should_Output_Custom_Help_When_Registered_By_Instance()
|
||||
{
|
||||
var registrar = new DefaultTypeRegistrar();
|
||||
|
||||
// Given
|
||||
var fixture = new CommandAppTester(registrar);
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
// Create the custom help provider
|
||||
var helpProvider = new CustomHelpProvider(configurator.Settings, "1.0");
|
||||
|
||||
// Register the custom help provider instance
|
||||
registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
|
||||
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddCommand<DogCommand>("dog");
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run();
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Custom_Help_Registered_By_Type")]
|
||||
public Task Should_Output_Custom_Help_When_Registered_By_Type()
|
||||
{
|
||||
var registrar = new DefaultTypeRegistrar();
|
||||
|
||||
// Given
|
||||
var fixture = new CommandAppTester(registrar);
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
// Register the custom help provider type
|
||||
registrar.Register(typeof(IHelpProvider), typeof(RedirectHelpProvider));
|
||||
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddCommand<DogCommand>("dog");
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run();
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Custom_Help_Configured_By_Instance")]
|
||||
public Task Should_Output_Custom_Help_When_Configured_By_Instance()
|
||||
{
|
||||
var registrar = new DefaultTypeRegistrar();
|
||||
|
||||
// Given
|
||||
var fixture = new CommandAppTester(registrar);
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
// Configure the custom help provider instance
|
||||
configurator.SetHelpProvider(new CustomHelpProvider(configurator.Settings, "1.0"));
|
||||
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddCommand<DogCommand>("dog");
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run();
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Custom_Help_Configured_By_Type")]
|
||||
public Task Should_Output_Custom_Help_When_Configured_By_Type()
|
||||
{
|
||||
var registrar = new DefaultTypeRegistrar();
|
||||
|
||||
// Given
|
||||
var fixture = new CommandAppTester(registrar);
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
// Configure the custom help provider type
|
||||
configurator.SetHelpProvider<RedirectHelpProvider>();
|
||||
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddCommand<DogCommand>("dog");
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run();
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("RootExamples")]
|
||||
public Task Should_Output_Root_Examples_Defined_On_Root()
|
||||
[Expectation("Root_Examples")]
|
||||
public Task Should_Output_Examples_Defined_On_Root()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddExample("dog", "--name", "Rufus", "--age", "12", "--good-boy");
|
||||
configurator.AddExample("horse", "--name", "Brutus");
|
||||
|
||||
// All root examples should be shown
|
||||
configurator.AddExample("dog", "--name", "Rufus", "--age", "12", "--good-boy");
|
||||
configurator.AddExample("dog", "--name", "Luna");
|
||||
configurator.AddExample("dog", "--name", "Charlie");
|
||||
configurator.AddExample("dog", "--name", "Bella");
|
||||
configurator.AddExample("dog", "--name", "Daisy");
|
||||
configurator.AddExample("dog", "--name", "Milo");
|
||||
configurator.AddExample("horse", "--name", "Brutus");
|
||||
configurator.AddExample("horse", "--name", "Sugar", "--IsAlive", "false");
|
||||
configurator.AddExample("horse", "--name", "Cash");
|
||||
configurator.AddExample("horse", "--name", "Dakota");
|
||||
configurator.AddExample("horse", "--name", "Cisco");
|
||||
configurator.AddExample("horse", "--name", "Spirit");
|
||||
|
||||
configurator.AddCommand<DogCommand>("dog");
|
||||
configurator.AddCommand<HorseCommand>("horse");
|
||||
});
|
||||
@ -241,21 +401,147 @@ public sealed partial class CommandAppTests
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("RootExamples_Children")]
|
||||
public Task Should_Output_Root_Examples_Defined_On_Direct_Children_If_Root_Have_No_Examples()
|
||||
[Expectation("Root_Examples_Children")]
|
||||
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:SingleLineCommentsMustNotBeFollowedByBlankLine", Justification = "Single line comment is relevant to several code blocks that follow.")]
|
||||
public Task Should_Output_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.SetApplicationName("myapp");
|
||||
|
||||
// It should be capped to the first 5 examples by default
|
||||
|
||||
configurator.AddCommand<DogCommand>("dog")
|
||||
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy");
|
||||
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
|
||||
.WithExample("dog", "--name", "Luna")
|
||||
.WithExample("dog", "--name", "Charlie")
|
||||
.WithExample("dog", "--name", "Bella")
|
||||
.WithExample("dog", "--name", "Daisy")
|
||||
.WithExample("dog", "--name", "Milo");
|
||||
|
||||
configurator.AddCommand<HorseCommand>("horse")
|
||||
.WithExample("horse", "--name", "Brutus");
|
||||
.WithExample("horse", "--name", "Brutus")
|
||||
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
|
||||
.WithExample("horse", "--name", "Cash")
|
||||
.WithExample("horse", "--name", "Dakota")
|
||||
.WithExample("horse", "--name", "Cisco")
|
||||
.WithExample("horse", "--name", "Spirit");
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("--help");
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Root_Examples_Children_Eight")]
|
||||
public Task Should_Output_Eight_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
|
||||
// Show the first 8 examples defined on the direct children
|
||||
configurator.Settings.MaximumIndirectExamples = 8;
|
||||
|
||||
configurator.AddCommand<DogCommand>("dog")
|
||||
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
|
||||
.WithExample("dog", "--name", "Luna")
|
||||
.WithExample("dog", "--name", "Charlie")
|
||||
.WithExample("dog", "--name", "Bella")
|
||||
.WithExample("dog", "--name", "Daisy")
|
||||
.WithExample("dog", "--name", "Milo");
|
||||
|
||||
configurator.AddCommand<HorseCommand>("horse")
|
||||
.WithExample("horse", "--name", "Brutus")
|
||||
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
|
||||
.WithExample("horse", "--name", "Cash")
|
||||
.WithExample("horse", "--name", "Dakota")
|
||||
.WithExample("horse", "--name", "Cisco")
|
||||
.WithExample("horse", "--name", "Spirit");
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("--help");
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Root_Examples_Children_Twelve")]
|
||||
public Task Should_Output_All_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
|
||||
// Show all examples defined on the direct children
|
||||
configurator.Settings.MaximumIndirectExamples = int.MaxValue;
|
||||
|
||||
configurator.AddCommand<DogCommand>("dog")
|
||||
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
|
||||
.WithExample("dog", "--name", "Luna")
|
||||
.WithExample("dog", "--name", "Charlie")
|
||||
.WithExample("dog", "--name", "Bella")
|
||||
.WithExample("dog", "--name", "Daisy")
|
||||
.WithExample("dog", "--name", "Milo");
|
||||
|
||||
configurator.AddCommand<HorseCommand>("horse")
|
||||
.WithExample("horse", "--name", "Brutus")
|
||||
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
|
||||
.WithExample("horse", "--name", "Cash")
|
||||
.WithExample("horse", "--name", "Dakota")
|
||||
.WithExample("horse", "--name", "Cisco")
|
||||
.WithExample("horse", "--name", "Spirit");
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("--help");
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Root_Examples_Children_None")]
|
||||
public Task Should_Not_Output_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
|
||||
// Do not show examples defined on the direct children
|
||||
configurator.Settings.MaximumIndirectExamples = 0;
|
||||
|
||||
configurator.AddCommand<DogCommand>("dog")
|
||||
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
|
||||
.WithExample("dog", "--name", "Luna")
|
||||
.WithExample("dog", "--name", "Charlie")
|
||||
.WithExample("dog", "--name", "Bella")
|
||||
.WithExample("dog", "--name", "Daisy")
|
||||
.WithExample("dog", "--name", "Milo");
|
||||
|
||||
configurator.AddCommand<HorseCommand>("horse")
|
||||
.WithExample("horse", "--name", "Brutus")
|
||||
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
|
||||
.WithExample("horse", "--name", "Cash")
|
||||
.WithExample("horse", "--name", "Dakota")
|
||||
.WithExample("horse", "--name", "Cisco")
|
||||
.WithExample("horse", "--name", "Spirit");
|
||||
});
|
||||
|
||||
// When
|
||||
@ -266,8 +552,9 @@ public sealed partial class CommandAppTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("RootExamples_Leafs")]
|
||||
public Task Should_Output_Root_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
|
||||
[Expectation("Root_Examples_Leafs")]
|
||||
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:SingleLineCommentsMustNotBeFollowedByBlankLine", Justification = "Single line comment is relevant to several code blocks that follow.")]
|
||||
public Task Should_Output_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
@ -276,11 +563,148 @@ public sealed partial class CommandAppTests
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddBranch<AnimalSettings>("animal", animal =>
|
||||
{
|
||||
animal.SetDescription("The animal command.");
|
||||
animal.SetDescription("The animal command.");
|
||||
|
||||
// It should be capped to the first 5 examples by default
|
||||
|
||||
animal.AddCommand<DogCommand>("dog")
|
||||
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy");
|
||||
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
|
||||
.WithExample("animal", "dog", "--name", "Luna")
|
||||
.WithExample("animal", "dog", "--name", "Charlie")
|
||||
.WithExample("animal", "dog", "--name", "Bella")
|
||||
.WithExample("animal", "dog", "--name", "Daisy")
|
||||
.WithExample("animal", "dog", "--name", "Milo");
|
||||
|
||||
animal.AddCommand<HorseCommand>("horse")
|
||||
.WithExample("animal", "horse", "--name", "Brutus");
|
||||
.WithExample("animal", "horse", "--name", "Brutus")
|
||||
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
|
||||
.WithExample("animal", "horse", "--name", "Cash")
|
||||
.WithExample("animal", "horse", "--name", "Dakota")
|
||||
.WithExample("animal", "horse", "--name", "Cisco")
|
||||
.WithExample("animal", "horse", "--name", "Spirit");
|
||||
});
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("--help");
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Root_Examples_Leafs_Eight")]
|
||||
public Task Should_Output_Eight_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddBranch<AnimalSettings>("animal", animal =>
|
||||
{
|
||||
animal.SetDescription("The animal command.");
|
||||
|
||||
// Show the first 8 examples defined on the direct children
|
||||
configurator.Settings.MaximumIndirectExamples = 8;
|
||||
|
||||
animal.AddCommand<DogCommand>("dog")
|
||||
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
|
||||
.WithExample("animal", "dog", "--name", "Luna")
|
||||
.WithExample("animal", "dog", "--name", "Charlie")
|
||||
.WithExample("animal", "dog", "--name", "Bella")
|
||||
.WithExample("animal", "dog", "--name", "Daisy")
|
||||
.WithExample("animal", "dog", "--name", "Milo");
|
||||
|
||||
animal.AddCommand<HorseCommand>("horse")
|
||||
.WithExample("animal", "horse", "--name", "Brutus")
|
||||
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
|
||||
.WithExample("animal", "horse", "--name", "Cash")
|
||||
.WithExample("animal", "horse", "--name", "Dakota")
|
||||
.WithExample("animal", "horse", "--name", "Cisco")
|
||||
.WithExample("animal", "horse", "--name", "Spirit");
|
||||
});
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("--help");
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Root_Examples_Leafs_Twelve")]
|
||||
public Task Should_Output_All_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddBranch<AnimalSettings>("animal", animal =>
|
||||
{
|
||||
animal.SetDescription("The animal command.");
|
||||
|
||||
// Show all examples defined on the direct children
|
||||
configurator.Settings.MaximumIndirectExamples = int.MaxValue;
|
||||
|
||||
animal.AddCommand<DogCommand>("dog")
|
||||
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
|
||||
.WithExample("animal", "dog", "--name", "Luna")
|
||||
.WithExample("animal", "dog", "--name", "Charlie")
|
||||
.WithExample("animal", "dog", "--name", "Bella")
|
||||
.WithExample("animal", "dog", "--name", "Daisy")
|
||||
.WithExample("animal", "dog", "--name", "Milo");
|
||||
|
||||
animal.AddCommand<HorseCommand>("horse")
|
||||
.WithExample("animal", "horse", "--name", "Brutus")
|
||||
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
|
||||
.WithExample("animal", "horse", "--name", "Cash")
|
||||
.WithExample("animal", "horse", "--name", "Dakota")
|
||||
.WithExample("animal", "horse", "--name", "Cisco")
|
||||
.WithExample("animal", "horse", "--name", "Spirit");
|
||||
});
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("--help");
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Root_Examples_Leafs_None")]
|
||||
public Task Should_Not_Output_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddBranch<AnimalSettings>("animal", animal =>
|
||||
{
|
||||
animal.SetDescription("The animal command.");
|
||||
|
||||
// Do not show examples defined on the direct children
|
||||
configurator.Settings.MaximumIndirectExamples = 0;
|
||||
|
||||
animal.AddCommand<DogCommand>("dog")
|
||||
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
|
||||
.WithExample("animal", "dog", "--name", "Luna")
|
||||
.WithExample("animal", "dog", "--name", "Charlie")
|
||||
.WithExample("animal", "dog", "--name", "Bella")
|
||||
.WithExample("animal", "dog", "--name", "Daisy")
|
||||
.WithExample("animal", "dog", "--name", "Milo");
|
||||
|
||||
animal.AddCommand<HorseCommand>("horse")
|
||||
.WithExample("animal", "horse", "--name", "Brutus")
|
||||
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
|
||||
.WithExample("animal", "horse", "--name", "Cash")
|
||||
.WithExample("animal", "horse", "--name", "Dakota")
|
||||
.WithExample("animal", "horse", "--name", "Cisco")
|
||||
.WithExample("animal", "horse", "--name", "Spirit");
|
||||
});
|
||||
});
|
||||
|
||||
@ -292,18 +716,31 @@ public sealed partial class CommandAppTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("CommandExamples")]
|
||||
public Task Should_Only_Output_Command_Examples_Defined_On_Command()
|
||||
[Expectation("Branch_Examples")]
|
||||
public Task Should_Output_Examples_Defined_On_Branch()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddBranch<AnimalSettings>("animal", animal =>
|
||||
{
|
||||
animal.SetDescription("The animal command.");
|
||||
animal.AddExample(new[] { "animal", "--help" });
|
||||
animal.SetDescription("The animal command.");
|
||||
|
||||
// All branch examples should be shown
|
||||
animal.AddExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy");
|
||||
animal.AddExample("animal", "dog", "--name", "Luna");
|
||||
animal.AddExample("animal", "dog", "--name", "Charlie");
|
||||
animal.AddExample("animal", "dog", "--name", "Bella");
|
||||
animal.AddExample("animal", "dog", "--name", "Daisy");
|
||||
animal.AddExample("animal", "dog", "--name", "Milo");
|
||||
animal.AddExample("animal", "horse", "--name", "Brutus");
|
||||
animal.AddExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false");
|
||||
animal.AddExample("animal", "horse", "--name", "Cash");
|
||||
animal.AddExample("animal", "horse", "--name", "Dakota");
|
||||
animal.AddExample("animal", "horse", "--name", "Cisco");
|
||||
animal.AddExample("animal", "horse", "--name", "Spirit");
|
||||
|
||||
animal.AddCommand<DogCommand>("dog")
|
||||
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy");
|
||||
@ -317,19 +754,26 @@ public sealed partial class CommandAppTests
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("DefaultExamples")]
|
||||
public Task Should_Output_Root_Examples_If_Default_Command_Is_Specified()
|
||||
[Expectation("Default_Examples")]
|
||||
public Task Should_Output_Examples_Defined_On_Root_If_Default_Command_Is_Specified()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.SetDefaultCommand<LionCommand>();
|
||||
fixture.SetDefaultCommand<DogCommand>();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
configurator.AddExample("12", "-c", "3");
|
||||
|
||||
// All root examples should be shown
|
||||
configurator.AddExample("--name", "Rufus", "--age", "12", "--good-boy");
|
||||
configurator.AddExample("--name", "Luna");
|
||||
configurator.AddExample("--name", "Charlie");
|
||||
configurator.AddExample("--name", "Bella");
|
||||
configurator.AddExample("--name", "Daisy");
|
||||
configurator.AddExample("--name", "Milo");
|
||||
});
|
||||
|
||||
// When
|
||||
|
@ -5,27 +5,92 @@ public sealed partial class CommandAppTests
|
||||
public sealed class Version
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Output_The_Version_To_The_Console()
|
||||
public void Should_Output_CLI_Version_To_The_Console()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(config =>
|
||||
{
|
||||
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||
{
|
||||
animal.AddBranch<MammalSettings>("mammal", mammal =>
|
||||
{
|
||||
mammal.AddCommand<DogCommand>("dog");
|
||||
mammal.AddCommand<HorseCommand>("horse");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run(Constants.VersionCommand);
|
||||
|
||||
// Then
|
||||
result.Output.ShouldStartWith("Spectre.Cli version ");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Output_Application_Version_To_The_Console_With_No_Command()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationVersion("1.0");
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("--version");
|
||||
|
||||
// Then
|
||||
result.Output.ShouldBe("1.0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Output_Application_Version_To_The_Console_With_Command()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationVersion("1.0");
|
||||
|
||||
configurator.AddCommand<EmptyCommand>("empty");
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("empty", "--version");
|
||||
|
||||
// Then
|
||||
result.Output.ShouldBe("1.0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Output_Application_Version_To_The_Console_With_Default_Command()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.SetDefaultCommand<EmptyCommand>();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationVersion("1.0");
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("--version");
|
||||
|
||||
// Then
|
||||
result.Output.ShouldBe("1.0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Output_Application_Version_To_The_Console_With_Branch_Default_Command()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationVersion("1.0");
|
||||
|
||||
configurator.AddBranch<EmptyCommandSettings>("branch", branch =>
|
||||
{
|
||||
branch.SetDefaultCommand<EmptyCommand>();
|
||||
});
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("--version");
|
||||
|
||||
// Then
|
||||
result.Output.ShouldBe("1.0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -362,7 +362,7 @@ public sealed partial class CommandAppTests
|
||||
});
|
||||
|
||||
// When
|
||||
var result = app.Run("-c", "0", "-v", "50", "ABBA", "Herreys");
|
||||
var result = app.Run("-c", "0", "--value", "50", "ABBA", "Herreys");
|
||||
|
||||
// Then
|
||||
result.ExitCode.ShouldBe(0);
|
||||
|
Loading…
x
Reference in New Issue
Block a user