Merge remote-tracking branch 'upstream/main' into blz/issues/1390

# Conflicts:
#	src/Tests/Spectre.Console.Tests/Expectations/Widgets/Layout/Render_Layout_With_Three_And_One_Columns.Output.verified.txt
This commit is contained in:
BlazeFace
2024-10-25 17:47:02 -07:00
722 changed files with 1450 additions and 6551 deletions

View File

@ -98,4 +98,7 @@ dotnet_diagnostic.IDE0044.severity = warning
dotnet_diagnostic.RCS1047.severity = none
# RCS1090: Call 'ConfigureAwait(false)'.
dotnet_diagnostic.RCS1090.severity = warning
dotnet_diagnostic.RCS1090.severity = warning
# The file header is missing or not located at the top of the file
dotnet_diagnostic.SA1633.severity = none

View File

@ -1,4 +1,4 @@
<Project>
<Project>
<PropertyGroup Label="Settings">
<Deterministic>true</Deterministic>
<LangVersion>12</LangVersion>
@ -7,19 +7,38 @@
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);SA1633</NoWarn>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\..\resources\spectre.snk</AssemblyOriginatorKeyFile>
<PublicKey>00240000048000009400000006020000002400005253413100040000010001006146d3789d31477cf4a3b508dcf772ff9ccad8613f6bd6b17b9c4a960a7a7b551ecd22e4f4119ced70ee8bbdf3ca0a117c99fd6248c16255ea9033110c2233d42e74e81bf4f3f7eb09bfe8b53ad399d957514f427171a86f5fe9fe0014be121d571c80c4a0cfc3531bdbf5a2900d936d93f2c94171b9134f7644a1ac3612a0d0</PublicKey>
</PropertyGroup>
<PropertyGroup Label="Deterministic Build" Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup Condition="$(IsPackable)">
<None Include="$(MSBuildThisFileDirectory)../resources/nuget/logo.png">
<Pack>true</Pack>
<PackagePath>\</PackagePath>
<Link>Properties/Package/Logo.png</Link>
</None>
<None Include="$(MSBuildThisFileDirectory)../resources/nuget/$(AssemblyName).md">
<Pack>true</Pack>
<PackagePath>\README.md</PackagePath>
<Link>Properties/Package/README.md</Link>
</None>
</ItemGroup>
<PropertyGroup Label="Package Information">
<Description>A library that makes it easier to create beautiful console applications.</Description>
<Copyright>Patrik Svensson, Phil Scott, Nils Andresen, Cédric Luthi, Frank Ray</Copyright>
<Authors>Patrik Svensson, Phil Scott, Nils Andresen, Cédric Luthi, Frank Ray</Authors>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/spectreconsole/spectre.console</RepositoryUrl>
<PackageIcon>small-logo.png</PackageIcon>
<PackageIcon>logo.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<PackageProjectUrl>https://github.com/spectreconsole/spectre.console</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
@ -31,14 +50,18 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)/stylecop.json" Link="Properties/stylecop.json"/>
</ItemGroup>
<!-- 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" Version="4.3.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PackageReference Include="MinVer" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Roslynator.Analyzers" Version="4.11.0">
<PackageReference Include="Roslynator.Analyzers">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>

View File

@ -0,0 +1,30 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup Label="Dependencies">
<PackageVersion Include="MinVer" PrivateAssets="All" Version="6.0.0"/>
<PackageVersion Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0"/>
<PackageVersion Include="Wcwidth.Sources" Version="2.0.0"/>
<PackageVersion Include="IsExternalInit" Version="1.0.3"/>
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0"/>
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1"/>
<PackageVersion Include="Shouldly" Version="4.2.1"/>
<PackageVersion Include="Spectre.Verify.Extensions" Version="22.3.2-preview.0.1"/>
<PackageVersion Include="Verify.Xunit" Version="26.4.5"/>
<PackageVersion Include="xunit" Version="2.9.0"/>
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2"/>
<PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageVersion Include="Nullable" Version="1.3.1"/>
<PackageVersion Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" />
</ItemGroup>
<ItemGroup Label="Static Analysis">
<PackageVersion Include="StyleCop.Analyzers" PrivateAssets="All" Version="1.2.0-beta.556"/>
<PackageVersion Include="Roslynator.Analyzers" PrivateAssets="All" Version="4.12.5"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
<IsPackable>true</IsPackable>
<Description>A library that extends Spectre.Console with ImageSharp superpowers.</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net7.0;net6.0;netstandard2.0</TargetFrameworks>
<ImplicitUsings>true</ImplicitUsings>
<IsPackable>true</IsPackable>
<Description>A library that extends Spectre.Console with JSON superpowers.</Description>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\Spectre.Console\Internal\Extensions\CharExtensions.cs" Link="Internal\CharExtensions.cs" />
<Compile Include="..\..\Spectre.Console\Internal\Extensions\EnumerableExtensions.cs" Link="Internal\EnumerableExtensions.cs" />
<Compile Include="..\..\Spectre.Console\Internal\Text\StringBuffer.cs" Link="Internal\StringBuffer.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
</Project>

View File

@ -1,15 +0,0 @@
namespace Spectre.Console.Analyzer.Sandbox;
/// <summary>
/// Sample sandbox for testing out analyzers.
/// </summary>
public static class Program
{
/// <summary>
/// The program's entry point.
/// </summary>
public static void Main()
{
AnsiConsole.WriteLine("Project is set up with a reference to Spectre.Console.Analyzer");
}
}

View File

@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Spectre.Console.Analyzer\Spectre.Console.Analyzer.csproj" PrivateAssets="all" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
<ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
</Project>

View File

