From b551bbd244aedb102086ff66199dc9aefeda0a1f Mon Sep 17 00:00:00 2001 From: Patrik Svensson Date: Tue, 30 Sep 2025 08:59:13 +0200 Subject: [PATCH] Add OpenCLI integration to Spectre.Console.Cli --- docs/input/cli/opencli.md | 22 ++ src/Directory.Packages.props | 1 + src/Spectre.Console.Cli/CommandApp.cs | 1 + .../Internal/CommandExecutor.cs | 7 + .../Internal/Commands/ExplainCommand.cs | 2 +- .../Internal/Commands/IBuiltInCommand.cs | 9 + .../Commands/OpenCliGeneratorCommand.cs | 224 ++++++++++++++++++ .../Internal/Commands/VersionCommand.cs | 8 +- .../Internal/Commands/XmlDocCommand.cs | 8 +- src/Spectre.Console.Cli/Internal/Constants.cs | 2 + .../Internal/Extensions/OpenCliExtensions.cs | 13 + .../Internal/Extensions/TypeExtensions.cs | 15 ++ .../Modelling/CommandInfoExtensions.cs | 10 + .../Internal/Modelling/CommandParameter.cs | 2 +- src/Spectre.Console.Cli/Properties/Usings.cs | 1 + .../Spectre.Console.Cli.csproj | 5 +- src/Spectre.Console.slnx | 2 +- .../Spectre.Console.Cli.Tests/Constants.cs | 21 +- .../OpenCli/Generate.Output.verified.txt | 190 +++++++++++++++ .../Unit/CommandAppTests.OpenCli.cs | 33 +++ 20 files changed, 549 insertions(+), 27 deletions(-) create mode 100644 docs/input/cli/opencli.md create mode 100644 src/Spectre.Console.Cli/Internal/Commands/IBuiltInCommand.cs create mode 100644 src/Spectre.Console.Cli/Internal/Commands/OpenCliGeneratorCommand.cs create mode 100644 src/Spectre.Console.Cli/Internal/Extensions/OpenCliExtensions.cs create mode 100644 src/Tests/Spectre.Console.Cli.Tests/Expectations/OpenCli/Generate.Output.verified.txt create mode 100644 src/Tests/Spectre.Console.Cli.Tests/Unit/CommandAppTests.OpenCli.cs 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); + } + } +}