mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-08-01 01:35:58 +08:00
Compare commits
16 Commits
c2f21b9eb0
...
37a04f3a74
Author | SHA1 | Date | |
---|---|---|---|
37a04f3a74 | |||
f32f80dc57 | |||
7f3ebe02c4 | |||
d77bfb6391 | |||
7819f0693d | |||
465be9391b | |||
7e5ddb1efe | |||
aabe8eeaf8 | |||
108b23fca8 | |||
7051bc9e2d | |||
65bab890f2 | |||
bd0e2d3e22 | |||
9efc426eb9 | |||
2570202990 | |||
e4b5b56d93 | |||
67c3909bbb |
@ -57,15 +57,15 @@ Task("Test")
|
||||
});
|
||||
|
||||
Task("Package")
|
||||
.IsDependentOn("Test")
|
||||
//.IsDependentOn("Test")
|
||||
.Does(context =>
|
||||
{
|
||||
context.DotNetPack($"./src/Spectre.Console.sln", new DotNetPackSettings {
|
||||
Configuration = configuration,
|
||||
Verbosity = DotNetVerbosity.Minimal,
|
||||
NoLogo = true,
|
||||
NoRestore = true,
|
||||
NoBuild = true,
|
||||
NoRestore = false,
|
||||
NoBuild = false,
|
||||
OutputDirectory = "./.artifacts",
|
||||
MSBuildSettings = new DotNetMSBuildSettings()
|
||||
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
|
||||
@ -73,7 +73,7 @@ Task("Package")
|
||||
});
|
||||
|
||||
Task("Publish-NuGet")
|
||||
.WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions")
|
||||
//.WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions")
|
||||
.IsDependentOn("Package")
|
||||
.Does(context =>
|
||||
{
|
||||
@ -90,6 +90,7 @@ Task("Publish-NuGet")
|
||||
{
|
||||
Source = "https://api.nuget.org/v3/index.json",
|
||||
ApiKey = apiKey,
|
||||
SkipDuplicate = true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -38,7 +38,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Playwright" Version="1.51.0" />
|
||||
<PackageReference Include="Microsoft.Playwright" Version="1.52.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Statiq.CodeAnalysis" Version="1.0.0-beta.72" />
|
||||
<PackageReference Include="Statiq.Common" Version="1.0.0-beta.72" />
|
||||
|
2
docs/input/assets/casts/align-rich.cast
Normal file
2
docs/input/assets/casts/align-rich.cast
Normal file
@ -0,0 +1,2 @@
|
||||
{"version": 2, "width": 40, "height": 3, "timestamp": 1667342769, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}}
|
||||
[0.0, "o", "\u001b[H\u001b[2B\u001b[38;5;9;48;5;0mSpectre!\u001b[0m"]
|
66
docs/input/widgets/align.md
Normal file
66
docs/input/widgets/align.md
Normal file
@ -0,0 +1,66 @@
|
||||
Title: Align
|
||||
Description: "Use **Align** to render and position widgets in the console."
|
||||
Highlights:
|
||||
- Custom colors
|
||||
- Labels
|
||||
- Use your own data with a converter.
|
||||
Reference: T:Spectre.Console.Align
|
||||
|
||||
---
|
||||
|
||||
Use `Align` to render and position widgets in the console.
|
||||
|
||||
<?# AsciiCast cast="align" /?>
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic usage
|
||||
|
||||
```csharp
|
||||
// Render an item and align it in the bottom-left corner of the console
|
||||
AnsiConsole.Write(new Align(
|
||||
new Text("Spectre!"),
|
||||
HorizontalAlignment.Left,
|
||||
VerticalAlignment.Bottom
|
||||
));
|
||||
```
|
||||
|
||||
### Align items from an IEnumerable
|
||||
|
||||
```csharp
|
||||
// Create a list of items
|
||||
var alignItems = new List<Text>(){
|
||||
new Text("Spectre"),
|
||||
new Text("Console"),
|
||||
new Text("Is Awesome!")
|
||||
};
|
||||
|
||||
// Render the items in the middle-right of the console
|
||||
AnsiConsole.Write(new Align(
|
||||
alignItems,
|
||||
HorizontalAlignment.Right,
|
||||
VerticalAlignment.Middle
|
||||
));
|
||||
```
|
||||
|
||||
### Dynamically align with different widgets
|
||||
|
||||
```csharp
|
||||
// Create a table
|
||||
var table = new Table()
|
||||
.AddColumn("ID")
|
||||
.AddColumn("Methods")
|
||||
.AddColumn("Purpose")
|
||||
.AddRow("1", "Center()", "Initializes a new instance that is center aligned")
|
||||
.AddRow("2", "Measure()", "Measures the renderable object")
|
||||
.AddRow("3", "Right()", "Initializes a new instance that is right aligned.");
|
||||
|
||||
// Create a panel
|
||||
var panel = new Panel(table)
|
||||
.Header("Other Align Methods")
|
||||
.Border(BoxBorder.Double);
|
||||
|
||||
// Renders the panel in the top-center of the console
|
||||
AnsiConsole.Write(new Align(panel, HorizontalAlignment.Center, VerticalAlignment.Top));
|
||||
```
|
||||
|
@ -43,10 +43,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="1.2.0" />
|
||||
<PackageReference Include="AngleSharp" Version="1.3.0" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Scriban" Version="6.2.0" />
|
||||
<PackageReference Include="Scriban" Version="6.2.1" />
|
||||
<PackageReference Include="Spectre.IO" Version="0.18.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
<LangVersion>12</LangVersion>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>embedded</DebugType>
|
||||
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
@ -12,6 +11,7 @@
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\..\resources\spectre.snk</AssemblyOriginatorKeyFile>
|
||||
<PublicKey>00240000048000009400000006020000002400005253413100040000010001006146d3789d31477cf4a3b508dcf772ff9ccad8613f6bd6b17b9c4a960a7a7b551ecd22e4f4119ced70ee8bbdf3ca0a117c99fd6248c16255ea9033110c2233d42e74e81bf4f3f7eb09bfe8b53ad399d957514f427171a86f5fe9fe0014be121d571c80c4a0cfc3531bdbf5a2900d936d93f2c94171b9134f7644a1ac3612a0d0</PublicKey>
|
||||
<Version>1.0.3</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Deterministic Build" Condition="'$(GITHUB_ACTIONS)' == 'true'">
|
||||
@ -56,7 +56,6 @@
|
||||
|
||||
<!-- Allow folks to build with minimal dependencies (though they will need to provide their own Version data) -->
|
||||
<ItemGroup Label="Build Tools Package References" Condition="'$(UseBuildTimeTools)' != 'false'">
|
||||
<PackageReference Include="MinVer" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
|
@ -1,8 +1,2 @@
|
||||
<Project>
|
||||
<Target Name="Versioning" BeforeTargets="MinVer">
|
||||
<PropertyGroup Label="Build">
|
||||
<MinVerDefaultPreReleaseIdentifiers>preview.0</MinVerDefaultPreReleaseIdentifiers>
|
||||
<MinVerVerbosity>normal</MinVerVerbosity>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
</Project>
|
@ -6,22 +6,22 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="IsExternalInit" Version="1.0.3"/>
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3"/>
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0"/>
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6"/>
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0"/>
|
||||
<PackageVersion Include="MinVer" PrivateAssets="All" Version="6.0.0"/>
|
||||
<PackageVersion Include="PolySharp" Version="1.15.0"/>
|
||||
<PackageVersion Include="Roslynator.Analyzers" PrivateAssets="All" Version="4.13.1"/>
|
||||
<PackageVersion Include="Shouldly" Version="4.3.0"/>
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7"/>
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.10"/>
|
||||
<PackageVersion Include="Spectre.Verify.Extensions" Version="28.16.0"/>
|
||||
<PackageVersion Include="StyleCop.Analyzers" PrivateAssets="All" Version="1.2.0-beta.556"/>
|
||||
<PackageVersion Include="System.Memory" Version="4.6.3"/>
|
||||
<PackageVersion Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160"/>
|
||||
<PackageVersion Include="Verify.Xunit" Version="29.2.0"/>
|
||||
<PackageVersion Include="Verify.Xunit" Version="30.4.0"/>
|
||||
<PackageVersion Include="Wcwidth.Sources" Version="2.0.0"/>
|
||||
<PackageVersion Include="xunit" Version="2.9.3"/>
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2"/>
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.1"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageId>NetAdmin.Spectre.Console.ImageSharp</PackageId>
|
||||
<Description>A library that extends Spectre.Console with ImageSharp superpowers.</Description>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
|
@ -4,6 +4,7 @@
|
||||
<TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageId>NetAdmin.Spectre.Console.Json</PackageId>
|
||||
<Description>A library that extends Spectre.Console with JSON superpowers.</Description>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
|
@ -45,6 +45,6 @@ public sealed class CommandArgumentAttribute : Attribute
|
||||
// Assign the result.
|
||||
Position = position;
|
||||
ValueName = result.Value;
|
||||
IsRequired = result.Required;
|
||||
IsRequired = result.IsRequired;
|
||||
}
|
||||
}
|
@ -30,6 +30,11 @@ public sealed class CommandOptionAttribute : Attribute
|
||||
/// </summary>
|
||||
public bool ValueIsOptional { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the value is required.
|
||||
/// </summary>
|
||||
public bool IsRequired { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this option is hidden from the help text.
|
||||
/// </summary>
|
||||
@ -39,7 +44,8 @@ public sealed class CommandOptionAttribute : Attribute
|
||||
/// Initializes a new instance of the <see cref="CommandOptionAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="template">The option template.</param>
|
||||
public CommandOptionAttribute(string template)
|
||||
/// <param name="isRequired">Indicates whether the option is required or not.</param>
|
||||
public CommandOptionAttribute(string template, bool isRequired = false)
|
||||
{
|
||||
if (template == null)
|
||||
{
|
||||
@ -54,6 +60,7 @@ public sealed class CommandOptionAttribute : Attribute
|
||||
ShortNames = result.ShortNames;
|
||||
ValueName = result.Value;
|
||||
ValueIsOptional = result.ValueIsOptional;
|
||||
IsRequired = isRequired;
|
||||
}
|
||||
|
||||
internal bool IsMatch(string name)
|
||||
|
24
src/Spectre.Console.Cli/Annotations/LocalizationAttribute.cs
Normal file
24
src/Spectre.Console.Cli/Annotations/LocalizationAttribute.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a "Description" feature should display its localization description.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
|
||||
public class LocalizationAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or Sets a strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
public Type ResourceClass { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalizationAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="resourceClass">
|
||||
/// The type of the resource manager.
|
||||
/// </param>
|
||||
public LocalizationAttribute(Type resourceClass)
|
||||
{
|
||||
ResourceClass = resourceClass;
|
||||
}
|
||||
}
|
@ -13,13 +13,13 @@ public abstract class AsyncCommand : ICommand<EmptyCommandSettings>
|
||||
public abstract Task<int> ExecuteAsync(CommandContext context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings)
|
||||
Task<int> ICommand<EmptyCommandSettings>.ExecuteAsync(CommandContext context, EmptyCommandSettings settings)
|
||||
{
|
||||
return ExecuteAsync(context);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
|
||||
Task<int> ICommand.ExecuteAsync(CommandContext context, CommandSettings settings)
|
||||
{
|
||||
return ExecuteAsync(context);
|
||||
}
|
||||
|
@ -33,14 +33,14 @@ public abstract class AsyncCommand<TSettings> : ICommand<TSettings>
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
|
||||
Task<int> ICommand.ExecuteAsync(CommandContext context, CommandSettings settings)
|
||||
{
|
||||
Debug.Assert(settings is TSettings, "Command settings is of unexpected type.");
|
||||
return ExecuteAsync(context, (TSettings)settings);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings)
|
||||
Task<int> ICommand<TSettings>.ExecuteAsync(CommandContext context, TSettings settings)
|
||||
{
|
||||
return ExecuteAsync(context, settings);
|
||||
}
|
||||
|
@ -14,13 +14,13 @@ public abstract class Command : ICommand<EmptyCommandSettings>
|
||||
public abstract int Execute(CommandContext context);
|
||||
|
||||
/// <inheritdoc/>
|
||||
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings)
|
||||
Task<int> ICommand<EmptyCommandSettings>.ExecuteAsync(CommandContext context, EmptyCommandSettings settings)
|
||||
{
|
||||
return Task.FromResult(Execute(context));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
|
||||
Task<int> ICommand.ExecuteAsync(CommandContext context, CommandSettings settings)
|
||||
{
|
||||
return Task.FromResult(Execute(context));
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ public sealed class CommandApp : ICommandApp
|
||||
}
|
||||
|
||||
return await _executor
|
||||
.Execute(_configurator, args)
|
||||
.ExecuteAsync(_configurator, args)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -34,14 +34,14 @@ public abstract class Command<TSettings> : ICommand<TSettings>
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
|
||||
Task<int> ICommand.ExecuteAsync(CommandContext context, CommandSettings settings)
|
||||
{
|
||||
Debug.Assert(settings is TSettings, "Command settings is of unexpected type.");
|
||||
return Task.FromResult(Execute(context, (TSettings)settings));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings)
|
||||
Task<int> ICommand<TSettings>.ExecuteAsync(CommandContext context, TSettings settings)
|
||||
{
|
||||
return Task.FromResult(Execute(context, settings));
|
||||
}
|
||||
|
@ -37,6 +37,16 @@ public class CommandRuntimeException : CommandAppException
|
||||
return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{argument.Value}'.");
|
||||
}
|
||||
|
||||
internal static CommandRuntimeException MissingRequiredOption(CommandTree node, CommandOption option)
|
||||
{
|
||||
if (node.Command.Name == CliConstants.DefaultCommandName)
|
||||
{
|
||||
return new CommandRuntimeException($"Missing required option '{option.GetOptionName()}'.");
|
||||
}
|
||||
|
||||
return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{option.GetOptionName()}'.");
|
||||
}
|
||||
|
||||
internal static CommandRuntimeException NoConverterFound(CommandParameter parameter)
|
||||
{
|
||||
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");
|
||||
|
@ -53,8 +53,8 @@ public class HelpProvider : IHelpProvider
|
||||
{
|
||||
var arguments = new List<HelpArgument>();
|
||||
arguments.AddRange(command?.Parameters?.OfType<ICommandArgument>()?.Select(
|
||||
x => new HelpArgument(x.Value, x.Position, x.Required, x.Description))
|
||||
?? Array.Empty<HelpArgument>());
|
||||
x => new HelpArgument(x.Value, x.Position, x.IsRequired, x.Description))
|
||||
?? Array.Empty<HelpArgument>());
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
@ -65,15 +65,20 @@ public class HelpProvider : IHelpProvider
|
||||
public string? Long { get; }
|
||||
public string? Value { get; }
|
||||
public bool? ValueIsOptional { get; }
|
||||
public bool IsRequired { get; }
|
||||
public string? Description { get; }
|
||||
public object? DefaultValue { get; }
|
||||
|
||||
private HelpOption(string? @short, string? @long, string? @value, bool? valueIsOptional, string? description, object? defaultValue)
|
||||
private HelpOption(
|
||||
string? @short, string? @long, string? @value,
|
||||
bool? valueIsOptional, bool isRequired,
|
||||
string? description, object? defaultValue)
|
||||
{
|
||||
Short = @short;
|
||||
Long = @long;
|
||||
Value = value;
|
||||
ValueIsOptional = valueIsOptional;
|
||||
IsRequired = isRequired;
|
||||
Description = description;
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
@ -85,7 +90,8 @@ public class HelpProvider : IHelpProvider
|
||||
{
|
||||
var parameters = new List<HelpOption>
|
||||
{
|
||||
new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null),
|
||||
new HelpOption("h", "help", null, null, false,
|
||||
resources.PrintHelpDescription, null),
|
||||
};
|
||||
|
||||
// Version information applies to the entire CLI application.
|
||||
@ -107,17 +113,18 @@ public class HelpProvider : IHelpProvider
|
||||
// Only show the version option if there is an application version set.
|
||||
if (model.ApplicationVersion != null)
|
||||
{
|
||||
parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null));
|
||||
parameters.Add(new HelpOption("v", "version", null, null, false,
|
||||
resources.PrintVersionDescription, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.IsFlag && o.DefaultValue?.Value is false ? null : o.DefaultValue?.Value))
|
||||
?? Array.Empty<HelpOption>());
|
||||
new HelpOption(
|
||||
o.ShortNames.FirstOrDefault(), o.LongNames.FirstOrDefault(),
|
||||
o.ValueName, o.ValueIsOptional, o.IsRequired, o.Description,
|
||||
o.IsFlag && o.DefaultValue?.Value is false ? null : o.DefaultValue?.Value))
|
||||
?? Array.Empty<HelpOption>());
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
@ -215,7 +222,9 @@ public class HelpProvider : IHelpProvider
|
||||
{
|
||||
if (isCurrent)
|
||||
{
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.CurrentCommand ?? Style.Plain, $"{current.Name}"));
|
||||
parameters.Add(NewComposer().Style(
|
||||
helpStyles?.Usage?.CurrentCommand ?? Style.Plain,
|
||||
$"{current.Name}"));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -228,38 +237,46 @@ public class HelpProvider : IHelpProvider
|
||||
if (isCurrent)
|
||||
{
|
||||
foreach (var argument in current.Parameters.OfType<ICommandArgument>()
|
||||
.Where(a => a.Required).OrderBy(a => a.Position).ToArray())
|
||||
.Where(a => a.IsRequired).OrderBy(a => a.Position).ToArray())
|
||||
{
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.RequiredArgument ?? Style.Plain, $"<{argument.Value}>"));
|
||||
parameters.Add(NewComposer().Style(
|
||||
helpStyles?.Usage?.RequiredArgument ?? Style.Plain,
|
||||
$"<{argument.Value}>"));
|
||||
}
|
||||
}
|
||||
|
||||
var optionalArguments = current.Parameters.OfType<ICommandArgument>().Where(x => !x.Required).ToArray();
|
||||
var optionalArguments = current.Parameters.OfType<ICommandArgument>().Where(x => !x.IsRequired)
|
||||
.ToArray();
|
||||
if (optionalArguments.Length > 0 || !isCurrent)
|
||||
{
|
||||
foreach (var optionalArgument in optionalArguments)
|
||||
{
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.OptionalArgument ?? Style.Plain, $"[{optionalArgument.Value}]"));
|
||||
parameters.Add(NewComposer().Style(
|
||||
helpStyles?.Usage?.OptionalArgument ?? Style.Plain,
|
||||
$"[{optionalArgument.Value}]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isCurrent)
|
||||
{
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Options ?? Style.Plain, $"[{resources.Options}]"));
|
||||
parameters.Add(NewComposer()
|
||||
.Style(helpStyles?.Usage?.Options ?? Style.Plain, $"[{resources.Options}]"));
|
||||
}
|
||||
}
|
||||
|
||||
if (command.IsBranch && command.DefaultCommand == null)
|
||||
{
|
||||
// The user must specify the command
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"<{resources.Command}>"));
|
||||
parameters.Add(NewComposer()
|
||||
.Style(helpStyles?.Usage?.Command ?? Style.Plain, $"<{resources.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(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]"));
|
||||
parameters.Add(NewComposer()
|
||||
.Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]"));
|
||||
}
|
||||
else if (command.IsDefaultCommand)
|
||||
{
|
||||
@ -269,7 +286,8 @@ public class HelpProvider : IHelpProvider
|
||||
{
|
||||
// Commands other than the default are present
|
||||
// So make these optional in the usage statement
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]"));
|
||||
parameters.Add(NewComposer()
|
||||
.Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -338,7 +356,8 @@ public class HelpProvider : IHelpProvider
|
||||
for (var index = 0; index < Math.Min(maxExamples, examples.Count); index++)
|
||||
{
|
||||
var args = string.Join(" ", examples[index]);
|
||||
composer.Tab().Text(model.ApplicationName).Space().Style(helpStyles?.Examples?.Arguments ?? Style.Plain, args);
|
||||
composer.Tab().Text(model.ApplicationName).Space()
|
||||
.Style(helpStyles?.Examples?.Arguments ?? Style.Plain, args);
|
||||
composer.LineBreak();
|
||||
}
|
||||
|
||||
@ -364,7 +383,8 @@ public class HelpProvider : IHelpProvider
|
||||
|
||||
var result = new List<IRenderable>
|
||||
{
|
||||
NewComposer().LineBreak().Style(helpStyles?.Arguments?.Header ?? Style.Plain, $"{resources.Arguments}:").LineBreak(),
|
||||
NewComposer().LineBreak().Style(helpStyles?.Arguments?.Header ?? Style.Plain, $"{resources.Arguments}:")
|
||||
.LineBreak(),
|
||||
};
|
||||
|
||||
var grid = new Grid();
|
||||
@ -407,7 +427,8 @@ public class HelpProvider : IHelpProvider
|
||||
|
||||
var result = new List<IRenderable>
|
||||
{
|
||||
NewComposer().LineBreak().Style(helpStyles?.Options?.Header ?? Style.Plain, $"{resources.Options}:").LineBreak(),
|
||||
NewComposer().LineBreak().Style(helpStyles?.Options?.Header ?? Style.Plain, $"{resources.Options}:")
|
||||
.LineBreak(),
|
||||
};
|
||||
|
||||
var helpOptions = parameters.ToArray();
|
||||
@ -439,7 +460,15 @@ public class HelpProvider : IHelpProvider
|
||||
columns.Add(GetDefaultValueForOption(option.DefaultValue));
|
||||
}
|
||||
|
||||
columns.Add(NewComposer().Text(NormalizeDescription(option.Description)));
|
||||
var description = option.Description;
|
||||
if (option.IsRequired)
|
||||
{
|
||||
description = string.IsNullOrWhiteSpace(description)
|
||||
? "[i]Required[/]"
|
||||
: description.TrimEnd('.') + ". [i]Required[/]";
|
||||
}
|
||||
|
||||
columns.Add(NewComposer().Text(NormalizeDescription(description)));
|
||||
|
||||
grid.AddRow(columns.ToArray());
|
||||
}
|
||||
@ -470,7 +499,8 @@ public class HelpProvider : IHelpProvider
|
||||
|
||||
var result = new List<IRenderable>
|
||||
{
|
||||
NewComposer().LineBreak().Style(helpStyles?.Commands?.Header ?? Style.Plain, $"{resources.Commands}:").LineBreak(),
|
||||
NewComposer().LineBreak().Style(helpStyles?.Commands?.Header ?? Style.Plain, $"{resources.Commands}:")
|
||||
.LineBreak(),
|
||||
};
|
||||
|
||||
var grid = new Grid();
|
||||
@ -546,11 +576,11 @@ public class HelpProvider : IHelpProvider
|
||||
composer.Text(" ");
|
||||
if (option.ValueIsOptional ?? false)
|
||||
{
|
||||
composer.Style(helpStyles?.Options?.OptionalOption ?? Style.Plain, $"[{option.Value}]");
|
||||
composer.Style(helpStyles?.Options?.OptionalOptionValue ?? Style.Plain, $"[{option.Value}]");
|
||||
}
|
||||
else
|
||||
{
|
||||
composer.Style(helpStyles?.Options?.RequiredOption ?? Style.Plain, $"<{option.Value}>");
|
||||
composer.Style(helpStyles?.Options?.RequiredOptionValue ?? Style.Plain, $"<{option.Value}>");
|
||||
}
|
||||
}
|
||||
|
||||
@ -564,8 +594,15 @@ public class HelpProvider : IHelpProvider
|
||||
null => NewComposer().Text(" "),
|
||||
"" => NewComposer().Text(" "),
|
||||
Array { Length: 0 } => NewComposer().Text(" "),
|
||||
Array array => NewComposer().Join(", ", array.Cast<object>().Select(o => NewComposer().Style(helpStyles?.Options?.DefaultValue ?? Style.Plain, o.ToString() ?? string.Empty))),
|
||||
_ => NewComposer().Style(helpStyles?.Options?.DefaultValue ?? Style.Plain, defaultValue?.ToString() ?? string.Empty),
|
||||
Array array => NewComposer().Join(
|
||||
", ",
|
||||
array.Cast<object>().Select(o =>
|
||||
NewComposer().Style(
|
||||
helpStyles?.Options?.DefaultValue ?? Style.Plain,
|
||||
o.ToString() ?? string.Empty))),
|
||||
_ => NewComposer().Style(
|
||||
helpStyles?.Options?.DefaultValue ?? Style.Plain,
|
||||
defaultValue?.ToString() ?? string.Empty),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -76,8 +76,8 @@ public sealed class HelpProviderStyle
|
||||
Header = "yellow",
|
||||
DefaultValueHeader = "lime",
|
||||
DefaultValue = "bold",
|
||||
RequiredOption = "silver",
|
||||
OptionalOption = "grey",
|
||||
RequiredOptionValue = "silver",
|
||||
OptionalOptionValue = "grey",
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -212,8 +212,13 @@ public sealed class OptionStyle
|
||||
/// </summary>
|
||||
public Style? RequiredOption { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for required option values.
|
||||
/// </summary>
|
||||
public Style? RequiredOptionValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for optional options.
|
||||
/// </summary>
|
||||
public Style? OptionalOption { get; set; }
|
||||
public Style? OptionalOptionValue { get; set; }
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ public interface ICommandParameter
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the parameter is required.
|
||||
/// </summary>
|
||||
bool Required { get; }
|
||||
bool IsRequired { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the parameter.
|
||||
|
@ -19,5 +19,5 @@ public interface ICommand
|
||||
/// <param name="context">The command context.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns>The validation result.</returns>
|
||||
Task<int> Execute(CommandContext context, CommandSettings settings);
|
||||
Task<int> ExecuteAsync(CommandContext context, CommandSettings settings);
|
||||
}
|
@ -13,5 +13,5 @@ public interface ICommand<TSettings> : ICommandLimiter<TSettings>
|
||||
/// <param name="context">The command context.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
|
||||
Task<int> Execute(CommandContext context, TSettings settings);
|
||||
Task<int> ExecuteAsync(CommandContext context, TSettings settings);
|
||||
}
|
@ -12,7 +12,7 @@ internal sealed class CommandExecutor
|
||||
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
|
||||
}
|
||||
|
||||
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
|
||||
public async Task<int> ExecuteAsync(IConfiguration configuration, IEnumerable<string> args)
|
||||
{
|
||||
CommandTreeParserResult parsedResult;
|
||||
|
||||
@ -103,7 +103,7 @@ internal sealed class CommandExecutor
|
||||
}
|
||||
|
||||
// Is this the default and is it called without arguments when there are required arguments?
|
||||
if (leaf.Command.IsDefaultCommand && arguments.Count == 0 && leaf.Command.Parameters.Any(p => p.Required))
|
||||
if (leaf.Command.IsDefaultCommand && arguments.Count == 0 && leaf.Command.Parameters.Any(p => p.IsRequired))
|
||||
{
|
||||
// Display help for default command.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||
@ -118,7 +118,7 @@ internal sealed class CommandExecutor
|
||||
leaf.Command.Data);
|
||||
|
||||
// Execute the command tree.
|
||||
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
|
||||
return await ExecuteAsync(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,7 +215,7 @@ internal sealed class CommandExecutor
|
||||
return (parsedResult, tokenizerResult);
|
||||
}
|
||||
|
||||
private static async Task<int> Execute(
|
||||
private static async Task<int> ExecuteAsync(
|
||||
CommandTree leaf,
|
||||
CommandTree tree,
|
||||
CommandContext context,
|
||||
@ -249,7 +249,7 @@ internal sealed class CommandExecutor
|
||||
}
|
||||
|
||||
// Execute the command.
|
||||
var result = await command.Execute(context, settings);
|
||||
var result = await command.ExecuteAsync(context, settings);
|
||||
foreach (var interceptor in interceptors)
|
||||
{
|
||||
interceptor.InterceptResult(context, settings, ref result);
|
||||
|
@ -9,12 +9,14 @@ internal static class CommandValidator
|
||||
{
|
||||
foreach (var parameter in node.Unmapped)
|
||||
{
|
||||
if (parameter.Required)
|
||||
if (parameter.IsRequired)
|
||||
{
|
||||
switch (parameter)
|
||||
{
|
||||
case CommandArgument argument:
|
||||
throw CommandRuntimeException.MissingRequiredArgument(node, argument);
|
||||
case CommandOption option:
|
||||
throw CommandRuntimeException.MissingRequiredOption(node, option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -212,7 +212,7 @@ internal sealed class ExplainCommand : Command<ExplainCommand.Settings>
|
||||
parameterNode.AddNode(ValueMarkup("Value", commandArgumentParameter.Value));
|
||||
}
|
||||
|
||||
parameterNode.AddNode(ValueMarkup("Required", parameter.Required.ToString()));
|
||||
parameterNode.AddNode(ValueMarkup("Required", parameter.IsRequired.ToString()));
|
||||
|
||||
if (parameter.Converter != null)
|
||||
{
|
||||
|
@ -142,7 +142,7 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
|
||||
var node = document.CreateElement("Argument");
|
||||
node.SetNullableAttribute("Name", argument.Value);
|
||||
node.SetAttribute("Position", argument.Position.ToString(CultureInfo.InvariantCulture));
|
||||
node.SetBooleanAttribute("Required", argument.Required);
|
||||
node.SetBooleanAttribute("Required", argument.IsRequired);
|
||||
node.SetEnumAttribute("Kind", argument.ParameterKind);
|
||||
node.SetNullableAttribute("ClrType", argument.ParameterType?.FullName);
|
||||
|
||||
@ -186,7 +186,7 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
|
||||
node.SetNullableAttribute("Short", option.ShortNames);
|
||||
node.SetNullableAttribute("Long", option.LongNames);
|
||||
node.SetNullableAttribute("Value", option.ValueName);
|
||||
node.SetBooleanAttribute("Required", option.Required);
|
||||
node.SetBooleanAttribute("Required", option.IsRequired);
|
||||
node.SetEnumAttribute("Kind", option.ParameterKind);
|
||||
node.SetNullableAttribute("ClrType", option.ParameterType?.FullName);
|
||||
|
||||
|
@ -5,12 +5,12 @@ internal static class TemplateParser
|
||||
public sealed class ArgumentResult
|
||||
{
|
||||
public string Value { get; set; }
|
||||
public bool Required { get; set; }
|
||||
public bool IsRequired { get; set; }
|
||||
|
||||
public ArgumentResult(string value, bool required)
|
||||
public ArgumentResult(string value, bool isRequired)
|
||||
{
|
||||
Value = value;
|
||||
Required = required;
|
||||
IsRequired = isRequired;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ internal sealed class DelegateCommand : ICommand
|
||||
_func = func;
|
||||
}
|
||||
|
||||
public Task<int> Execute(CommandContext context, CommandSettings settings)
|
||||
public Task<int> ExecuteAsync(CommandContext context, CommandSettings settings)
|
||||
{
|
||||
return _func(context, settings);
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal static class LocalizationExtensions
|
||||
{
|
||||
public static string? LocalizedDescription(this MemberInfo me)
|
||||
{
|
||||
var description = me.GetCustomAttribute<DescriptionAttribute>();
|
||||
if (description is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var localization = me.GetCustomAttribute<LocalizationAttribute>();
|
||||
string? localizedText;
|
||||
return (localizedText = localization?.ResourceClass
|
||||
.GetProperty(description.Description)?
|
||||
.GetValue(default) as string) != null
|
||||
? localizedText
|
||||
: description.Description;
|
||||
}
|
||||
}
|
@ -48,10 +48,10 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo
|
||||
|
||||
if (CommandType != null && string.IsNullOrWhiteSpace(Description))
|
||||
{
|
||||
var description = CommandType.GetCustomAttribute<DescriptionAttribute>();
|
||||
var description = CommandType.LocalizedDescription();
|
||||
if (description != null)
|
||||
{
|
||||
Description = description.Description;
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ internal static class CommandModelBuilder
|
||||
|
||||
private static CommandOption BuildOptionParameter(PropertyInfo property, CommandOptionAttribute attribute)
|
||||
{
|
||||
var description = property.GetCustomAttribute<DescriptionAttribute>();
|
||||
var description = property.LocalizedDescription();
|
||||
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
|
||||
var deconstructor = property.GetCustomAttribute<PairDeconstructorAttribute>();
|
||||
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
|
||||
@ -189,14 +189,14 @@ internal static class CommandModelBuilder
|
||||
}
|
||||
|
||||
return new CommandOption(property.PropertyType, kind,
|
||||
property, description?.Description, converter, deconstructor,
|
||||
property, description, converter, deconstructor,
|
||||
attribute, valueProvider, validators, defaultValue,
|
||||
attribute.ValueIsOptional);
|
||||
}
|
||||
|
||||
private static CommandArgument BuildArgumentParameter(PropertyInfo property, CommandArgumentAttribute attribute)
|
||||
{
|
||||
var description = property.GetCustomAttribute<DescriptionAttribute>();
|
||||
var description = property.LocalizedDescription();
|
||||
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
|
||||
var defaultValue = property.GetCustomAttribute<DefaultValueAttribute>();
|
||||
var valueProvider = property.GetCustomAttribute<ParameterValueProviderAttribute>();
|
||||
@ -206,7 +206,7 @@ internal static class CommandModelBuilder
|
||||
|
||||
return new CommandArgument(
|
||||
property.PropertyType, kind, property,
|
||||
description?.Description, converter,
|
||||
description, converter,
|
||||
defaultValue, attribute, valueProvider,
|
||||
validators);
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ internal static class CommandModelValidator
|
||||
// Arguments
|
||||
foreach (var argument in arguments)
|
||||
{
|
||||
if (argument.Required && argument.DefaultValue != null)
|
||||
if (argument.IsRequired && argument.DefaultValue != null)
|
||||
{
|
||||
throw CommandConfigurationException.RequiredArgumentsCannotHaveDefaultValue(argument);
|
||||
}
|
||||
|
@ -14,8 +14,9 @@ internal sealed class CommandOption : CommandParameter, ICommandOption
|
||||
CommandOptionAttribute optionAttribute, ParameterValueProviderAttribute? valueProvider,
|
||||
IEnumerable<ParameterValidationAttribute> validators,
|
||||
DefaultValueAttribute? defaultValue, bool valueIsOptional)
|
||||
: base(parameterType, parameterKind, property, description, converter,
|
||||
defaultValue, deconstructor, valueProvider, validators, false, optionAttribute.IsHidden)
|
||||
: base(parameterType, parameterKind, property, description, converter,
|
||||
defaultValue, deconstructor, valueProvider, validators,
|
||||
optionAttribute.IsRequired, optionAttribute.IsHidden)
|
||||
{
|
||||
LongNames = optionAttribute.LongNames;
|
||||
ShortNames = optionAttribute.ShortNames;
|
||||
|
@ -12,7 +12,7 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
|
||||
public PairDeconstructorAttribute? PairDeconstructor { get; }
|
||||
public List<ParameterValidationAttribute> Validators { get; }
|
||||
public ParameterValueProviderAttribute? ValueProvider { get; }
|
||||
public bool Required { get; set; }
|
||||
public bool IsRequired { get; set; }
|
||||
public bool IsHidden { get; }
|
||||
public string PropertyName => Property.Name;
|
||||
|
||||
@ -39,7 +39,7 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
|
||||
PairDeconstructor = deconstructor;
|
||||
ValueProvider = valueProvider;
|
||||
Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>());
|
||||
Required = required;
|
||||
IsRequired = required;
|
||||
IsHidden = isHidden;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageId>NetAdmin.Spectre.Console.Cli</PackageId>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<IsAotCompatible Condition="'$(TargetFramework)' != 'netstandard2.0'" >false</IsAotCompatible>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
|
||||
<IsTestProject>false</IsTestProject>
|
||||
<IsPackable>true</IsPackable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Description>Contains testing utilities for Spectre.Console.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -2,7 +2,26 @@ namespace Spectre.Console;
|
||||
|
||||
internal static class Cell
|
||||
{
|
||||
private static readonly int?[] _runeWidthCache = new int?[char.MaxValue];
|
||||
private const sbyte Sentinel = -2;
|
||||
|
||||
/// <summary>
|
||||
/// UnicodeCalculator.GetWidth documents the width as (-1, 0, 1, 2). We only need space for these values and a sentinel for uninitialized values.
|
||||
/// This is only five values in total so we are storing one byte per value. We could store 2 per byte but that would add more logic to the retrieval.
|
||||
/// We should add one to char.MaxValue because the total number of characters includes \0 too so there are 65536 valid chars.
|
||||
/// </summary>
|
||||
private static readonly sbyte[] _runeWidthCache = new sbyte[char.MaxValue + 1];
|
||||
|
||||
static Cell()
|
||||
{
|
||||
#if !NETSTANDARD2_0
|
||||
Array.Fill(_runeWidthCache, Sentinel);
|
||||
#else
|
||||
for (var i = 0; i < _runeWidthCache.Length; i++)
|
||||
{
|
||||
_runeWidthCache[i] = Sentinel;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public static int GetCellLength(string text)
|
||||
{
|
||||
@ -29,6 +48,13 @@ internal static class Cell
|
||||
return 1;
|
||||
}
|
||||
|
||||
return _runeWidthCache[rune] ??= UnicodeCalculator.GetWidth(rune);
|
||||
var width = _runeWidthCache[rune];
|
||||
if (width == Sentinel)
|
||||
{
|
||||
_runeWidthCache[rune] = (sbyte)UnicodeCalculator.GetWidth(rune);
|
||||
return _runeWidthCache[rune];
|
||||
}
|
||||
|
||||
return _runeWidthCache[rune];
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ internal sealed class LiveDisplayRenderer : IRenderHook
|
||||
{
|
||||
private readonly IAnsiConsole _console;
|
||||
private readonly LiveDisplayContext _context;
|
||||
|
||||
public LiveDisplayRenderer(IAnsiConsole console, LiveDisplayContext context)
|
||||
{
|
||||
_console = console;
|
||||
@ -45,7 +44,7 @@ internal sealed class LiveDisplayRenderer : IRenderHook
|
||||
{
|
||||
lock (_context.Lock)
|
||||
{
|
||||
yield return _context.Live.PositionCursor();
|
||||
yield return _context.Live.PositionCursor(options);
|
||||
|
||||
foreach (var renderable in renderables)
|
||||
{
|
||||
|
@ -39,7 +39,7 @@ internal sealed class LiveRenderable : Renderable
|
||||
}
|
||||
}
|
||||
|
||||
public IRenderable PositionCursor()
|
||||
public IRenderable PositionCursor(RenderOptions options)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
@ -48,6 +48,14 @@ internal sealed class LiveRenderable : Renderable
|
||||
return new ControlCode(string.Empty);
|
||||
}
|
||||
|
||||
// Check if the size have been reduced
|
||||
if (_shape.Value.Height > options.ConsoleSize.Height || _shape.Value.Width > options.ConsoleSize.Width)
|
||||
{
|
||||
// Important reset shape, so the size can shrink
|
||||
_shape = null;
|
||||
return new ControlCode(ED(2) + ED(3) + CUP(1, 1));
|
||||
}
|
||||
|
||||
var linesToMoveUp = _shape.Value.Height - 1;
|
||||
return new ControlCode("\r" + CUU(linesToMoveUp));
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ internal sealed class DefaultProgressRenderer : ProgressRenderer
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
yield return _live.PositionCursor();
|
||||
yield return _live.PositionCursor(options);
|
||||
|
||||
foreach (var renderable in renderables)
|
||||
{
|
||||
|
@ -42,7 +42,7 @@ internal sealed class ListPromptRenderHook<T> : IRenderHook
|
||||
_dirty = false;
|
||||
}
|
||||
|
||||
yield return _live.PositionCursor();
|
||||
yield return _live.PositionCursor(options);
|
||||
|
||||
foreach (var renderable in renderables)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageId>NetAdmin.Spectre.Console</PackageId>
|
||||
<DefineConstants>$(DefineConstants)TRACE;WCWIDTH_VISIBILITY_INTERNAL</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
@ -19,7 +20,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Label="Dependencies">
|
||||
<PackageReference Include="System.Memory" />
|
||||
<PackageReference Include="Wcwidth.Sources">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
@ -34,6 +34,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<PackageReference Include="System.Memory" />
|
||||
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" PrivateAssets="all"/>
|
||||
<PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]"/>
|
||||
</ItemGroup>
|
||||
|
@ -226,6 +226,11 @@ public sealed class Style : IEquatable<Style>
|
||||
builder.Add("on " + Background.ToMarkup());
|
||||
}
|
||||
|
||||
if (Link != null)
|
||||
{
|
||||
builder.Add($"link={Link}");
|
||||
}
|
||||
|
||||
return string.Join(" ", builder);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,14 @@
|
||||
namespace Spectre.Console.Tests.Data;
|
||||
|
||||
public class RequiredOptionsSettings : CommandSettings
|
||||
{
|
||||
[CommandOption("--foo <VALUE>", true)]
|
||||
[Description("Foos the bars")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
public class RequiredOptionsWithoutDescriptionSettings : CommandSettings
|
||||
{
|
||||
[CommandOption("--foo <VALUE>", true)]
|
||||
public string Foo { get; set; }
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
USAGE:
|
||||
myapp [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
--foo <VALUE> Foos the bars. Required
|
@ -0,0 +1,6 @@
|
||||
USAGE:
|
||||
myapp [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Prints help information
|
||||
--foo <VALUE> Required
|
@ -1061,5 +1061,43 @@ public sealed partial class CommandAppTests
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Required_Options")]
|
||||
public Task Should_Show_Required_Options()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.SetDefaultCommand<GenericCommand<RequiredOptionsSettings>>();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("--help");
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("Required_Options_No_Description")]
|
||||
public Task Should_Show_Required_Options_Without_Description()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.SetDefaultCommand<GenericCommand<RequiredOptionsWithoutDescriptionSettings>>();
|
||||
fixture.Configure(configurator =>
|
||||
{
|
||||
configurator.SetApplicationName("myapp");
|
||||
});
|
||||
|
||||
// When
|
||||
var result = fixture.Run("--help");
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
namespace Spectre.Console.Tests.Unit.Cli;
|
||||
|
||||
public sealed partial class CommandAppTests
|
||||
{
|
||||
public sealed class Options
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Throw_If_Required_Option_Is_Missing()
|
||||
{
|
||||
// Given
|
||||
var fixture = new CommandAppTester();
|
||||
fixture.Configure(config =>
|
||||
{
|
||||
config.AddCommand<GenericCommand<RequiredOptionsSettings>>("test");
|
||||
config.PropagateExceptions();
|
||||
});
|
||||
|
||||
// When
|
||||
var result = Record.Exception(() => fixture.Run("test"));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<CommandRuntimeException>()
|
||||
.And(ex =>
|
||||
ex.Message.ShouldBe("Command 'test' is missing required argument 'foo'."));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
namespace Spectre.Console.Tests.Unit.Cli;
|
||||
|
||||
public sealed partial class CommandApptests
|
||||
public sealed partial class CommandAppTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Treat_Commands_As_Case_Sensitive_If_Specified()
|
||||
|
@ -1,4 +1,4 @@
|
||||
namespace Spectre.Console.Cli.Tests.Unit.Testing;
|
||||
namespace Spectre.Console.Tests.Unit.Cli;
|
||||
|
||||
public sealed class InteractiveCommandTests
|
||||
{
|
||||
|
@ -405,5 +405,31 @@ public sealed class StyleTests
|
||||
// Then
|
||||
result.ShouldBe("default on green");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_Expected_Markup_For_Style_With_Only_Link()
|
||||
{
|
||||
// Given
|
||||
var style = new Style(link:"https://spectreconsole.net/");
|
||||
|
||||
// When
|
||||
var result = style.ToMarkup();
|
||||
|
||||
// Then
|
||||
result.ShouldBe("link=https://spectreconsole.net/");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Return_Expected_Markup_For_Style_With_Background_And_Link()
|
||||
{
|
||||
// Given
|
||||
var style = new Style(background: Color.Blue, link: "https://spectreconsole.net/");
|
||||
|
||||
// When
|
||||
var result = style.ToMarkup();
|
||||
|
||||
// Then
|
||||
result.ShouldBe("default on blue link=https://spectreconsole.net/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user