@ -1,79 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Analyzer", "Spectre.Console.Analyzer\Spectre.Console.Analyzer.csproj", "{18178142-A80D-424F-882D-DB0F787210BD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Analyzer.Sandbox", "Spectre.Console.Analyzer.Sandbox\Spectre.Console.Analyzer.Sandbox.csproj", "{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Analyzer.Tests", "..\test\Spectre.Console.Analyzer.Tests\Spectre.Console.Analyzer.Tests.csproj", "{609D5D1B-D904-4A31-B237-A04B49910166}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console", "Spectre.Console\Spectre.Console.csproj", "{6BFF310F-9601-4E5D-BC80-118AC708D72A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{18178142-A80D-424F-882D-DB0F787210BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{18178142-A80D-424F-882D-DB0F787210BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18178142-A80D-424F-882D-DB0F787210BD}.Debug|x64.ActiveCfg = Debug|Any CPU
{18178142-A80D-424F-882D-DB0F787210BD}.Debug|x64.Build.0 = Debug|Any CPU
{18178142-A80D-424F-882D-DB0F787210BD}.Debug|x86.ActiveCfg = Debug|Any CPU
{18178142-A80D-424F-882D-DB0F787210BD}.Debug|x86.Build.0 = Debug|Any CPU
{18178142-A80D-424F-882D-DB0F787210BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18178142-A80D-424F-882D-DB0F787210BD}.Release|Any CPU.Build.0 = Release|Any CPU
{18178142-A80D-424F-882D-DB0F787210BD}.Release|x64.ActiveCfg = Release|Any CPU
{18178142-A80D-424F-882D-DB0F787210BD}.Release|x64.Build.0 = Release|Any CPU
{18178142-A80D-424F-882D-DB0F787210BD}.Release|x86.ActiveCfg = Release|Any CPU
{18178142-A80D-424F-882D-DB0F787210BD}.Release|x86.Build.0 = Release|Any CPU
{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}.Debug|x64.ActiveCfg = Debug|Any CPU
{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}.Debug|x64.Build.0 = Debug|Any CPU
{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}.Debug|x86.ActiveCfg = Debug|Any CPU
{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}.Debug|x86.Build.0 = Debug|Any CPU
{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}.Release|Any CPU.Build.0 = Release|Any CPU
{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}.Release|x64.ActiveCfg = Release|Any CPU
{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}.Release|x64.Build.0 = Release|Any CPU
{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}.Release|x86.ActiveCfg = Release|Any CPU
{44D2E280-8FCD-4FC1-9133-F61E344FD6A6}.Release|x86.Build.0 = Release|Any CPU
{609D5D1B-D904-4A31-B237-A04B49910166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{609D5D1B-D904-4A31-B237-A04B49910166}.Debug|Any CPU.Build.0 = Debug|Any CPU
{609D5D1B-D904-4A31-B237-A04B49910166}.Debug|x64.ActiveCfg = Debug|Any CPU
{609D5D1B-D904-4A31-B237-A04B49910166}.Debug|x64.Build.0 = Debug|Any CPU
{609D5D1B-D904-4A31-B237-A04B49910166}.Debug|x86.ActiveCfg = Debug|Any CPU
{609D5D1B-D904-4A31-B237-A04B49910166}.Debug|x86.Build.0 = Debug|Any CPU
{609D5D1B-D904-4A31-B237-A04B49910166}.Release|Any CPU.ActiveCfg = Release|Any CPU
{609D5D1B-D904-4A31-B237-A04B49910166}.Release|Any CPU.Build.0 = Release|Any CPU
{609D5D1B-D904-4A31-B237-A04B49910166}.Release|x64.ActiveCfg = Release|Any CPU
{609D5D1B-D904-4A31-B237-A04B49910166}.Release|x64.Build.0 = Release|Any CPU
{609D5D1B-D904-4A31-B237-A04B49910166}.Release|x86.ActiveCfg = Release|Any CPU
{609D5D1B-D904-4A31-B237-A04B49910166}.Release|x86.Build.0 = Release|Any CPU
{6BFF310F-9601-4E5D-BC80-118AC708D72A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BFF310F-9601-4E5D-BC80-118AC708D72A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6BFF310F-9601-4E5D-BC80-118AC708D72A}.Debug|x64.ActiveCfg = Debug|Any CPU
{6BFF310F-9601-4E5D-BC80-118AC708D72A}.Debug|x64.Build.0 = Debug|Any CPU
{6BFF310F-9601-4E5D-BC80-118AC708D72A}.Debug|x86.ActiveCfg = Debug|Any CPU
{6BFF310F-9601-4E5D-BC80-118AC708D72A}.Debug|x86.Build.0 = Debug|Any CPU
{6BFF310F-9601-4E5D-BC80-118AC708D72A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BFF310F-9601-4E5D-BC80-118AC708D72A}.Release|Any CPU.Build.0 = Release|Any CPU
{6BFF310F-9601-4E5D-BC80-118AC708D72A}.Release|x64.ActiveCfg = Release|Any CPU
{6BFF310F-9601-4E5D-BC80-118AC708D72A}.Release|x64.Build.0 = Release|Any CPU
{6BFF310F-9601-4E5D-BC80-118AC708D72A}.Release|x86.ActiveCfg = Release|Any CPU
{6BFF310F-9601-4E5D-BC80-118AC708D72A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3BABBA82-B60D-45CE-9C56-8B833474DD3C}
EndGlobalSection
EndGlobal

View File

@ -1,6 +0,0 @@
<SolutionConfiguration>
<Settings>
<AllowParallelTestExecution>True</AllowParallelTestExecution>
<SolutionConfigured>True</SolutionConfigured>
</Settings>
</SolutionConfiguration>

View File

@ -1,90 +0,0 @@
namespace Spectre.Console.Analyzer;
/// <summary>
/// Analyzer to suggest using available instances of AnsiConsole over the static methods.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class FavorInstanceAnsiConsoleOverStaticAnalyzer : SpectreAnalyzer
{
private static readonly DiagnosticDescriptor _diagnosticDescriptor =
Descriptors.S1010_FavorInstanceAnsiConsoleOverStatic;
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(_diagnosticDescriptor);
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
var ansiConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
if (ansiConsoleType == null)
{
return;
}
compilationStartContext.RegisterOperationAction(
context =>
{
// if this operation isn't an invocation against one of the System.Console methods
// defined in _methods then we can safely stop analyzing and return;
var invocationOperation = (IInvocationOperation)context.Operation;
if (!SymbolEqualityComparer.Default.Equals(invocationOperation.TargetMethod.ContainingType, ansiConsoleType))
{
return;
}
// if we aren't in a method then it might be too complex for us to handle.
if (!invocationOperation.Syntax.Ancestors().OfType<MethodDeclarationSyntax>().Any())
{
return;
}
if (!HasFieldAnsiConsole(invocationOperation.Syntax) &&
!HasParameterAnsiConsole(invocationOperation.Syntax))
{
return;
}
var methodSymbol = invocationOperation.TargetMethod;
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
context.ReportDiagnostic(
Diagnostic.Create(
_diagnosticDescriptor,
invocationOperation.Syntax.GetLocation(),
displayString));
}, OperationKind.Invocation);
}
private static bool HasParameterAnsiConsole(SyntaxNode syntaxNode)
{
return syntaxNode
.Ancestors().OfType<MethodDeclarationSyntax>()
.First()
.ParameterList.Parameters
.Any(i => i.Type?.NormalizeWhitespace()?.ToString() == "IAnsiConsole");
}
private static bool HasFieldAnsiConsole(SyntaxNode syntaxNode)
{
var isStatic = syntaxNode
.Ancestors()
.OfType<MethodDeclarationSyntax>()
.First()
.Modifiers.Any(i => i.IsKind(SyntaxKind.StaticKeyword));
return syntaxNode
.Ancestors().OfType<ClassDeclarationSyntax>()
.First()
.Members
.OfType<FieldDeclarationSyntax>()
.Any(i =>
i.Declaration.Type.NormalizeWhitespace().ToString() == "IAnsiConsole" &&
(!isStatic ^ i.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.StaticKeyword))));
}
}

View File

@ -1,74 +0,0 @@
namespace Spectre.Console.Analyzer;
/// <summary>
/// Analyzer to detect calls to live renderables within a live renderable context.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
[Shared]
public class NoConcurrentLiveRenderablesAnalyzer : SpectreAnalyzer
{
private static readonly DiagnosticDescriptor _diagnosticDescriptor =
Descriptors.S1020_AvoidConcurrentCallsToMultipleLiveRenderables;
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(_diagnosticDescriptor);
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
var liveTypes = Constants.LiveRenderables
.Select(i => compilationStartContext.Compilation.GetTypeByMetadataName(i))
.Where(i => i != null)
.ToImmutableArray();
if (liveTypes.Length == 0)
{
return;
}
compilationStartContext.RegisterOperationAction(
context =>
{
var invocationOperation = (IInvocationOperation)context.Operation;
var methodSymbol = invocationOperation.TargetMethod;
const string StartMethod = "Start";
if (methodSymbol.Name != StartMethod)
{
return;
}
if (liveTypes.All(i => !SymbolEqualityComparer.Default.Equals(i, methodSymbol.ContainingType)))
{
return;
}
var model = context.Operation.SemanticModel!;
var parentInvocations = invocationOperation
.Syntax.Ancestors()
.OfType<InvocationExpressionSyntax>()
.Select(i => model.GetOperation(i, context.CancellationToken))
.OfType<IInvocationOperation>()
.ToList();
if (parentInvocations.All(parent =>
parent.TargetMethod.Name != StartMethod || !liveTypes.Contains(parent.TargetMethod.ContainingType, SymbolEqualityComparer.Default)))
{
return;
}
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
context.ReportDiagnostic(
Diagnostic.Create(
_diagnosticDescriptor,
invocationOperation.Syntax.GetLocation(),
displayString));
}, OperationKind.Invocation);
}
}

View File

@ -1,80 +0,0 @@
namespace Spectre.Console.Analyzer;
/// <summary>
/// Analyzer to detect calls to live renderables within a live renderable context.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
[Shared]
public class NoPromptsDuringLiveRenderablesAnalyzer : SpectreAnalyzer
{
private static readonly DiagnosticDescriptor _diagnosticDescriptor =
Descriptors.S1021_AvoidPromptCallsDuringLiveRenderables;
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(_diagnosticDescriptor);
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
var ansiConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
var ansiConsoleExtensionsType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsoleExtensions");
if (ansiConsoleType is null && ansiConsoleExtensionsType is null)
{
return;
}
compilationStartContext.RegisterOperationAction(
context =>
{
// if this operation isn't an invocation against one of the System.Console methods
// defined in _methods then we can safely stop analyzing and return;
var invocationOperation = (IInvocationOperation)context.Operation;
var methodSymbol = invocationOperation.TargetMethod;
var promptMethods = ImmutableArray.Create("Ask", "Confirm", "Prompt");
if (!promptMethods.Contains(methodSymbol.Name))
{
return;
}
if (!SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, ansiConsoleType) &&
!SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, ansiConsoleExtensionsType))
{
return;
}
var model = context.Operation.SemanticModel!;
var parentInvocations = invocationOperation
.Syntax.Ancestors()
.OfType<InvocationExpressionSyntax>()
.Select(i => model.GetOperation(i, context.CancellationToken))
.OfType<IInvocationOperation>()
.ToList();
var liveTypes = Constants.LiveRenderables
.Select(i => context.Compilation.GetTypeByMetadataName(i))
.ToImmutableArray();
if (parentInvocations.All(parent =>
parent.TargetMethod.Name != "Start" ||
!liveTypes.Contains(parent.TargetMethod.ContainingType, SymbolEqualityComparer.Default)))
{
return;
}
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
context.ReportDiagnostic(
Diagnostic.Create(
_diagnosticDescriptor,
invocationOperation.Syntax.GetLocation(),
displayString));
}, OperationKind.Invocation);
}
}

View File

@ -1,22 +0,0 @@
namespace Spectre.Console.Analyzer;
/// <summary>
/// Base class for Spectre analyzers.
/// </summary>
public abstract class SpectreAnalyzer : DiagnosticAnalyzer
{
/// <inheritdoc />
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(AnalyzeCompilation);
}
/// <summary>
/// Analyze compilation.
/// </summary>
/// <param name="compilationStartContext">Compilation Start Analysis Context.</param>
protected abstract void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext);
}

View File

@ -1,61 +0,0 @@
namespace Spectre.Console.Analyzer;
/// <summary>
/// Analyzer to enforce the use of AnsiConsole over System.Console for known methods.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class UseSpectreInsteadOfSystemConsoleAnalyzer : SpectreAnalyzer
{
private static readonly DiagnosticDescriptor _diagnosticDescriptor =
Descriptors.S1000_UseAnsiConsoleOverSystemConsole;
private static readonly string[] _methods = { "WriteLine", "Write" };
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(_diagnosticDescriptor);
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
var systemConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("System.Console");
var spectreConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
if (systemConsoleType == null || spectreConsoleType == null)
{
return;
}
compilationStartContext.RegisterOperationAction(
context =>
{
// if this operation isn't an invocation against one of the System.Console methods
// defined in _methods then we can safely stop analyzing and return;
var invocationOperation = (IInvocationOperation)context.Operation;
var methodName = System.Array.Find(_methods, i => i.Equals(invocationOperation.TargetMethod.Name));
if (methodName == null)
{
return;
}
if (!SymbolEqualityComparer.Default.Equals(invocationOperation.TargetMethod.ContainingType, systemConsoleType))
{
return;
}
var methodSymbol = invocationOperation.TargetMethod;
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
context.ReportDiagnostic(
Diagnostic.Create(
_diagnosticDescriptor,
invocationOperation.Syntax.GetLocation(),
displayString));
}, OperationKind.Invocation);
}
}

View File

@ -1,14 +0,0 @@
namespace Spectre.Console.Analyzer;
internal static class Constants
{
internal const string StaticInstance = "AnsiConsole";
internal const string SpectreConsole = "Spectre.Console";
internal static readonly string[] LiveRenderables =
{
"Spectre.Console.LiveDisplay",
"Spectre.Console.Progress",
"Spectre.Console.Status",
};
}

View File

