mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
Add OpenCLI integration to Spectre.Console.Cli
This commit is contained in:
committed by
Patrik Svensson
parent
3a70fbec75
commit
b551bbd244
22
docs/input/cli/opencli.md
Normal file
22
docs/input/cli/opencli.md
Normal file
@@ -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
|
||||||
|
```
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0" />
|
<PackageVersion Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0" />
|
||||||
<PackageVersion Include="MinVer" PrivateAssets="All" Version="6.0.0" />
|
<PackageVersion Include="MinVer" PrivateAssets="All" Version="6.0.0" />
|
||||||
|
<PackageVersion Include="OpenCli.Sources" Version="0.5.0" />
|
||||||
<PackageVersion Include="PolySharp" Version="1.15.0" />
|
<PackageVersion Include="PolySharp" Version="1.15.0" />
|
||||||
<PackageVersion Include="Roslynator.Analyzers" PrivateAssets="All" Version="4.14.0" />
|
<PackageVersion Include="Roslynator.Analyzers" PrivateAssets="All" Version="4.14.0" />
|
||||||
<PackageVersion Include="Shouldly" Version="4.3.0" />
|
<PackageVersion Include="Shouldly" Version="4.3.0" />
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public sealed class CommandApp : ICommandApp
|
|||||||
cli.AddCommand<VersionCommand>(CliConstants.Commands.Version);
|
cli.AddCommand<VersionCommand>(CliConstants.Commands.Version);
|
||||||
cli.AddCommand<XmlDocCommand>(CliConstants.Commands.XmlDoc);
|
cli.AddCommand<XmlDocCommand>(CliConstants.Commands.XmlDoc);
|
||||||
cli.AddCommand<ExplainCommand>(CliConstants.Commands.Explain);
|
cli.AddCommand<ExplainCommand>(CliConstants.Commands.Explain);
|
||||||
|
cli.AddCommand<OpenCliGeneratorCommand>(CliConstants.Commands.OpenCli);
|
||||||
});
|
});
|
||||||
|
|
||||||
_executed = true;
|
_executed = true;
|
||||||
|
|||||||
@@ -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.
|
// Parse and map the model against the arguments.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ namespace Spectre.Console.Cli;
|
|||||||
|
|
||||||
[Description("Displays diagnostics about CLI configurations")]
|
[Description("Displays diagnostics about CLI configurations")]
|
||||||
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
|
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
|
||||||
internal sealed class ExplainCommand : Command<ExplainCommand.Settings>
|
internal sealed class ExplainCommand : Command<ExplainCommand.Settings>, IBuiltInCommand
|
||||||
{
|
{
|
||||||
private readonly CommandModel _commandModel;
|
private readonly CommandModel _commandModel;
|
||||||
private readonly IAnsiConsole _writer;
|
private readonly IAnsiConsole _writer;
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a built-in command.
|
||||||
|
/// Used as a marker interface.
|
||||||
|
/// </summary>
|
||||||
|
internal interface IBuiltInCommand : ICommand
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -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<OpenCliCommand> CreateCommands(IList<CommandInfo> commands)
|
||||||
|
{
|
||||||
|
var result = new List<OpenCliCommand>();
|
||||||
|
|
||||||
|
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<OpenCliArgument> CreateArguments(IEnumerable<CommandArgument>? arguments)
|
||||||
|
{
|
||||||
|
var result = new List<OpenCliArgument>();
|
||||||
|
|
||||||
|
if (arguments == null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var argument in arguments.OrderBy(x => x.Position))
|
||||||
|
{
|
||||||
|
var metadata = default(List<OpenCliMetadata>);
|
||||||
|
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<OpenCliOption> CreateOptions(IEnumerable<CommandOption>? options)
|
||||||
|
{
|
||||||
|
var result = new List<OpenCliOption>();
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var option in options.OrderBy(o => o.GetOptionName(), StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var arguments = new List<OpenCliArgument>();
|
||||||
|
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<OpenCliMetadata>);
|
||||||
|
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<string> Aliases) GetOptionNames(CommandOption option)
|
||||||
|
{
|
||||||
|
var name = GetOptionName(option);
|
||||||
|
var aliases = new HashSet<string>();
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ namespace Spectre.Console.Cli;
|
|||||||
|
|
||||||
[Description("Displays the CLI library version")]
|
[Description("Displays the CLI library version")]
|
||||||
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
|
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
|
||||||
internal sealed class VersionCommand : Command<VersionCommand.Settings>
|
internal sealed class VersionCommand : Command, IBuiltInCommand
|
||||||
{
|
{
|
||||||
private readonly IAnsiConsole _writer;
|
private readonly IAnsiConsole _writer;
|
||||||
|
|
||||||
@@ -11,11 +11,7 @@ internal sealed class VersionCommand : Command<VersionCommand.Settings>
|
|||||||
_writer = configuration.Settings.Console.GetConsole();
|
_writer = configuration.Settings.Console.GetConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Settings : CommandSettings
|
public override int Execute(CommandContext context)
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Execute(CommandContext context, Settings settings)
|
|
||||||
{
|
{
|
||||||
_writer.MarkupLine(
|
_writer.MarkupLine(
|
||||||
"[yellow]Spectre.Cli[/] version [aqua]{0}[/]",
|
"[yellow]Spectre.Cli[/] version [aqua]{0}[/]",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ namespace Spectre.Console.Cli;
|
|||||||
|
|
||||||
[Description("Generates an XML representation of the CLI configuration.")]
|
[Description("Generates an XML representation of the CLI configuration.")]
|
||||||
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
|
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
|
||||||
internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
|
internal sealed class XmlDocCommand : Command, IBuiltInCommand
|
||||||
{
|
{
|
||||||
private readonly CommandModel _model;
|
private readonly CommandModel _model;
|
||||||
private readonly IAnsiConsole _writer;
|
private readonly IAnsiConsole _writer;
|
||||||
@@ -13,11 +13,7 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
|
|||||||
_writer = configuration.Settings.Console.GetConsole();
|
_writer = configuration.Settings.Console.GetConsole();
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Settings : CommandSettings
|
public override int Execute(CommandContext context)
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int Execute(CommandContext context, Settings settings)
|
|
||||||
{
|
{
|
||||||
_writer.Write(Serialize(_model), Style.Plain);
|
_writer.Write(Serialize(_model), Style.Plain);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ internal static class CliConstants
|
|||||||
public const string DefaultCommandName = "__default_command";
|
public const string DefaultCommandName = "__default_command";
|
||||||
public const string True = "true";
|
public const string True = "true";
|
||||||
public const string False = "false";
|
public const string False = "false";
|
||||||
|
public const string DumpHelpOpenCliOption = "--help-dump-opencli";
|
||||||
|
|
||||||
public static string[] AcceptedBooleanValues { get; } = new string[]
|
public static string[] AcceptedBooleanValues { get; } = new string[]
|
||||||
{
|
{
|
||||||
@@ -18,5 +19,6 @@ internal static class CliConstants
|
|||||||
public const string Version = "version";
|
public const string Version = "version";
|
||||||
public const string XmlDoc = "xmldoc";
|
public const string XmlDoc = "xmldoc";
|
||||||
public const string Explain = "explain";
|
public const string Explain = "explain";
|
||||||
|
public const string OpenCli = "opencli";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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<string> ReadToEndAsync(this StreamReader reader, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return reader.ReadToEndAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -16,4 +16,19 @@ internal static class TypeExtensions
|
|||||||
|
|
||||||
return false;
|
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}>";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,16 @@ namespace Spectre.Console.Cli;
|
|||||||
|
|
||||||
internal static class CommandInfoExtensions
|
internal static class CommandInfoExtensions
|
||||||
{
|
{
|
||||||
|
public static IEnumerable<CommandArgument>? GetArguments(this CommandInfo? command)
|
||||||
|
{
|
||||||
|
return command?.Parameters.OfType<CommandArgument>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<CommandOption>? GetOptions(this CommandInfo? command)
|
||||||
|
{
|
||||||
|
return command?.Parameters.OfType<CommandOption>();
|
||||||
|
}
|
||||||
|
|
||||||
public static bool HaveParentWithOption(this CommandInfo command, CommandOption option)
|
public static bool HaveParentWithOption(this CommandInfo command, CommandOption option)
|
||||||
{
|
{
|
||||||
var parent = command?.Parent;
|
var parent = command?.Parent;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
|
|||||||
DefaultValue = defaultValue;
|
DefaultValue = defaultValue;
|
||||||
PairDeconstructor = deconstructor;
|
PairDeconstructor = deconstructor;
|
||||||
ValueProvider = valueProvider;
|
ValueProvider = valueProvider;
|
||||||
Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>());
|
Validators = new List<ParameterValidationAttribute>(validators ?? []);
|
||||||
IsRequired = required;
|
IsRequired = required;
|
||||||
IsHidden = isHidden;
|
IsHidden = isHidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ global using System.IO;
|
|||||||
global using System.Linq;
|
global using System.Linq;
|
||||||
global using System.Reflection;
|
global using System.Reflection;
|
||||||
global using System.Text;
|
global using System.Text;
|
||||||
|
global using System.Threading;
|
||||||
global using System.Threading.Tasks;
|
global using System.Threading.Tasks;
|
||||||
global using System.Xml;
|
global using System.Xml;
|
||||||
global using Spectre.Console.Cli.Help;
|
global using Spectre.Console.Cli.Help;
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup>
|
|
||||||
<IsAotCompatible Condition="'$(TargetFramework)' != 'netstandard2.0'">false</IsAotCompatible>
|
<IsAotCompatible Condition="'$(TargetFramework)' != 'netstandard2.0'">false</IsAotCompatible>
|
||||||
<IsTrimmable>false</IsTrimmable>
|
<IsTrimmable>false</IsTrimmable>
|
||||||
|
<DefineConstants>$(DefineConstants)OPENCLI_VISIBILITY_INTERNAL</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Label="REMOVE THIS">
|
<ItemGroup Label="REMOVE THIS">
|
||||||
<InternalsVisibleTo Include="Spectre.Console.Cli.Tests"/>
|
<InternalsVisibleTo Include="Spectre.Console.Cli.Tests"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Label="Dependencies">
|
<ItemGroup Label="Dependencies">
|
||||||
|
<PackageReference Include="OpenCli.Sources" />
|
||||||
<PackageReference Include="PolySharp">
|
<PackageReference Include="PolySharp">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ namespace Spectre.Console.Tests;
|
|||||||
public static class Constants
|
public static class Constants
|
||||||
{
|
{
|
||||||
public static string[] VersionCommand { get; } =
|
public static string[] VersionCommand { get; } =
|
||||||
new[]
|
[
|
||||||
{
|
|
||||||
CliConstants.Commands.Branch,
|
CliConstants.Commands.Branch,
|
||||||
CliConstants.Commands.Version,
|
CliConstants.Commands.Version
|
||||||
};
|
];
|
||||||
|
|
||||||
public static string[] XmlDocCommand { get; } =
|
public static string[] XmlDocCommand { get; } =
|
||||||
new[]
|
[
|
||||||
{
|
|
||||||
CliConstants.Commands.Branch,
|
CliConstants.Commands.Branch,
|
||||||
CliConstants.Commands.XmlDoc,
|
CliConstants.Commands.XmlDoc
|
||||||
};
|
];
|
||||||
|
|
||||||
|
public static string[] OpenCliOption { get; } =
|
||||||
|
[CliConstants.DumpHelpOpenCliOption];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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<DogCommand>("dog");
|
||||||
|
animals.AddCommand<CatCommand>("cat");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = fixture.Run(Constants.OpenCliOption);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
return Verifier.Verify(result.Output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user