diff --git a/docs/input/cli/opencli.md b/docs/input/cli/opencli.md
new file mode 100644
index 00000000..248a9bbb
--- /dev/null
+++ b/docs/input/cli/opencli.md
@@ -0,0 +1,22 @@
+Title: OpenCLI Integration
+Order: 15
+Description: OpenCLI integration
+Highlights:
+ - Generate OpenCLI descriptions
+---
+
+From version `0.52.0` and above, you will be able to generate [OpenCLI](https://opencli.org)
+descriptions from your `Spectre.Console.Cli` applications.
+
+Simply add the `--help-dump-opencli` option to your application, and an
+OpenCLI description will be written to stdout.
+
+```shell
+$ ./myapp --help-dump-opencli
+```
+
+If you want to save it to disk, pipe it to a file.
+
+```shell
+$ ./myapp --help-dump-opencli > myapp.openapi.json
+```
\ No newline at end of file
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index f9ab8976..87a94c14 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -8,6 +8,7 @@
+
diff --git a/src/Spectre.Console.Cli/CommandApp.cs b/src/Spectre.Console.Cli/CommandApp.cs
index cc6712db..fa94e915 100644
--- a/src/Spectre.Console.Cli/CommandApp.cs
+++ b/src/Spectre.Console.Cli/CommandApp.cs
@@ -79,6 +79,7 @@ public sealed class CommandApp : ICommandApp
cli.AddCommand(CliConstants.Commands.Version);
cli.AddCommand(CliConstants.Commands.XmlDoc);
cli.AddCommand(CliConstants.Commands.Explain);
+ cli.AddCommand(CliConstants.Commands.OpenCli);
});
_executed = true;
diff --git a/src/Spectre.Console.Cli/Internal/CommandExecutor.cs b/src/Spectre.Console.Cli/Internal/CommandExecutor.cs
index 9d24ad06..5bfbc215 100644
--- a/src/Spectre.Console.Cli/Internal/CommandExecutor.cs
+++ b/src/Spectre.Console.Cli/Internal/CommandExecutor.cs
@@ -68,6 +68,13 @@ internal sealed class CommandExecutor
}
}
}
+
+ // OpenCLI?
+ if (firstArgument.Equals(CliConstants.DumpHelpOpenCliOption, StringComparison.OrdinalIgnoreCase))
+ {
+ // Replace all arguments with the opencligen command
+ arguments = ["cli", "opencli"];
+ }
}
// Parse and map the model against the arguments.
diff --git a/src/Spectre.Console.Cli/Internal/Commands/ExplainCommand.cs b/src/Spectre.Console.Cli/Internal/Commands/ExplainCommand.cs
index c0de6e52..c1576beb 100644
--- a/src/Spectre.Console.Cli/Internal/Commands/ExplainCommand.cs
+++ b/src/Spectre.Console.Cli/Internal/Commands/ExplainCommand.cs
@@ -2,7 +2,7 @@ namespace Spectre.Console.Cli;
[Description("Displays diagnostics about CLI configurations")]
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
-internal sealed class ExplainCommand : Command
+internal sealed class ExplainCommand : Command, IBuiltInCommand
{
private readonly CommandModel _commandModel;
private readonly IAnsiConsole _writer;
diff --git a/src/Spectre.Console.Cli/Internal/Commands/IBuiltInCommand.cs b/src/Spectre.Console.Cli/Internal/Commands/IBuiltInCommand.cs
new file mode 100644
index 00000000..d3b783e7
--- /dev/null
+++ b/src/Spectre.Console.Cli/Internal/Commands/IBuiltInCommand.cs
@@ -0,0 +1,9 @@
+namespace Spectre.Console.Cli;
+
+///
+/// Represents a built-in command.
+/// Used as a marker interface.
+///
+internal interface IBuiltInCommand : ICommand
+{
+}
\ No newline at end of file
diff --git a/src/Spectre.Console.Cli/Internal/Commands/OpenCliGeneratorCommand.cs b/src/Spectre.Console.Cli/Internal/Commands/OpenCliGeneratorCommand.cs
new file mode 100644
index 00000000..7d3e444a
--- /dev/null
+++ b/src/Spectre.Console.Cli/Internal/Commands/OpenCliGeneratorCommand.cs
@@ -0,0 +1,224 @@
+using OpenCli;
+
+namespace Spectre.Console.Cli;
+
+internal sealed class OpenCliGeneratorCommand : Command, IBuiltInCommand
+{
+ private readonly IConfiguration _configuration;
+ private readonly CommandModel _model;
+
+ public OpenCliGeneratorCommand(IConfiguration configuration, CommandModel model)
+ {
+ _configuration = configuration;
+ _model = model ?? throw new ArgumentNullException(nameof(model));
+ }
+
+ public override int Execute(CommandContext context)
+ {
+ var document = new OpenCliDocument
+ {
+ OpenCli = "0.1-draft",
+ Info = new OpenCliInfo
+ {
+ Title = ((ICommandModel)_model).ApplicationName, Version = _model.ApplicationVersion ?? "1.0",
+ },
+ Commands = CreateCommands(_model.Commands),
+ Arguments = CreateArguments(_model.DefaultCommand?.GetArguments()),
+ Options = CreateOptions(_model.DefaultCommand?.GetOptions()),
+ };
+
+ var writer = _configuration.Settings.Console.GetConsole();
+ writer.WriteLine(document.Write());
+
+ return 0;
+ }
+
+ private List CreateCommands(IList commands)
+ {
+ var result = new List();
+
+ foreach (var command in commands.OrderBy(o => o.Name, StringComparer.OrdinalIgnoreCase))
+ {
+ if (typeof(IBuiltInCommand).IsAssignableFrom(command.CommandType))
+ {
+ continue;
+ }
+
+ var openCliCommand = new OpenCliCommand
+ {
+ Name = command.Name,
+ Aliases =
+ [
+ ..command.Aliases.OrderBy(str => str)
+ ],
+ Commands = CreateCommands(command.Children),
+ Arguments = CreateArguments(command.GetArguments()),
+ Options = CreateOptions(command.GetOptions()),
+ Description = command.Description,
+ Hidden = command.IsHidden,
+ Examples = [..command.Examples.Select(example => string.Join(" ", example))],
+ };
+
+ // Skip branches without commands
+ if (command.IsBranch && openCliCommand.Commands.Count == 0)
+ {
+ continue;
+ }
+
+ result.Add(openCliCommand);
+ }
+
+ return result;
+ }
+
+ private List CreateArguments(IEnumerable? arguments)
+ {
+ var result = new List();
+
+ if (arguments == null)
+ {
+ return result;
+ }
+
+ foreach (var argument in arguments.OrderBy(x => x.Position))
+ {
+ var metadata = default(List);
+ if (argument.ParameterType != typeof(void) &&
+ argument.ParameterType != typeof(bool))
+ {
+ metadata =
+ [
+ new OpenCliMetadata { Name = "ClrType", Value = argument.ParameterType.ToCliTypeString(), },
+ ];
+ }
+
+ result.Add(new OpenCliArgument
+ {
+ Name = argument.Value,
+ Required = argument.IsRequired,
+ Arity = new OpenCliArity
+ {
+ // TODO: Look into this
+ Minimum = 1,
+ Maximum = 1,
+ },
+ Description = argument.Description,
+ Hidden = argument.IsHidden,
+ Metadata = metadata,
+ AcceptedValues = null,
+ Group = null,
+ });
+ }
+
+ return result;
+ }
+
+ private List CreateOptions(IEnumerable? options)
+ {
+ var result = new List();
+
+ if (options == null)
+ {
+ return result;
+ }
+
+ foreach (var option in options.OrderBy(o => o.GetOptionName(), StringComparer.OrdinalIgnoreCase))
+ {
+ var arguments = new List();
+ if (option.ParameterType != typeof(void) &&
+ option.ParameterType != typeof(bool))
+ {
+ arguments.Add(new OpenCliArgument
+ {
+ Name = option.ValueName ?? "VALUE",
+ Required = !option.ValueIsOptional,
+ Arity = new OpenCliArity
+ {
+ // TODO: Look into this
+ Minimum = option.ValueIsOptional
+ ? 0
+ : 1,
+ Maximum = 1,
+ },
+ AcceptedValues = null,
+ Group = null,
+ Hidden = null,
+ Metadata =
+ [
+ new OpenCliMetadata
+ {
+ Name = "ClrType",
+ Value = option.ParameterType.ToCliTypeString(),
+ },
+ ],
+ });
+ }
+
+ var optionMetadata = default(List);
+ if (arguments.Count == 0 && option.ParameterType != typeof(void) &&
+ option.ParameterType != typeof(bool))
+ {
+ optionMetadata =
+ [
+ new OpenCliMetadata { Name = "ClrType", Value = option.ParameterType.ToCliTypeString(), },
+ ];
+ }
+
+ var (optionName, optionAliases) = GetOptionNames(option);
+ result.Add(new OpenCliOption
+ {
+ Name = optionName,
+ Required = option.IsRequired,
+ Aliases = [..optionAliases.OrderBy(str => str)],
+ Arguments = arguments,
+ Description = option.Description,
+ Group = null,
+ Hidden = option.IsHidden,
+ Recursive = option.IsShadowed, // TODO: Is this correct?
+ Metadata = optionMetadata,
+ });
+ }
+
+ return result;
+ }
+
+ private static (string Name, HashSet Aliases) GetOptionNames(CommandOption option)
+ {
+ var name = GetOptionName(option);
+ var aliases = new HashSet();
+
+ if (option.LongNames.Count > 0)
+ {
+ foreach (var alias in option.LongNames.Skip(1))
+ {
+ aliases.Add("--" + alias);
+ }
+
+ foreach (var alias in option.ShortNames)
+ {
+ aliases.Add("-" + alias);
+ }
+ }
+ else
+ {
+ foreach (var alias in option.LongNames)
+ {
+ aliases.Add("--" + alias);
+ }
+
+ foreach (var alias in option.ShortNames.Skip(1))
+ {
+ aliases.Add("-" + alias);
+ }
+ }
+
+ return (name, aliases);
+ }
+
+ private static string GetOptionName(CommandOption option)
+ {
+ return option.LongNames.Count > 0
+ ? "--" + option.LongNames[0]
+ : "-" + option.ShortNames[0];
+ }
+}
\ No newline at end of file
diff --git a/src/Spectre.Console.Cli/Internal/Commands/VersionCommand.cs b/src/Spectre.Console.Cli/Internal/Commands/VersionCommand.cs
index 20f3e70d..419171e7 100644
--- a/src/Spectre.Console.Cli/Internal/Commands/VersionCommand.cs
+++ b/src/Spectre.Console.Cli/Internal/Commands/VersionCommand.cs
@@ -2,7 +2,7 @@ namespace Spectre.Console.Cli;
[Description("Displays the CLI library version")]
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
-internal sealed class VersionCommand : Command
+internal sealed class VersionCommand : Command, IBuiltInCommand
{
private readonly IAnsiConsole _writer;
@@ -11,11 +11,7 @@ internal sealed class VersionCommand : Command
_writer = configuration.Settings.Console.GetConsole();
}
- public sealed class Settings : CommandSettings
- {
- }
-
- public override int Execute(CommandContext context, Settings settings)
+ public override int Execute(CommandContext context)
{
_writer.MarkupLine(
"[yellow]Spectre.Cli[/] version [aqua]{0}[/]",
diff --git a/src/Spectre.Console.Cli/Internal/Commands/XmlDocCommand.cs b/src/Spectre.Console.Cli/Internal/Commands/XmlDocCommand.cs
index c5de6bbd..2b322e02 100644
--- a/src/Spectre.Console.Cli/Internal/Commands/XmlDocCommand.cs
+++ b/src/Spectre.Console.Cli/Internal/Commands/XmlDocCommand.cs
@@ -2,7 +2,7 @@ namespace Spectre.Console.Cli;
[Description("Generates an XML representation of the CLI configuration.")]
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
-internal sealed class XmlDocCommand : Command
+internal sealed class XmlDocCommand : Command, IBuiltInCommand
{
private readonly CommandModel _model;
private readonly IAnsiConsole _writer;
@@ -13,11 +13,7 @@ internal sealed class XmlDocCommand : Command
_writer = configuration.Settings.Console.GetConsole();
}
- public sealed class Settings : CommandSettings
- {
- }
-
- public override int Execute(CommandContext context, Settings settings)
+ public override int Execute(CommandContext context)
{
_writer.Write(Serialize(_model), Style.Plain);
return 0;
diff --git a/src/Spectre.Console.Cli/Internal/Constants.cs b/src/Spectre.Console.Cli/Internal/Constants.cs
index 2edb1891..661af7c1 100644
--- a/src/Spectre.Console.Cli/Internal/Constants.cs
+++ b/src/Spectre.Console.Cli/Internal/Constants.cs
@@ -5,6 +5,7 @@ internal static class CliConstants
public const string DefaultCommandName = "__default_command";
public const string True = "true";
public const string False = "false";
+ public const string DumpHelpOpenCliOption = "--help-dump-opencli";
public static string[] AcceptedBooleanValues { get; } = new string[]
{
@@ -18,5 +19,6 @@ internal static class CliConstants
public const string Version = "version";
public const string XmlDoc = "xmldoc";
public const string Explain = "explain";
+ public const string OpenCli = "opencli";
}
}
\ No newline at end of file
diff --git a/src/Spectre.Console.Cli/Internal/Extensions/OpenCliExtensions.cs b/src/Spectre.Console.Cli/Internal/Extensions/OpenCliExtensions.cs
new file mode 100644
index 00000000..7856a106
--- /dev/null
+++ b/src/Spectre.Console.Cli/Internal/Extensions/OpenCliExtensions.cs
@@ -0,0 +1,13 @@
+#if NETSTANDARD2_0
+namespace System.IO;
+
+// Polyfills needed for OpenCli.
+// This can be removed once me migrate over to the Polyfill library.
+internal static class OpenCliExtensions
+{
+ public static Task ReadToEndAsync(this StreamReader reader, CancellationToken cancellationToken)
+ {
+ return reader.ReadToEndAsync();
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Spectre.Console.Cli/Internal/Extensions/TypeExtensions.cs b/src/Spectre.Console.Cli/Internal/Extensions/TypeExtensions.cs
index fe1def3c..543a3702 100644
--- a/src/Spectre.Console.Cli/Internal/Extensions/TypeExtensions.cs
+++ b/src/Spectre.Console.Cli/Internal/Extensions/TypeExtensions.cs
@@ -16,4 +16,19 @@ internal static class TypeExtensions
return false;
}
+
+ // Taken from https://github.com/dotnet/sdk/blob/main/src/Cli/Microsoft.DotNet.Cli.Utils/Extensions/TypeExtensions.cs#L15
+ // Licensed under MIT
+ public static string ToCliTypeString(this Type type)
+ {
+ var typeName = type.FullName ?? string.Empty;
+ if (!type.IsGenericType)
+ {
+ return typeName;
+ }
+
+ var genericTypeName = typeName.Substring(0, typeName.IndexOf('`'));
+ var genericTypes = string.Join(", ", type.GenericTypeArguments.Select(generic => generic.ToCliTypeString()));
+ return $"{genericTypeName}<{genericTypes}>";
+ }
}
\ No newline at end of file
diff --git a/src/Spectre.Console.Cli/Internal/Modelling/CommandInfoExtensions.cs b/src/Spectre.Console.Cli/Internal/Modelling/CommandInfoExtensions.cs
index b2aa9e61..140fe290 100644
--- a/src/Spectre.Console.Cli/Internal/Modelling/CommandInfoExtensions.cs
+++ b/src/Spectre.Console.Cli/Internal/Modelling/CommandInfoExtensions.cs
@@ -2,6 +2,16 @@ namespace Spectre.Console.Cli;
internal static class CommandInfoExtensions
{
+ public static IEnumerable? GetArguments(this CommandInfo? command)
+ {
+ return command?.Parameters.OfType();
+ }
+
+ public static IEnumerable? GetOptions(this CommandInfo? command)
+ {
+ return command?.Parameters.OfType();
+ }
+
public static bool HaveParentWithOption(this CommandInfo command, CommandOption option)
{
var parent = command?.Parent;
diff --git a/src/Spectre.Console.Cli/Internal/Modelling/CommandParameter.cs b/src/Spectre.Console.Cli/Internal/Modelling/CommandParameter.cs
index 1f8f0594..a854abf0 100644
--- a/src/Spectre.Console.Cli/Internal/Modelling/CommandParameter.cs
+++ b/src/Spectre.Console.Cli/Internal/Modelling/CommandParameter.cs
@@ -38,7 +38,7 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
DefaultValue = defaultValue;
PairDeconstructor = deconstructor;
ValueProvider = valueProvider;
- Validators = new List(validators ?? Array.Empty());
+ Validators = new List(validators ?? []);
IsRequired = required;
IsHidden = isHidden;
}
diff --git a/src/Spectre.Console.Cli/Properties/Usings.cs b/src/Spectre.Console.Cli/Properties/Usings.cs
index 48d442f5..4cd53971 100644
--- a/src/Spectre.Console.Cli/Properties/Usings.cs
+++ b/src/Spectre.Console.Cli/Properties/Usings.cs
@@ -9,6 +9,7 @@ global using System.IO;
global using System.Linq;
global using System.Reflection;
global using System.Text;
+global using System.Threading;
global using System.Threading.Tasks;
global using System.Xml;
global using Spectre.Console.Cli.Help;
diff --git a/src/Spectre.Console.Cli/Spectre.Console.Cli.csproj b/src/Spectre.Console.Cli/Spectre.Console.Cli.csproj
index 66cfb70d..e4f5f1a7 100644
--- a/src/Spectre.Console.Cli/Spectre.Console.Cli.csproj
+++ b/src/Spectre.Console.Cli/Spectre.Console.Cli.csproj
@@ -3,11 +3,11 @@
net9.0;net8.0;netstandard2.0
true
-
-
false
false
+ $(DefineConstants)OPENCLI_VISIBILITY_INTERNAL
+
@@ -18,6 +18,7 @@
+
all
runtime; build; native; contentfiles; analyzers
diff --git a/src/Spectre.Console.slnx b/src/Spectre.Console.slnx
index 1ce05c2b..25d17f02 100644
--- a/src/Spectre.Console.slnx
+++ b/src/Spectre.Console.slnx
@@ -28,4 +28,4 @@
-
+
\ No newline at end of file
diff --git a/src/Tests/Spectre.Console.Cli.Tests/Constants.cs b/src/Tests/Spectre.Console.Cli.Tests/Constants.cs
index 33af2441..914854f0 100644
--- a/src/Tests/Spectre.Console.Cli.Tests/Constants.cs
+++ b/src/Tests/Spectre.Console.Cli.Tests/Constants.cs
@@ -3,16 +3,17 @@ namespace Spectre.Console.Tests;
public static class Constants
{
public static string[] VersionCommand { get; } =
- new[]
- {
- CliConstants.Commands.Branch,
- CliConstants.Commands.Version,
- };
+ [
+ CliConstants.Commands.Branch,
+ CliConstants.Commands.Version
+ ];
public static string[] XmlDocCommand { get; } =
- new[]
- {
- CliConstants.Commands.Branch,
- CliConstants.Commands.XmlDoc,
- };
+ [
+ CliConstants.Commands.Branch,
+ CliConstants.Commands.XmlDoc
+ ];
+
+ public static string[] OpenCliOption { get; } =
+ [CliConstants.DumpHelpOpenCliOption];
}
diff --git a/src/Tests/Spectre.Console.Cli.Tests/Expectations/OpenCli/Generate.Output.verified.txt b/src/Tests/Spectre.Console.Cli.Tests/Expectations/OpenCli/Generate.Output.verified.txt
new file mode 100644
index 00000000..3cb036e0
--- /dev/null
+++ b/src/Tests/Spectre.Console.Cli.Tests/Expectations/OpenCli/Generate.Output.verified.txt
@@ -0,0 +1,190 @@
+{
+ "opencli": "0.1-draft",
+ "info": {
+ "title": "my-app",
+ "version": "1.2.3"
+ },
+ "commands": [
+ {
+ "name": "animals",
+ "commands": [
+ {
+ "name": "cat",
+ "options": [
+ {
+ "name": "--agility",
+ "required": false,
+ "arguments": [
+ {
+ "name": "VALUE",
+ "required": true,
+ "arity": {
+ "minimum": 1,
+ "maximum": 1
+ },
+ "metadata": [
+ {
+ "name": "ClrType",
+ "value": "System.Int32"
+ }
+ ]
+ }
+ ],
+ "description": "The agility between 0 and 100.",
+ "recursive": false,
+ "hidden": false
+ },
+ {
+ "name": "--alive",
+ "required": false,
+ "aliases": [
+ "--not-dead",
+ "-a"
+ ],
+ "description": "Indicates whether or not the animal is alive.",
+ "recursive": false,
+ "hidden": false
+ },
+ {
+ "name": "--name",
+ "required": false,
+ "aliases": [
+ "--pet-name",
+ "-n",
+ "-p"
+ ],
+ "arguments": [
+ {
+ "name": "VALUE",
+ "required": true,
+ "arity": {
+ "minimum": 1,
+ "maximum": 1
+ },
+ "metadata": [
+ {
+ "name": "ClrType",
+ "value": "System.String"
+ }
+ ]
+ }
+ ],
+ "recursive": false,
+ "hidden": false
+ }
+ ],
+ "arguments": [
+ {
+ "name": "LEGS",
+ "required": false,
+ "arity": {
+ "minimum": 1,
+ "maximum": 1
+ },
+ "description": "The number of legs.",
+ "hidden": false,
+ "metadata": [
+ {
+ "name": "ClrType",
+ "value": "System.Int32"
+ }
+ ]
+ }
+ ],
+ "hidden": false,
+ "examples": []
+ },
+ {
+ "name": "dog",
+ "options": [
+ {
+ "name": "--alive",
+ "required": false,
+ "aliases": [
+ "--not-dead",
+ "-a"
+ ],
+ "description": "Indicates whether or not the animal is alive.",
+ "recursive": false,
+ "hidden": false
+ },
+ {
+ "name": "--good-boy",
+ "required": false,
+ "aliases": [
+ "-g"
+ ],
+ "recursive": false,
+ "hidden": false
+ },
+ {
+ "name": "--name",
+ "required": false,
+ "aliases": [
+ "--pet-name",
+ "-n",
+ "-p"
+ ],
+ "arguments": [
+ {
+ "name": "VALUE",
+ "required": true,
+ "arity": {
+ "minimum": 1,
+ "maximum": 1
+ },
+ "metadata": [
+ {
+ "name": "ClrType",
+ "value": "System.String"
+ }
+ ]
+ }
+ ],
+ "recursive": false,
+ "hidden": false
+ }
+ ],
+ "arguments": [
+ {
+ "name": "LEGS",
+ "required": false,
+ "arity": {
+ "minimum": 1,
+ "maximum": 1
+ },
+ "description": "The number of legs.",
+ "hidden": false,
+ "metadata": [
+ {
+ "name": "ClrType",
+ "value": "System.Int32"
+ }
+ ]
+ },
+ {
+ "name": "AGE",
+ "required": true,
+ "arity": {
+ "minimum": 1,
+ "maximum": 1
+ },
+ "hidden": false,
+ "metadata": [
+ {
+ "name": "ClrType",
+ "value": "System.Int32"
+ }
+ ]
+ }
+ ],
+ "description": "The dog command.",
+ "hidden": false,
+ "examples": []
+ }
+ ],
+ "hidden": false,
+ "examples": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/Tests/Spectre.Console.Cli.Tests/Unit/CommandAppTests.OpenCli.cs b/src/Tests/Spectre.Console.Cli.Tests/Unit/CommandAppTests.OpenCli.cs
new file mode 100644
index 00000000..3baa327c
--- /dev/null
+++ b/src/Tests/Spectre.Console.Cli.Tests/Unit/CommandAppTests.OpenCli.cs
@@ -0,0 +1,33 @@
+namespace Spectre.Console.Tests.Unit.Cli;
+
+public sealed partial class CommandAppTests
+{
+ [ExpectationPath("OpenCli")]
+ public sealed partial class OpenCli
+ {
+ [Fact]
+ [Expectation("Generate")]
+ public Task Should_Output_OpenCli_Description()
+ {
+ // Given
+ var fixture = new CommandAppTester();
+ fixture.Configure(config =>
+ {
+ config.SetApplicationName("my-app");
+ config.SetApplicationVersion("1.2.3");
+
+ config.AddBranch("animals", animals =>
+ {
+ animals.AddCommand("dog");
+ animals.AddCommand("cat");
+ });
+ });
+
+ // When
+ var result = fixture.Run(Constants.OpenCliOption);
+
+ // Then
+ return Verifier.Verify(result.Output);
+ }
+ }
+}