@ -1,76 +0,0 @@
using static Microsoft.CodeAnalysis.DiagnosticSeverity;
using static Spectre.Console.Analyzer.Descriptors.Category;
namespace Spectre.Console.Analyzer;
/// <summary>
/// Code analysis descriptors.
/// </summary>
public static class Descriptors
{
internal enum Category
{
Usage, // 1xxx
}
private static readonly ConcurrentDictionary<Category, string> _categoryMapping = new();
private static DiagnosticDescriptor Rule(string id, string title, Category category, DiagnosticSeverity defaultSeverity, string messageFormat, string? description = null)
{
var helpLink = $"https://spectreconsole.net/analyzer/rules/{id.ToLowerInvariant()}";
const bool IsEnabledByDefault = true;
return new DiagnosticDescriptor(
id,
title,
messageFormat,
_categoryMapping.GetOrAdd(category, c => c.ToString()),
defaultSeverity,
IsEnabledByDefault,
description,
helpLink);
}
/// <summary>
/// Gets definitions of diagnostics Spectre1000.
/// </summary>
public static DiagnosticDescriptor S1000_UseAnsiConsoleOverSystemConsole { get; } =
Rule(
"Spectre1000",
"Use AnsiConsole instead of System.Console",
Usage,
Warning,
"Use AnsiConsole instead of System.Console");
/// <summary>
/// Gets definitions of diagnostics Spectre1010.
/// </summary>
public static DiagnosticDescriptor S1010_FavorInstanceAnsiConsoleOverStatic { get; } =
Rule(
"Spectre1010",
"Favor the use of the instance of AnsiConsole over the static helper.",
Usage,
Info,
"Favor the use of the instance of AnsiConsole over the static helper.");
/// <summary>
/// Gets definitions of diagnostics Spectre1020.
/// </summary>
public static DiagnosticDescriptor S1020_AvoidConcurrentCallsToMultipleLiveRenderables { get; } =
Rule(
"Spectre1020",
"Avoid calling other live renderables while a current renderable is running.",
Usage,
Warning,
"Avoid calling other live renderables while a current renderable is running.");
/// <summary>
/// Gets definitions of diagnostics Spectre1020.
/// </summary>
public static DiagnosticDescriptor S1021_AvoidPromptCallsDuringLiveRenderables { get; } =
Rule(
"Spectre1021",
"Avoid prompting for input while a current renderable is running.",
Usage,
Warning,
"Avoid prompting for input while a current renderable is running.");
}

View File

@ -1,202 +0,0 @@
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Simplification;
namespace Spectre.Console.Analyzer.CodeActions;
/// <summary>
/// Code action to change calls to System.Console to AnsiConsole.
/// </summary>
public class SwitchToAnsiConsoleAction : CodeAction
{
private readonly Document _document;
private readonly InvocationExpressionSyntax _originalInvocation;
/// <summary>
/// Initializes a new instance of the <see cref="SwitchToAnsiConsoleAction"/> class.
/// </summary>
/// <param name="document">Document to change.</param>
/// <param name="originalInvocation">The method to change.</param>
/// <param name="title">Title of the fix.</param>
public SwitchToAnsiConsoleAction(Document document, InvocationExpressionSyntax originalInvocation, string title)
{
_document = document;
_originalInvocation = originalInvocation;
Title = title;
}
/// <inheritdoc />
public override string Title { get; }
/// <inheritdoc />
public override string EquivalenceKey => Title;
/// <inheritdoc />
protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);
var compilation = editor.SemanticModel.Compilation;
var operation = editor.SemanticModel.GetOperation(_originalInvocation, cancellationToken) as IInvocationOperation;
if (operation == null)
{
return _document;
}
// If there is an IAnsiConsole passed into the method then we'll use it.
// otherwise we'll check for a field level instance.
// if neither of those exist we'll fall back to the static param.
var spectreConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
var iansiConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.IAnsiConsole");
ISymbol? accessibleConsoleSymbol = spectreConsoleSymbol;
if (iansiConsoleSymbol != null)
{
var isInStaticContext = IsInStaticContext(operation, cancellationToken, out var parentStaticMemberStartPosition);
foreach (var symbol in editor.SemanticModel.LookupSymbols(operation.Syntax.GetLocation().SourceSpan.Start))
{
// LookupSymbols check the accessibility of the symbol, but it can
// suggest instance members when the current context is static.
var symbolType = symbol switch
{
IParameterSymbol parameter => parameter.Type,
IFieldSymbol field when !isInStaticContext || field.IsStatic => field.Type,
IPropertySymbol { GetMethod: not null } property when !isInStaticContext || property.IsStatic => property.Type,
ILocalSymbol local => local.Type,
_ => null,
};
// Locals can be returned even if there are not valid in the current context. For instance,
// it can return locals declared after the current location. Or it can return locals that
// should not be accessible in a static local function.
//
// void Sample()
// {
// int local = 0;
// static void LocalFunction() => local; <-- local is invalid here but LookupSymbols suggests it
// }
//
// Parameters from the ancestor methods or local functions are also returned even if the operation is in a static local function.
if (symbol.Kind is SymbolKind.Local or SymbolKind.Parameter)
{
var localPosition = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken).GetLocation().SourceSpan.Start;
// The local is not part of the source tree
if (localPosition == null)
{
break;
}
// The local is declared after the current expression
if (localPosition > _originalInvocation.Span.Start)
{
break;
}
// The local is declared outside the static local function
if (isInStaticContext && localPosition < parentStaticMemberStartPosition)
{
break;
}
}
if (IsOrImplementSymbol(symbolType, iansiConsoleSymbol))
{
accessibleConsoleSymbol = symbol;
break;
}
}
}
if (accessibleConsoleSymbol == null)
{
return _document;
}
// Replace the original invocation
var generator = editor.Generator;
var consoleExpression = accessibleConsoleSymbol switch
{
ITypeSymbol typeSymbol => generator.TypeExpression(typeSymbol, addImport: true).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation),
_ => generator.IdentifierName(accessibleConsoleSymbol.Name),
};
var newExpression = generator.InvocationExpression(generator.MemberAccessExpression(consoleExpression, operation.TargetMethod.Name), _originalInvocation.ArgumentList.Arguments)
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia())
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia());
editor.ReplaceNode(_originalInvocation, newExpression);
return editor.GetChangedDocument();
}
private static bool IsOrImplementSymbol(ITypeSymbol? symbol, ITypeSymbol interfaceSymbol)
{
if (symbol == null)
{
return false;
}
if (SymbolEqualityComparer.Default.Equals(symbol, interfaceSymbol))
{
return true;
}
foreach (var iface in symbol.AllInterfaces)
{
if (SymbolEqualityComparer.Default.Equals(iface, interfaceSymbol))
{
return true;
}
}
return false;
}
private static bool IsInStaticContext(IOperation operation, CancellationToken cancellationToken, out int parentStaticMemberStartPosition)
{
// Local functions can be nested, and an instance local function can be declared
// in a static local function. So, you need to continue to check ancestors when a
// local function is not static.
foreach (var member in operation.Syntax.Ancestors())
{
if (member is LocalFunctionStatementSyntax localFunction)
{
var symbol = operation.SemanticModel!.GetDeclaredSymbol(localFunction, cancellationToken);
if (symbol != null && symbol.IsStatic)
{
parentStaticMemberStartPosition = localFunction.GetLocation().SourceSpan.Start;
return true;
}
}
else if (member is LambdaExpressionSyntax lambdaExpression)
{
var symbol = operation.SemanticModel!.GetSymbolInfo(lambdaExpression, cancellationToken).Symbol;
if (symbol != null && symbol.IsStatic)
{
parentStaticMemberStartPosition = lambdaExpression.GetLocation().SourceSpan.Start;
return true;
}
}
else if (member is AnonymousMethodExpressionSyntax anonymousMethod)
{
var symbol = operation.SemanticModel!.GetSymbolInfo(anonymousMethod, cancellationToken).Symbol;
if (symbol != null && symbol.IsStatic)
{
parentStaticMemberStartPosition = anonymousMethod.GetLocation().SourceSpan.Start;
return true;
}
}
else if (member is MethodDeclarationSyntax methodDeclaration)
{
parentStaticMemberStartPosition = methodDeclaration.GetLocation().SourceSpan.Start;
var symbol = operation.SemanticModel!.GetDeclaredSymbol(methodDeclaration, cancellationToken);
return symbol != null && symbol.IsStatic;
}
}
parentStaticMemberStartPosition = -1;
return false;
}
}

View File

@ -1,35 +0,0 @@
namespace Spectre.Console.Analyzer.FixProviders;
/// <summary>
/// Fix provider to change System.Console calls to AnsiConsole calls.
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp)]
[Shared]
public class StaticAnsiConsoleToInstanceFix : CodeFixProvider
{
/// <inheritdoc />
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
Descriptors.S1010_FavorInstanceAnsiConsoleOverStatic.Id);
/// <inheritdoc />
public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
/// <inheritdoc />
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (root != null)
{
var methodDeclaration = root.FindNode(context.Span, getInnermostNodeForTie: true).FirstAncestorOrSelf<InvocationExpressionSyntax>();
if (methodDeclaration != null)
{
context.RegisterCodeFix(
new SwitchToAnsiConsoleAction(
context.Document,
methodDeclaration,
"Convert static AnsiConsole calls to local instance."),
context.Diagnostics);
}
}
}
}

View File

@ -1,35 +0,0 @@
namespace Spectre.Console.Analyzer.FixProviders;
/// <summary>
/// Fix provider to change System.Console calls to AnsiConsole calls.
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp)]
[Shared]
public class SystemConsoleToAnsiConsoleFix : CodeFixProvider
{
/// <inheritdoc />
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
Descriptors.S1000_UseAnsiConsoleOverSystemConsole.Id);
/// <inheritdoc />
public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
/// <inheritdoc />
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (root != null)
{
var methodDeclaration = root.FindNode(context.Span).FirstAncestorOrSelf<InvocationExpressionSyntax>();
if (methodDeclaration != null)
{
context.RegisterCodeFix(
new SwitchToAnsiConsoleAction(
context.Document,
methodDeclaration,
"Convert static call to AnsiConsole to Spectre.Console.AnsiConsole"),
context.Diagnostics);
}
}
}
}

View File

@ -1,8 +0,0 @@
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Spectre.Console.Analyzer;
internal static class Syntax
{
public static readonly UsingDirectiveSyntax SpectreUsing = UsingDirective(QualifiedName(IdentifierName("Spectre"), IdentifierName("Console")));
}

View File

@ -1,14 +0,0 @@
global using System.Collections.Concurrent;
global using System.Collections.Immutable;
global using System.Composition;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
global using Microsoft.CodeAnalysis;
global using Microsoft.CodeAnalysis.CodeActions;
global using Microsoft.CodeAnalysis.CodeFixes;
global using Microsoft.CodeAnalysis.CSharp;
global using Microsoft.CodeAnalysis.CSharp.Syntax;
global using Microsoft.CodeAnalysis.Diagnostics;
global using Microsoft.CodeAnalysis.Operations;
global using Spectre.Console.Analyzer.CodeActions;

View File

@ -1,33 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Best practice analyzers for Spectre.Console.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>true</IsPackable>
<DevelopmentDependency>true</DevelopmentDependency>
<IncludeBuildOutput>false</IncludeBuildOutput>
<Nullable>enable</Nullable>
<NoPackageAnalysis>true</NoPackageAnalysis>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Remove="bin\Debug\netstandard2.0\\Spectre.Console.Analyzer.dll" />
</ItemGroup>
<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
</Project>

View File

@ -13,6 +13,11 @@ public sealed class CommandContext
/// </value>
public IRemainingArguments Remaining { get; }
/// <summary>
/// Gets all the arguments that were passed to the applicaton.
/// </summary>
public IReadOnlyList<string> Arguments { get; }
/// <summary>
/// Gets the name of the command.
/// </summary>
@ -32,11 +37,17 @@ public sealed class CommandContext
/// <summary>
/// Initializes a new instance of the <see cref="CommandContext"/> class.
/// </summary>
/// <param name="arguments">All arguments that were passed to the application.</param>
/// <param name="remaining">The remaining arguments.</param>
/// <param name="name">The command name.</param>
/// <param name="data">The command data.</param>
public CommandContext(IRemainingArguments remaining, string name, object? data)
public CommandContext(
IEnumerable<string> arguments,
IRemainingArguments remaining,
string name,
object? data)
{
Arguments = arguments.ToSafeReadOnlyList();
Remaining = remaining ?? throw new System.ArgumentNullException(nameof(remaining));
Name = name ?? throw new System.ArgumentNullException(nameof(name));
Data = data;

View File

@ -82,7 +82,7 @@ public static class ConfiguratorExtensions
}
/// <summary>
/// Overrides the auto-detected version of the application.
/// Sets the version of the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="version">The version of application.</param>
@ -98,6 +98,25 @@ public static class ConfiguratorExtensions
return configurator;
}
/// <summary>
/// Uses the version retrieved from the <see cref="AssemblyInformationalVersionAttribute"/>
/// as the application's version.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator UseAssemblyInformationalVersion(this IConfigurator configurator)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ApplicationVersion =
VersionHelper.GetVersion(Assembly.GetEntryAssembly());
return configurator;
}
/// <summary>
/// Hides the <c>DEFAULT</c> column that lists default values coming from the
/// <see cref="DefaultValueAttribute"/> in the options help text.
@ -324,11 +343,16 @@ public static class ConfiguratorExtensions
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
public static ICommandConfigurator AddDelegate<TSettings>(
this IConfigurator<TSettings> configurator,
this IConfigurator<TSettings>? configurator,
string name,
Func<CommandContext, int> func)
where TSettings : CommandSettings
where TSettings : CommandSettings
{
if (typeof(TSettings).IsAbstract)
{
AddDelegate(configurator as IConfigurator<EmptyCommandSettings>, name, func);
}
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));

View File

@ -41,7 +41,7 @@ public class HelpProvider : IHelpProvider
public bool Required { get; }
public string? Description { get; }
public HelpArgument(string name, int position, bool required, string? description)
private HelpArgument(string name, int position, bool required, string? description)
{
Name = name;
Position = position;
@ -68,7 +68,7 @@ public class HelpProvider : IHelpProvider
public string? Description { get; }
public object? DefaultValue { get; }
public HelpOption(string? @short, string? @long, string? @value, bool? valueIsOptional, string? description, object? defaultValue)
private HelpOption(string? @short, string? @long, string? @value, bool? valueIsOptional, string? description, object? defaultValue)
{
Short = @short;
Long = @long;
@ -78,17 +78,27 @@ public class HelpProvider : IHelpProvider
DefaultValue = defaultValue;
}
public static IReadOnlyList<HelpOption> Get(ICommandInfo? command, HelpProviderResources resources)
public static IReadOnlyList<HelpOption> Get(
ICommandModel model,
ICommandInfo? command,
HelpProviderResources resources)
{
var parameters = new List<HelpOption>();
parameters.Add(new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null));
var parameters = new List<HelpOption>
{
new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null),
};
// Version information applies to the entire application
// Include the "-v" option in the help when at the root of the command line application
// Don't allow the "-v" option if users have specified one or more sub-commands
if ((command == null || command?.Parent == null) && !(command?.IsBranch ?? false))
if ((command?.Parent == null) && !(command?.IsBranch ?? false))
{
parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null));
// Only show the version command if there is an
// application version set.
if (model.ApplicationVersion != null)
{
parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null));
}
}
parameters.AddRange(command?.Parameters.OfType<ICommandOption>().Where(o => !o.IsHidden).Select(o =>
@ -101,11 +111,6 @@ public class HelpProvider : IHelpProvider
}
}
internal Composer NewComposer()
{
return new Composer(RenderMarkupInline);
}
/// <summary>
/// Initializes a new instance of the <see cref="HelpProvider"/> class.
/// </summary>
@ -383,7 +388,7 @@ public class HelpProvider : IHelpProvider
public virtual IEnumerable<IRenderable> GetOptions(ICommandModel model, ICommandInfo? command)
{
// Collect all options into a single structure.
var parameters = HelpOption.Get(command, resources);
var parameters = HelpOption.Get(model, command, resources);
if (parameters.Count == 0)
{
return Array.Empty<IRenderable>();
@ -420,7 +425,7 @@ public class HelpProvider : IHelpProvider
if (defaultValueColumn)
{
columns.Add(GetOptionDefaultValue(option.DefaultValue));
columns.Add(GetDefaultValueForOption(option.DefaultValue));
}
columns.Add(NewComposer().Text(option.Description?.TrimEnd('.') ?? " "));
@ -433,60 +438,6 @@ public class HelpProvider : IHelpProvider
return result;
}
private IRenderable GetOptionParts(HelpOption option)
{
var composer = NewComposer();
if (option.Short != null)
{
composer.Text("-").Text(option.Short);
if (option.Long != null)
{
composer.Text(", ");
}
}
else
{
composer.Text(" ");
if (option.Long != null)
{
composer.Text(" ");
}
}
if (option.Long != null)
{
composer.Text("--").Text(option.Long);
}
if (option.Value != null)
{
composer.Text(" ");
if (option.ValueIsOptional ?? false)
{
composer.Style(helpStyles?.Options?.OptionalOption ?? Style.Plain, $"[{option.Value}]");
}
else
{
composer.Style(helpStyles?.Options?.RequiredOption ?? Style.Plain, $"<{option.Value}>");
}
}
return composer;
}
private IRenderable GetOptionDefaultValue(object? defaultValue)
{
return defaultValue switch
{
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),
};
}
/// <summary>
/// Gets the commands section of the help information.
/// </summary>
@ -556,4 +507,63 @@ public class HelpProvider : IHelpProvider
{
yield break;
}
private Composer NewComposer()
{
return new Composer(RenderMarkupInline);
}
private IRenderable GetOptionParts(HelpOption option)
{
var composer = NewComposer();
if (option.Short != null)
{
composer.Text("-").Text(option.Short);
if (option.Long != null)
{
composer.Text(", ");
}
}
else
{
composer.Text(" ");
if (option.Long != null)
{
composer.Text(" ");
}
}
if (option.Long != null)
{
composer.Text("--").Text(option.Long);
}
if (option.Value != null)
{
composer.Text(" ");
if (option.ValueIsOptional ?? false)
{
composer.Style(helpStyles?.Options?.OptionalOption ?? Style.Plain, $"[{option.Value}]");
}
else
{
composer.Style(helpStyles?.Options?.RequiredOption ?? Style.Plain, $"<{option.Value}>");
}
}
return composer;
}
private Composer GetDefaultValueForOption(object? defaultValue)
{
return defaultValue switch
{
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),
};
}
}

View File

@ -9,4 +9,9 @@ public interface ICommandModel : ICommandContainer
/// Gets the name of the application.
/// </summary>
string ApplicationName { get; }
/// <summary>
/// Gets the version of the application.
/// </summary>
string? ApplicationVersion { get; }
}

View File

@ -12,6 +12,7 @@ public interface IRemainingArguments
/// <summary>
/// Gets the raw, non-parsed remaining arguments.
/// This is normally everything after the `--` delimiter.
/// </summary>
IReadOnlyList<string> Raw { get; }
}

View File

@ -17,7 +17,7 @@ internal sealed class CommandExecutor
throw new ArgumentNullException(nameof(configuration));
}
args ??= new List<string>();
var arguments = args.ToSafeReadOnlyList();
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
@ -31,7 +31,7 @@ internal sealed class CommandExecutor
if (model.DefaultCommand == null)
{
// Got at least one argument?
var firstArgument = args.FirstOrDefault();
var firstArgument = arguments.FirstOrDefault();
if (firstArgument != null)
{
// Asking for version? Kind of a hack, but it's alright.
@ -39,15 +39,18 @@ internal sealed class CommandExecutor
if (firstArgument.Equals("--version", StringComparison.OrdinalIgnoreCase) ||
firstArgument.Equals("-v", StringComparison.OrdinalIgnoreCase))
{
var console = configuration.Settings.Console.GetConsole();
console.WriteLine(ResolveApplicationVersion(configuration));
return 0;
if (configuration.Settings.ApplicationVersion != null)
{
var console = configuration.Settings.Console.GetConsole();
console.MarkupLine(configuration.Settings.ApplicationVersion);
return 0;
}
}
}
}
// Parse and map the model against the arguments.
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, arguments);
// Register the arguments with the container.
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
@ -79,7 +82,7 @@ internal sealed class CommandExecutor
}
// Is this the default and is it called without arguments when there are required arguments?
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
if (leaf.Command.IsDefaultCommand && arguments.Count == 0 && leaf.Command.Parameters.Any(p => p.Required))
{
// Display help for default command.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
@ -87,15 +90,18 @@ internal sealed class CommandExecutor
}
// Create the content.
var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data);
var context = new CommandContext(
arguments,
parsedResult.Remaining,
leaf.Command.Name,
leaf.Command.Data);
// Execute the command tree.
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
}
}
#pragma warning disable CS8603 // Possible null reference return.
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IReadOnlyList<string> args)
{
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);
@ -103,7 +109,7 @@ internal sealed class CommandExecutor
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
var parsedResult = parser.Parse(parserContext, tokenizerResult);
var lastParsedLeaf = parsedResult?.Tree?.GetLeafCommand();
var lastParsedLeaf = parsedResult.Tree?.GetLeafCommand();
var lastParsedCommand = lastParsedLeaf?.Command;
if (lastParsedLeaf != null && lastParsedCommand != null &&
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
@ -122,14 +128,6 @@ internal sealed class CommandExecutor
return parsedResult;
}
#pragma warning restore CS8603 // Possible null reference return.
private static string ResolveApplicationVersion(IConfiguration configuration)
{
return
configuration.Settings.ApplicationVersion ?? // potential override
VersionHelper.GetVersion(Assembly.GetEntryAssembly());
}
private static async Task<int> Execute(
CommandTree leaf,

View File

@ -15,23 +15,16 @@ internal sealed class ExplainCommand : Command<ExplainCommand.Settings>
public sealed class Settings : CommandSettings
{
public Settings(string[]? commands, bool? detailed, bool includeHidden)
{
Commands = commands;
Detailed = detailed;
IncludeHidden = includeHidden;
}
[CommandArgument(0, "[command]")]
public string[]? Commands { get; }
public string[]? Commands { get; set; }
[Description("Include detailed information about the commands.")]
[CommandOption("-d|--detailed")]
public bool? Detailed { get; }
public bool? Detailed { get; set; }
[Description("Include hidden commands and options.")]
[CommandOption("--hidden")]
public bool IncludeHidden { get; }
public bool IncludeHidden { get; set; }
}
public override int Execute(CommandContext context, Settings settings)

View File

@ -86,7 +86,7 @@ internal static class TemplateParser
foreach (var character in token.Value)
{
if (!char.IsLetterOrDigit(character) && character != '-' && character != '_')
if (!char.IsLetterOrDigit(character) && character != '-' && character != '_' && character != '?')
{
throw CommandTemplateException.InvalidCharacterInOptionName(template, token, character);
}

View File

@ -0,0 +1,14 @@
namespace Spectre.Console.Cli;
internal static class EnumerableExtensions
{
public static IReadOnlyList<T> ToSafeReadOnlyList<T>(this IEnumerable<T> source)
{
return source switch
{
null => new List<T>(),
IReadOnlyList<T> list => list,
_ => source.ToList(),
};
}
}

View File

@ -3,6 +3,7 @@ namespace Spectre.Console.Cli;
internal sealed class CommandModel : ICommandContainer, ICommandModel
{
public string? ApplicationName { get; }
public string? ApplicationVersion { get; }
public ParsingMode ParsingMode { get; }
public IList<CommandInfo> Commands { get; }
public IList<string[]> Examples { get; }
@ -20,9 +21,10 @@ internal sealed class CommandModel : ICommandContainer, ICommandModel
IEnumerable<string[]> examples)
{
ApplicationName = settings.ApplicationName;
ApplicationVersion = settings.ApplicationVersion;
ParsingMode = settings.ParsingMode;
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
Commands = new List<CommandInfo>(commands);
Examples = new List<string[]>(examples);
}
/// <summary>

View File

@ -21,7 +21,7 @@ internal class CommandTreeParser
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_parsingMode = parsingMode ?? _configuration.ParsingMode;
_help = new CommandOptionAttribute("-h|--help");
_help = new CommandOptionAttribute("-?|-h|--help");
_convertFlagsToRemainingArguments = convertFlagsToRemainingArguments ?? false;
CaseSensitivity = caseSensitivity;
@ -302,11 +302,7 @@ internal class CommandTreeParser
var valueToken = stream.Peek();
if (valueToken?.TokenKind == CommandTreeToken.Kind.String)
{
var parseValue = true;
if (token.TokenKind == CommandTreeToken.Kind.ShortOption && token.IsGrouped)
{
parseValue = false;
}
bool parseValue = token is not { TokenKind: CommandTreeToken.Kind.ShortOption, IsGrouped: true };
if (context.State == State.Normal && parseValue)
{
@ -333,7 +329,7 @@ internal class CommandTreeParser
{
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
context.AddRemainingArgument(token.Value, value);
context.AddRemainingArgument(token.Representation, value);
// Prevent the option and it's non-boolean value from being added to
// mapped parameters (otherwise an exception will be thrown later
@ -364,14 +360,14 @@ internal class CommandTreeParser
// In relaxed parsing mode?
if (context.ParsingMode == ParsingMode.Relaxed)
{
context.AddRemainingArgument(token.Value, value);
context.AddRemainingArgument(token.Representation, value);
}
}
}
}
else
{
context.AddRemainingArgument(token.Value, parseValue ? valueToken.Value : null);
context.AddRemainingArgument(token.Representation, parseValue ? valueToken.Value : null);
}
}
else
@ -379,7 +375,7 @@ internal class CommandTreeParser
if (parameter == null && // Only add tokens which have not been matched to a command parameter
(context.State == State.Remaining || context.ParsingMode == ParsingMode.Relaxed))
{
context.AddRemainingArgument(token.Value, null);
context.AddRemainingArgument(token.Representation, null);
}
}

View File

@ -171,12 +171,12 @@ internal static class CommandTreeTokenizer
}
// Encountered a separator?
if (current == '=' || current == ':')
if (current is '=' or ':')
{
break;
}
if (char.IsLetter(current))
if (char.IsLetter(current) || current is '?')
{
context.AddRemaining(current);
reader.Read(); // Consume
@ -184,7 +184,7 @@ internal static class CommandTreeTokenizer
var value = current.ToString(CultureInfo.InvariantCulture);
result.Add(result.Count == 0
? new CommandTreeToken(CommandTreeToken.Kind.ShortOption, position, value, $"-{value}")
: new CommandTreeToken(CommandTreeToken.Kind.ShortOption, position + result.Count, value, value));
: new CommandTreeToken(CommandTreeToken.Kind.ShortOption, position + result.Count, value, $"-{value}"));
}
else if (result.Count == 0 && char.IsDigit(current))
{

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Arguments" xml:space="preserve">
<value>ARGUMENTOS</value>
</data>
<data name="Command" xml:space="preserve">
<value>COMANDO</value>
</data>
<data name="Commands" xml:space="preserve">
<value>COMMANDOS</value>
</data>
<data name="Default" xml:space="preserve">
<value>POR DEFECTO</value>
</data>
<data name="Description" xml:space="preserve">
<value>DESCRIPCION</value>
</data>
<data name="Examples" xml:space="preserve">
<value>EJEMPLOS</value>
</data>
<data name="Options" xml:space="preserve">
<value>OPCIONES</value>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>Imprime información de ayuda</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>Imprime información de versión</value>
</data>
<data name="Usage" xml:space="preserve">
<value>USO</value>
</data>
</root>

View File

@ -2,19 +2,15 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net7.0;net6.0;netstandard2.0</TargetFrameworks>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
<NoWarn>SA1633</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" />
<ItemGroup Label="REMOVE THIS">
<InternalsVisibleTo Include="Spectre.Console.Cli.Tests" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
<InternalsVisibleTo Include="Spectre.Console.Cli.Tests" />
<ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
@ -23,9 +19,9 @@
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" PrivateAssets="all" />
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" PrivateAssets="all" />
<PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]" />
<PackageReference Include="Nullable" Version="1.3.1">
<PackageReference Include="Nullable">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
<Description>A library that extends Spectre.Console with ImageSharp superpowers.</Description>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
</Project>

View File

@ -1,26 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net7.0;net6.0;netstandard2.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<IsPackable>true</IsPackable>
<Description>A library that extends Spectre.Console with JSON superpowers.</Description>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Spectre.Console\Internal\Extensions\CharExtensions.cs" Link="Internal\CharExtensions.cs" />
<Compile Include="..\Spectre.Console\Internal\Extensions\EnumerableExtensions.cs" Link="Internal\EnumerableExtensions.cs" />
<Compile Include="..\Spectre.Console\Internal\Text\StringBuffer.cs" Link="Internal\StringBuffer.cs" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup>
</Project>

View File

@ -3,21 +3,15 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
<IsTestProject>false</IsTestProject>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
<Description>Contains testing utilities for Spectre.Console.</Description>
</PropertyGroup>
<ItemGroup>
<ItemGroup Label="REMOVE THIS">
<InternalsVisibleTo Include="Spectre.Console.Tests" />
<InternalsVisibleTo Include="Spectre.Console.Cli.Tests" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
</ItemGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\Spectre.Console.Cli\Spectre.Console.Cli.csproj" />
<ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" />

View File

@ -5,7 +5,7 @@ VisualStudioVersion = 17.1.32414.318
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console", "Spectre.Console\Spectre.Console.csproj", "{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{20595AD4-8D75-4AF8-B6BC-9C38C160423F}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{20595AD4-8D75-4AF8-B6BC-9C38C160423F}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Directory.Build.props = Directory.Build.props
@ -13,28 +13,34 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{20595AD4-8
..\dotnet-tools.json = ..\dotnet-tools.json
..\global.json = ..\global.json
stylecop.json = stylecop.json
Directory.Packages.props = Directory.Packages.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub", "GitHub", "{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D}"
ProjectSection(SolutionItems) = preProject
..\.github\workflows\ci.yaml = ..\.github\workflows\ci.yaml
..\.github\workflows\docs.yaml = ..\.github\workflows\docs.yaml
..\.github\workflows\publish.yaml = ..\.github\workflows\publish.yaml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.ImageSharp", "Spectre.Console.ImageSharp\Spectre.Console.ImageSharp.csproj", "{0EFE694D-0770-4E71-BF4E-EC2B41362F79}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.ImageSharp", "Extensions\Spectre.Console.ImageSharp\Spectre.Console.ImageSharp.csproj", "{0EFE694D-0770-4E71-BF4E-EC2B41362F79}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Testing", "Spectre.Console.Testing\Spectre.Console.Testing.csproj", "{7D5F6704-8249-46DD-906C-9E66419F215F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{E0E45070-123C-4A4D-AA98-2A780308876C}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{E0E45070-123C-4A4D-AA98-2A780308876C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Tests", "..\test\Spectre.Console.Tests\Spectre.Console.Tests.csproj", "{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Tests", "Tests\Spectre.Console.Tests\Spectre.Console.Tests.csproj", "{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli", "Spectre.Console.Cli\Spectre.Console.Cli.csproj", "{1B67B74F-1243-4381-9A2B-86EA66D135C5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli.Tests", "..\test\Spectre.Console.Cli.Tests\Spectre.Console.Cli.Tests.csproj", "{E07C46D2-714F-4116-BADD-FEE09617A9C4}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli.Tests", "Tests\Spectre.Console.Cli.Tests\Spectre.Console.Cli.Tests.csproj", "{E07C46D2-714F-4116-BADD-FEE09617A9C4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Json", "Spectre.Console.Json\Spectre.Console.Json.csproj", "{579E6E31-1E2F-4FE1-8F8C-9770878993E9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Json", "Extensions\Spectre.Console.Json\Spectre.Console.Json.csproj", "{579E6E31-1E2F-4FE1-8F8C-9770878993E9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{F34EFD87-6CEA-453F-858B-094EA413578C}"
ProjectSection(SolutionItems) = preProject
Tests\Directory.Build.props = Tests\Directory.Build.props
Tests\.editorconfig = Tests\.editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -138,6 +144,8 @@ Global
{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D} = {20595AD4-8D75-4AF8-B6BC-9C38C160423F}
{0EFE694D-0770-4E71-BF4E-EC2B41362F79} = {E0E45070-123C-4A4D-AA98-2A780308876C}
{579E6E31-1E2F-4FE1-8F8C-9770878993E9} = {E0E45070-123C-4A4D-AA98-2A780308876C}
{60A4CADD-2B3D-48ED-89C0-1637A1F111AE} = {F34EFD87-6CEA-453F-858B-094EA413578C}
{E07C46D2-714F-4116-BADD-FEE09617A9C4} = {F34EFD87-6CEA-453F-858B-094EA413578C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}

View File

@ -1,2 +0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String></wpf:ResourceDictionary>

View File

@ -1,6 +0,0 @@
<SolutionConfiguration>
<Settings>
<AllowParallelTestExecution>True</AllowParallelTestExecution>
<SolutionConfigured>True</SolutionConfigured>
</Settings>
</SolutionConfiguration>

View File

@ -52,11 +52,19 @@ public static partial class AnsiConsoleExtensions
{
if (text.Length > 0)
{
var lastChar = text.Last();
text = text.Substring(0, text.Length - 1);
if (mask != null)
{
console.Write("\b \b");
if (UnicodeCalculator.GetWidth(lastChar) == 1)
{
console.Write("\b \b");
}
else if (UnicodeCalculator.GetWidth(lastChar) == 2)
{
console.Write("\b \b\b \b");
}
}
}

View File

@ -10,10 +10,11 @@ public static class CalendarExtensions
/// </summary>
/// <param name="calendar">The calendar to add the calendar event to.</param>
/// <param name="date">The calendar event date.</param>
/// <param name="customEventHighlightStyle">The calendar event custom highlight style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Calendar AddCalendarEvent(this Calendar calendar, DateTime date)
public static Calendar AddCalendarEvent(this Calendar calendar, DateTime date, Style? customEventHighlightStyle = null)
{
return AddCalendarEvent(calendar, string.Empty, date.Year, date.Month, date.Day);
return AddCalendarEvent(calendar, string.Empty, date.Year, date.Month, date.Day, customEventHighlightStyle);
}
/// <summary>
@ -22,10 +23,11 @@ public static class CalendarExtensions
/// <param name="calendar">The calendar to add the calendar event to.</param>
/// <param name="description">The calendar event description.</param>
/// <param name="date">The calendar event date.</param>
/// <param name="customEventHighlightStyle">The calendar event custom highlight style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Calendar AddCalendarEvent(this Calendar calendar, string description, DateTime date)
public static Calendar AddCalendarEvent(this Calendar calendar, string description, DateTime date, Style? customEventHighlightStyle = null)
{
return AddCalendarEvent(calendar, description, date.Year, date.Month, date.Day);
return AddCalendarEvent(calendar, description, date.Year, date.Month, date.Day, customEventHighlightStyle);
}
/// <summary>
@ -35,10 +37,11 @@ public static class CalendarExtensions
/// <param name="year">The year of the calendar event.</param>
/// <param name="month">The month of the calendar event.</param>
/// <param name="day">The day of the calendar event.</param>
/// <param name="customEventHighlightStyle">The calendar event custom highlight style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Calendar AddCalendarEvent(this Calendar calendar, int year, int month, int day)
public static Calendar AddCalendarEvent(this Calendar calendar, int year, int month, int day, Style? customEventHighlightStyle = null)
{
return AddCalendarEvent(calendar, string.Empty, year, month, day);
return AddCalendarEvent(calendar, string.Empty, year, month, day, customEventHighlightStyle);
}
/// <summary>
@ -49,15 +52,16 @@ public static class CalendarExtensions
/// <param name="year">The year of the calendar event.</param>
/// <param name="month">The month of the calendar event.</param>
/// <param name="day">The day of the calendar event.</param>
/// <param name="customEventHighlightStyle">The calendar event custom highlight style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Calendar AddCalendarEvent(this Calendar calendar, string description, int year, int month, int day)
public static Calendar AddCalendarEvent(this Calendar calendar, string description, int year, int month, int day, Style? customEventHighlightStyle = null)
{
if (calendar is null)
{
throw new ArgumentNullException(nameof(calendar));
}
calendar.CalendarEvents.Add(new CalendarEvent(description, year, month, day));
calendar.CalendarEvents.Add(new CalendarEvent(description, year, month, day, customEventHighlightStyle));
return calendar;
}
@ -65,7 +69,7 @@ public static class CalendarExtensions
/// Sets the calendar's highlight <see cref="Style"/>.
/// </summary>
/// <param name="calendar">The calendar.</param>
/// <param name="style">The highlight style.</param>
/// <param name="style">The default highlight style.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Calendar HighlightStyle(this Calendar calendar, Style? style)
{

View File

@ -98,7 +98,7 @@ internal sealed class HtmlEncoder : IAnsiConsoleEncoder
css.Add("font-weight: bold");
}
if ((style.Decoration & Decoration.Bold) != 0)
if ((style.Decoration & Decoration.Italic) != 0)
{
css.Add("font-style: italic");
}

View File

@ -14,6 +14,7 @@ internal sealed class ListPrompt<T>
public async Task<ListPromptState<T>> Show(
ListPromptTree<T> tree,
Func<T, string> converter,
SelectionMode selectionMode,
bool skipUnselectableItems,
bool searchEnabled,
@ -41,7 +42,12 @@ internal sealed class ListPrompt<T>
}
var nodes = tree.Traverse().ToList();
var state = new ListPromptState<T>(nodes, _strategy.CalculatePageSize(_console, nodes.Count, requestedPageSize), wrapAround, selectionMode, skipUnselectableItems, searchEnabled);
if (nodes.Count == 0)
{
throw new InvalidOperationException("Cannot show an empty selection prompt. Please call the AddChoice() method to configure the prompt.");
}
var state = new ListPromptState<T>(nodes, converter, _strategy.CalculatePageSize(_console, nodes.Count, requestedPageSize), wrapAround, selectionMode, skipUnselectableItems, searchEnabled);
var hook = new ListPromptRenderHook<T>(_console, () => BuildRenderable(state));
using (new RenderHookScope(_console, hook))

View File

@ -9,4 +9,15 @@ internal sealed class ListPromptConstants
public const string InstructionsMarkup = "[grey](Press <space> to select, <enter> to accept)[/]";
public const string MoreChoicesMarkup = "[grey](Move up and down to reveal more choices)[/]";
public const string SearchPlaceholderMarkup = "[grey](Type to search)[/]";
public static string GetSelectedCheckbox(bool isGroup, SelectionMode mode, Style? style = null)
{
if (style != null)
{
return "[[" + $"[{style.ToMarkup()}]X[/]" + "]]";
}
return isGroup && mode == SelectionMode.Leaf
? GroupSelectedCheckbox : SelectedCheckbox;
}
}

View File

@ -3,6 +3,8 @@ namespace Spectre.Console;
internal sealed class ListPromptState<T>
where T : notnull
{
private readonly Func<T, string> _converter;
public int Index { get; private set; }
public int ItemCount => Items.Count;
public int PageSize { get; }
@ -16,8 +18,15 @@ internal sealed class ListPromptState<T>
public ListPromptItem<T> Current => Items[Index];
public string SearchText { get; private set; }
public ListPromptState(IReadOnlyList<ListPromptItem<T>> items, int pageSize, bool wrapAround, SelectionMode mode, bool skipUnselectableItems, bool searchEnabled)
public ListPromptState(
IReadOnlyList<ListPromptItem<T>> items,
Func<T, string> converter,
int pageSize, bool wrapAround,
SelectionMode mode,
bool skipUnselectableItems,
bool searchEnabled)
{
_converter = converter ?? throw new ArgumentNullException(nameof(converter));
Items = items;
PageSize = pageSize;
WrapAround = wrapAround;
@ -126,7 +135,11 @@ internal sealed class ListPromptState<T>
if (!char.IsControl(keyInfo.KeyChar))
{
search = SearchText + keyInfo.KeyChar;
var item = Items.FirstOrDefault(x => x.Data.ToString()?.Contains(search, StringComparison.OrdinalIgnoreCase) == true && (!x.IsGroup || Mode != SelectionMode.Leaf));
var item = Items.FirstOrDefault(x =>
_converter.Invoke(x.Data).Contains(search, StringComparison.OrdinalIgnoreCase)
&& (!x.IsGroup || Mode != SelectionMode.Leaf));
if (item != null)
{
index = Items.IndexOf(item);
@ -140,7 +153,10 @@ internal sealed class ListPromptState<T>
search = search.Substring(0, search.Length - 1);
}
var item = Items.FirstOrDefault(x => x.Data.ToString()?.Contains(search, StringComparison.OrdinalIgnoreCase) == true && (!x.IsGroup || Mode != SelectionMode.Leaf));
var item = Items.FirstOrDefault(x =>
_converter.Invoke(x.Data).Contains(search, StringComparison.OrdinalIgnoreCase) &&
(!x.IsGroup || Mode != SelectionMode.Leaf));
if (item != null)
{
index = Items.IndexOf(item);

View File

@ -94,7 +94,8 @@ public sealed class MultiSelectionPrompt<T> : IPrompt<List<T>>, IListPromptStrat
{
// Create the list prompt
var prompt = new ListPrompt<T>(console, this);
var result = await prompt.Show(Tree, Mode, false, false, PageSize, WrapAround, cancellationToken).ConfigureAwait(false);
var converter = Converter ?? TypeConverterHelper.ConvertToString;
var result = await prompt.Show(Tree, converter, Mode, false, false, PageSize, WrapAround, cancellationToken).ConfigureAwait(false);
if (Mode == SelectionMode.Leaf)
{
@ -256,8 +257,7 @@ public sealed class MultiSelectionPrompt<T> : IPrompt<List<T>>, IListPromptStrat
}
var checkbox = item.Node.IsSelected
? (item.Node.IsGroup && Mode == SelectionMode.Leaf
? ListPromptConstants.GroupSelectedCheckbox : ListPromptConstants.SelectedCheckbox)
? ListPromptConstants.GetSelectedCheckbox(item.Node.IsGroup, Mode, HighlightStyle)
: ListPromptConstants.Checkbox;
grid.AddRow(new Markup(indent + prompt + " " + checkbox + " " + text, style));

View File

@ -99,7 +99,8 @@ public sealed class SelectionPrompt<T> : IPrompt<T>, IListPromptStrategy<T>
{
// Create the list prompt
var prompt = new ListPrompt<T>(console, this);
var result = await prompt.Show(_tree, Mode, true, SearchEnabled, PageSize, WrapAround, cancellationToken).ConfigureAwait(false);
var converter = Converter ?? TypeConverterHelper.ConvertToString;
var result = await prompt.Show(_tree, converter, Mode, true, SearchEnabled, PageSize, WrapAround, cancellationToken).ConfigureAwait(false);
// Return the selected item
return result.Items[result.Index].Data;

View File

@ -2,22 +2,22 @@
<PropertyGroup>
<TargetFrameworks>net8.0;net7.0;net6.0;netstandard2.0</TargetFrameworks>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
<NoWarn>SA1633</NoWarn>
<DefineConstants>$(DefineConstants)TRACE;WCWIDTH_VISIBILITY_INTERNAL</DefineConstants>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
<EmbeddedResource Include="Widgets\Figlet\Fonts\Standard.flf" />
<None Remove="Widgets\Figlet\Fonts\Standard.flf" />
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
<InternalsVisibleTo Include="$(AssemblyName).Tests" />
<ItemGroup Label="REMOVE THIS">
<InternalsVisibleTo Include="$(AssemblyName).Tests"/>
</ItemGroup>
<ItemGroup>
<PackageReference Condition="'$(TargetFramework)' == 'netstandard2.0'" Include="System.Memory" Version="4.5.5" />
<PackageReference Include="Wcwidth.Sources" Version="2.0.0">
<ItemGroup Label="Standard Figlet font">
<EmbeddedResource Include="Widgets\Figlet\Fonts\Standard.flf"/>
<None Remove="Widgets\Figlet\Fonts\Standard.flf"/>
</ItemGroup>
<ItemGroup Label="Dependencies">
<PackageReference Condition="'$(TargetFramework)' == 'netstandard2.0'" Include="System.Memory"/>
<PackageReference Include="Wcwidth.Sources">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
@ -28,17 +28,12 @@
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" PrivateAssets="all" />
<PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]" />
<PackageReference Include="Nullable" Version="1.3.1">
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" PrivateAssets="all"/>
<PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]"/>
<PackageReference Include="Nullable">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants)TRACE;WCWIDTH_VISIBILITY_INTERNAL</DefineConstants>
</PropertyGroup>
</Project>

View File

@ -196,9 +196,10 @@ public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorde
{
if (weekdays[currentDay - 1] == weekday)
{
if (_calendarEvents.Any(e => e.Month == Month && e.Day == currentDay))
var todayEvent = _calendarEvents.LastOrDefault(e => e.Month == Month && e.Day == currentDay);
if (todayEvent != null)
{
row.Add(new Markup(currentDay.ToString(CultureInfo.InvariantCulture) + "*", _highlightStyle));
row.Add(new Markup(currentDay.ToString(CultureInfo.InvariantCulture) + "*", todayEvent.CustomHighlightStyle ?? _highlightStyle));
}
else
{

View File

@ -25,14 +25,20 @@ public sealed class CalendarEvent
/// </summary>
public int Day { get; }
/// <summary>
/// Gets the custom highlight style of the calendar event.
/// </summary>
public Style? CustomHighlightStyle { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CalendarEvent"/> class.
/// </summary>
/// <param name="year">The year of the calendar event.</param>
/// <param name="month">The month of the calendar event.</param>
/// <param name="day">The day of the calendar event.</param>
public CalendarEvent(int year, int month, int day)
: this(string.Empty, year, month, day)
/// <param name="customHighlightStyle">The custom highlight style of the calendar event.</param>
public CalendarEvent(int year, int month, int day, Style? customHighlightStyle = null)
: this(string.Empty, year, month, day, customHighlightStyle)
{
}
@ -43,11 +49,13 @@ public sealed class CalendarEvent
/// <param name="year">The year of the calendar event.</param>
/// <param name="month">The month of the calendar event.</param>
/// <param name="day">The day of the calendar event.</param>
public CalendarEvent(string description, int year, int month, int day)
/// <param name="customHighlightStyle">The custom highlight style of the calendar event.</param>
public CalendarEvent(string description, int year, int month, int day, Style? customHighlightStyle = null)
{
Description = description ?? string.Empty;
Year = year;
Month = month;
Day = day;
CustomHighlightStyle = customHighlightStyle;
}
}

View File

@ -150,9 +150,9 @@ internal static class TableRenderer
result.Add(Segment.LineBreak);
}
// Show row separator?
// Show row separator, if headers are hidden show separator after the first row
if (context.Border.SupportsRowSeparator && context.ShowRowSeparators
&& !isFirstRow && !isLastRow)
&& (!isFirstRow || (isFirstRow && !context.ShowHeaders)) && !isLastRow)
{
var hasVisibleFootes = context is { ShowFooters: true, HasFooters: true };
var isNextLastLine = index == context.Rows.Count - 2;

View File

@ -7,6 +7,7 @@ namespace Spectre.Console;
public sealed class Tree : Renderable, IHasTreeNodes
{
private readonly TreeNode _root;
private bool _expanded = true;
/// <summary>
/// Gets or sets the tree style.
@ -26,7 +27,15 @@ public sealed class Tree : Renderable, IHasTreeNodes
/// <summary>
/// Gets or sets a value indicating whether or not the tree is expanded or not.
/// </summary>
public bool Expanded { get; set; } = true;
public bool Expanded
{
get => _expanded;
set
{
_expanded = value;
_root.Expand(value);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="Tree"/> class.

107
src/Tests/.editorconfig Normal file
View File

@ -0,0 +1,107 @@
root = false
[*.cs]
# Default severity for analyzer diagnostics with category 'StyleCop.CSharp.DocumentationRules'
dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = none
# IDE0055: Fix formatting
dotnet_diagnostic.IDE0055.severity = warning
# SA1101: Prefix local calls with this
dotnet_diagnostic.SA1101.severity = none
# SA1633: File should have header
dotnet_diagnostic.SA1633.severity = none
# SA1201: Elements should appear in the correct order
dotnet_diagnostic.SA1201.severity = none
# SA1202: Public members should come before private members
dotnet_diagnostic.SA1202.severity = none
# SA1309: Field names should not begin with underscore
dotnet_diagnostic.SA1309.severity = none
# SA1404: Code analysis suppressions should have justification
dotnet_diagnostic.SA1404.severity = none
# SA1516: Elements should be separated by a blank line
dotnet_diagnostic.SA1516.severity = none
# CA1303: Do not pass literals as localized parameters
dotnet_diagnostic.CA1303.severity = none
# CSA1204: Static members should appear before non-static members
dotnet_diagnostic.SA1204.severity = none
# IDE0052: Remove unread private members
dotnet_diagnostic.IDE0052.severity = warning
# IDE0063: Use simple 'using' statement
csharp_prefer_simple_using_statement = false:suggestion
# IDE0018: Variable declaration can be inlined
dotnet_diagnostic.IDE0018.severity = warning
# SA1625: Element documenation should not be copied and pasted
dotnet_diagnostic.SA1625.severity = none
# IDE0005: Using directive is unnecessary
dotnet_diagnostic.IDE0005.severity = warning
# SA1117: Parameters should be on same line or separate lines
dotnet_diagnostic.SA1117.severity = none
# SA1404: Code analysis suppression should have justification
dotnet_diagnostic.SA1404.severity = none
# SA1101: Prefix local calls with this
dotnet_diagnostic.SA1101.severity = none
# SA1633: File should have header
dotnet_diagnostic.SA1633.severity = none
# SA1649: File name should match first type name
dotnet_diagnostic.SA1649.severity = none
# SA1402: File may only contain a single type
dotnet_diagnostic.SA1402.severity = none
# CA1814: Prefer jagged arrays over multidimensional
dotnet_diagnostic.CA1814.severity = none
# RCS1194: Implement exception constructors.
dotnet_diagnostic.RCS1194.severity = none
# CA1032: Implement standard exception constructors
dotnet_diagnostic.CA1032.severity = none
# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly
dotnet_diagnostic.CA1826.severity = none
# RCS1079: Throwing of new NotImplementedException.
dotnet_diagnostic.RCS1079.severity = warning
# RCS1057: Add empty line between declarations.
dotnet_diagnostic.RCS1057.severity = none
# RCS1057: Validate arguments correctly
dotnet_diagnostic.RCS1227.severity = none
# IDE0004: Remove Unnecessary Cast
dotnet_diagnostic.IDE0004.severity = warning
# CA1810: Initialize reference type static fields inline
dotnet_diagnostic.CA1810.severity = none
# IDE0044: Add readonly modifier
dotnet_diagnostic.IDE0044.severity = warning
# RCS1047: Non-asynchronous method name should not end with 'Async'.
dotnet_diagnostic.RCS1047.severity = none
# RCS1090: Call 'ConfigureAwait(false)'.
dotnet_diagnostic.RCS1090.severity = warning
# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = none

View File

@ -0,0 +1,13 @@
<Project>
<PropertyGroup Label="Settings">
<LangVersion>12</LangVersion>
<IsPackable>false</IsPackable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\..\..\resources\spectre.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)/../stylecop.json" Link="Properties/stylecop.json"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,18 @@
namespace Spectre.Console.Tests;
public static class Constants
{
public static string[] VersionCommand { get; } =
new[]
{
CliConstants.Commands.Branch,
CliConstants.Commands.Version,
};
public static string[] XmlDocCommand { get; } =
new[]
{
CliConstants.Commands.Branch,
CliConstants.Commands.XmlDoc,
};
}

View File

@ -0,0 +1,8 @@
using SystemConsole = System.Console;
namespace Spectre.Console.Tests.Data;
public abstract class AnimalCommand<TSettings> : Command<TSettings>
where TSettings : CommandSettings
{
}

View File

@ -0,0 +1,28 @@
namespace Spectre.Console.Tests.Data;
public sealed class AsynchronousCommand : AsyncCommand<AsynchronousCommandSettings>
{
private readonly IAnsiConsole _console;
public AsynchronousCommand(IAnsiConsole console)
{
_console = console;
}
public async override Task<int> ExecuteAsync(CommandContext context, AsynchronousCommandSettings settings)
{
// Simulate a long running asynchronous task
await Task.Delay(200);
if (settings.ThrowException)
{
throw new Exception($"Throwing exception asynchronously");
}
else
{
_console.WriteLine($"Finished executing asynchronously");
}
return 0;
}
}

View File

@ -0,0 +1,9 @@
namespace Spectre.Console.Tests.Data;
public class CatCommand : AnimalCommand<CatSettings>
{
public override int Execute(CommandContext context, CatSettings settings)
{
return 0;
}
}

View File

@ -0,0 +1,30 @@
namespace Spectre.Console.Tests.Data;
[Description("The dog command.")]
public class DogCommand : AnimalCommand<DogSettings>
{
public override ValidationResult Validate(CommandContext context, DogSettings settings)
{
if (context is null)
{
throw new System.ArgumentNullException(nameof(context));
}
if (settings is null)
{
throw new System.ArgumentNullException(nameof(settings));
}
if (settings.Age > 100 && !context.Remaining.Raw.Contains("zombie"))
{
return ValidationResult.Error("Dog is too old...");
}
return base.Validate(context, settings);
}
public override int Execute(CommandContext context, DogSettings settings)
{
return 0;
}
}

View File

@ -0,0 +1,34 @@
namespace Spectre.Console.Tests.Data;
public sealed class DumpRemainingCommand : Command<EmptyCommandSettings>
{
private readonly IAnsiConsole _console;
public DumpRemainingCommand(IAnsiConsole console)
{
_console = console;
}
public override int Execute(CommandContext context, EmptyCommandSettings settings)
{
if (context.Remaining.Raw.Count > 0)
{
_console.WriteLine("# Raw");
foreach (var item in context.Remaining.Raw)
{
_console.WriteLine(item);
}
}
if (context.Remaining.Parsed.Count > 0)
{
_console.WriteLine("# Parsed");
foreach (var item in context.Remaining.Parsed)
{
_console.WriteLine(string.Format("{0}={1}", item.Key, string.Join(",", item.Select(x => x))));
}
}
return 0;
}
}

View File

@ -0,0 +1,9 @@
namespace Spectre.Console.Tests.Data;
public sealed class EmptyCommand : Command<EmptyCommandSettings>
{
public override int Execute(CommandContext context, EmptyCommandSettings settings)
{
return 0;
}
}

View File

@ -0,0 +1,10 @@
namespace Spectre.Console.Tests.Data;
public sealed class GenericCommand<TSettings> : Command<TSettings>
where TSettings : CommandSettings
{
public override int Execute(CommandContext context, TSettings settings)
{
return 0;
}
}

View File

@ -0,0 +1,10 @@
namespace Spectre.Console.Tests.Data;
[Description("The giraffe command.")]
public sealed class GiraffeCommand : Command<GiraffeSettings>
{
public override int Execute(CommandContext context, GiraffeSettings settings)
{
return 0;
}
}

View File

@ -0,0 +1,17 @@
using Spectre.Console;
public class GreeterCommand : Command<OptionalArgumentWithDefaultValueSettings>
{
private readonly IAnsiConsole _console;
public GreeterCommand(IAnsiConsole console)
{
_console = console;
}
public override int Execute(CommandContext context, OptionalArgumentWithDefaultValueSettings settings)
{
_console.WriteLine(settings.Greeting);
return 0;
}
}

View File

@ -0,0 +1,9 @@
namespace Spectre.Console.Tests.Data;
public sealed class HiddenOptionsCommand : Command<HiddenOptionSettings>
{
public override int Execute(CommandContext context, HiddenOptionSettings settings)
{
return 0;
}
}

View File

@ -0,0 +1,10 @@
namespace Spectre.Console.Tests.Data;
[Description("The horse command.")]
public class HorseCommand : AnimalCommand<HorseSettings>
{
public override int Execute(CommandContext context, HorseSettings settings)
{
return 0;
}
}

View File

@ -0,0 +1,9 @@
namespace Spectre.Console.Tests.Data;
public sealed class InvalidCommand : Command<InvalidSettings>
{
public override int Execute(CommandContext context, InvalidSettings settings)
{
return 0;
}
}

View File

@ -0,0 +1,10 @@
namespace Spectre.Console.Tests.Data;
[Description("The lion command.")]
public class LionCommand : AnimalCommand<LionSettings>
{
public override int Execute(CommandContext context, LionSettings settings)
{
return 0;
}
}

View File

@ -0,0 +1,12 @@
namespace Spectre.Console.Tests.Data;
public sealed class NoDescriptionCommand : Command<EmptyCommandSettings>
{
[CommandOption("-f|--foo <VALUE>")]
public int Foo { get; set; }
public override int Execute(CommandContext context, EmptyCommandSettings settings)
{
return 0;
}
}

View File

@ -0,0 +1,9 @@
namespace Spectre.Console.Tests.Data;
public class OptionVectorCommand : Command<OptionVectorSettings>
{
public override int Execute(CommandContext context, OptionVectorSettings settings)
{
return 0;
}
}

View File

@ -0,0 +1,9 @@
namespace Spectre.Console.Tests.Data;
public sealed class ThrowingCommand : Command<ThrowingCommandSettings>
{
public override int Execute(CommandContext context, ThrowingCommandSettings settings)
{
throw new InvalidOperationException("W00t?");
}
}

View File

@ -0,0 +1,10 @@
namespace Spectre.Console.Tests.Data;
[Description("The turtle command.")]
public class TurtleCommand : AnimalCommand<TurtleSettings>
{
public override int Execute(CommandContext context, TurtleSettings settings)
{
return 0;
}
}

View File

@ -0,0 +1,18 @@
namespace Spectre.Console.Tests.Data;
public sealed class VersionCommand : Command<VersionSettings>
{
private readonly IAnsiConsole _console;
public VersionCommand(IAnsiConsole console)
{
_console = console;
}
public override int Execute(CommandContext context, VersionSettings settings)
{
_console.WriteLine($"VersionCommand ran, Version: {settings.Version ?? string.Empty}");
return 0;
}
}

View File

@ -0,0 +1,14 @@
namespace Spectre.Console.Tests.Data;
public sealed class CatAgilityConverter : TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string stringValue)
{
return stringValue.Length;
}
return base.ConvertFrom(context, culture, value);
}
}

View File

@ -0,0 +1,14 @@
namespace Spectre.Console.Tests.Data;
public sealed class StringToIntegerConverter : TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string stringValue)
{
return int.Parse(stringValue, CultureInfo.InvariantCulture);
}
return base.ConvertFrom(context, culture, value);
}
}

Some files were not shown because too many files have changed in this diff Show More