Use file scoped namespace declarations

This commit is contained in:
Patrik Svensson
2021-12-21 11:06:46 +01:00
committed by Phil Scott
parent 1dbaf50935
commit ec1188b837
607 changed files with 28739 additions and 29245 deletions

View File

@ -1,7 +1,7 @@
<Project>
<PropertyGroup Label="Settings">
<Deterministic>true</Deterministic>
<LangVersion>9.0</LangVersion>
<LangVersion>10</LangVersion>
<DebugSymbols>true</DebugSymbols>
<DebugType>embedded</DebugType>
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>

View File

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

View File

@ -6,90 +6,89 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
namespace Spectre.Console.Analyzer
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
{
/// <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)
{
private static readonly DiagnosticDescriptor _diagnosticDescriptor =
Descriptors.S1010_FavorInstanceAnsiConsoleOverStatic;
compilationStartContext.RegisterOperationAction(
context =>
{
var ansiConsoleType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(_diagnosticDescriptor);
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
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 (!Equals(invocationOperation.TargetMethod.ContainingType, ansiConsoleType))
{
var ansiConsoleType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
return;
}
// 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 (!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 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;
}
if (!HasFieldAnsiConsole(invocationOperation.Syntax) &&
!HasParameterAnsiConsole(invocationOperation.Syntax))
{
return;
}
var methodSymbol = invocationOperation.TargetMethod;
var methodSymbol = invocationOperation.TargetMethod;
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
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.Kind() == 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.Kind() == SyntaxKind.StaticKeyword)));
}
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.Kind() == 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.Kind() == SyntaxKind.StaticKeyword)));
}
}

View File

@ -7,72 +7,71 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
namespace Spectre.Console.Analyzer
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
{
/// <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)
{
private static readonly DiagnosticDescriptor _diagnosticDescriptor =
Descriptors.S1020_AvoidConcurrentCallsToMultipleLiveRenderables;
compilationStartContext.RegisterOperationAction(
context =>
{
var invocationOperation = (IInvocationOperation)context.Operation;
var methodSymbol = invocationOperation.TargetMethod;
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(_diagnosticDescriptor);
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
compilationStartContext.RegisterOperationAction(
context =>
const string StartMethod = "Start";
if (methodSymbol.Name != StartMethod)
{
var invocationOperation = (IInvocationOperation)context.Operation;
var methodSymbol = invocationOperation.TargetMethod;
return;
}
const string StartMethod = "Start";
if (methodSymbol.Name != StartMethod)
{
return;
}
var liveTypes = Constants.LiveRenderables
.Select(i => context.Compilation.GetTypeByMetadataName(i))
.ToImmutableArray();
var liveTypes = Constants.LiveRenderables
.Select(i => context.Compilation.GetTypeByMetadataName(i))
.ToImmutableArray();
if (liveTypes.All(i => !Equals(i, methodSymbol.ContainingType)))
{
return;
}
if (liveTypes.All(i => !Equals(i, methodSymbol.ContainingType)))
{
return;
}
var model = context.Compilation.GetSemanticModel(context.Operation.Syntax.SyntaxTree);
var parentInvocations = invocationOperation
.Syntax.Ancestors()
.OfType<InvocationExpressionSyntax>()
.Select(i => model.GetOperation(i))
.OfType<IInvocationOperation>()
.ToList();
var model = context.Compilation.GetSemanticModel(context.Operation.Syntax.SyntaxTree);
var parentInvocations = invocationOperation
.Syntax.Ancestors()
.OfType<InvocationExpressionSyntax>()
.Select(i => model.GetOperation(i))
.OfType<IInvocationOperation>()
.ToList();
if (parentInvocations.All(parent =>
parent.TargetMethod.Name != StartMethod || !liveTypes.Contains(parent.TargetMethod.ContainingType)))
{
return;
}
if (parentInvocations.All(parent =>
parent.TargetMethod.Name != StartMethod || !liveTypes.Contains(parent.TargetMethod.ContainingType)))
{
return;
}
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
context.ReportDiagnostic(
Diagnostic.Create(
_diagnosticDescriptor,
invocationOperation.Syntax.GetLocation(),
displayString));
}, OperationKind.Invocation);
}
context.ReportDiagnostic(
Diagnostic.Create(
_diagnosticDescriptor,
invocationOperation.Syntax.GetLocation(),
displayString));
}, OperationKind.Invocation);
}
}
}

View File

@ -7,78 +7,77 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
namespace Spectre.Console.Analyzer
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
{
/// <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)
{
private static readonly DiagnosticDescriptor _diagnosticDescriptor =
Descriptors.S1021_AvoidPromptCallsDuringLiveRenderables;
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;
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(_diagnosticDescriptor);
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
compilationStartContext.RegisterOperationAction(
context =>
var promptMethods = ImmutableArray.Create("Ask", "Confirm", "Prompt");
if (!promptMethods.Contains(methodSymbol.Name))
{
// 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;
return;
}
var promptMethods = ImmutableArray.Create("Ask", "Confirm", "Prompt");
if (!promptMethods.Contains(methodSymbol.Name))
{
return;
}
var ansiConsoleType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
var ansiConsoleExtensionsType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsoleExtensions");
var ansiConsoleType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
var ansiConsoleExtensionsType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsoleExtensions");
if (!Equals(methodSymbol.ContainingType, ansiConsoleType) && !Equals(methodSymbol.ContainingType, ansiConsoleExtensionsType))
{
return;
}
if (!Equals(methodSymbol.ContainingType, ansiConsoleType) && !Equals(methodSymbol.ContainingType, ansiConsoleExtensionsType))
{
return;
}
var model = context.Compilation.GetSemanticModel(context.Operation.Syntax.SyntaxTree);
var parentInvocations = invocationOperation
.Syntax.Ancestors()
.OfType<InvocationExpressionSyntax>()
.Select(i => model.GetOperation(i))
.OfType<IInvocationOperation>()
.ToList();
var model = context.Compilation.GetSemanticModel(context.Operation.Syntax.SyntaxTree);
var parentInvocations = invocationOperation
.Syntax.Ancestors()
.OfType<InvocationExpressionSyntax>()
.Select(i => model.GetOperation(i))
.OfType<IInvocationOperation>()
.ToList();
var liveTypes = Constants.LiveRenderables
.Select(i => context.Compilation.GetTypeByMetadataName(i))
.ToImmutableArray();
var liveTypes = Constants.LiveRenderables
.Select(i => context.Compilation.GetTypeByMetadataName(i))
.ToImmutableArray();
if (parentInvocations.All(parent =>
parent.TargetMethod.Name != "Start" ||
!liveTypes.Contains(parent.TargetMethod.ContainingType)))
{
return;
}
if (parentInvocations.All(parent =>
parent.TargetMethod.Name != "Start" ||
!liveTypes.Contains(parent.TargetMethod.ContainingType)))
{
return;
}
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
context.ReportDiagnostic(
Diagnostic.Create(
_diagnosticDescriptor,
invocationOperation.Syntax.GetLocation(),
displayString));
}, OperationKind.Invocation);
}
context.ReportDiagnostic(
Diagnostic.Create(
_diagnosticDescriptor,
invocationOperation.Syntax.GetLocation(),
displayString));
}, OperationKind.Invocation);
}
}
}

View File

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

View File

@ -4,60 +4,59 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
namespace Spectre.Console.Analyzer
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
{
/// <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)
{
private static readonly DiagnosticDescriptor _diagnosticDescriptor =
Descriptors.S1000_UseAnsiConsoleOverSystemConsole;
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;
private static readonly string[] _methods = { "WriteLine", "Write" };
/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(_diagnosticDescriptor);
/// <inheritdoc />
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
{
compilationStartContext.RegisterOperationAction(
context =>
var methodName = System.Array.Find(_methods, i => i.Equals(invocationOperation.TargetMethod.Name));
if (methodName == null)
{
// 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;
return;
}
var methodName = System.Array.Find(_methods, i => i.Equals(invocationOperation.TargetMethod.Name));
if (methodName == null)
{
return;
}
var systemConsoleType = context.Compilation.GetTypeByMetadataName("System.Console");
var systemConsoleType = context.Compilation.GetTypeByMetadataName("System.Console");
if (!Equals(invocationOperation.TargetMethod.ContainingType, systemConsoleType))
{
return;
}
if (!Equals(invocationOperation.TargetMethod.ContainingType, systemConsoleType))
{
return;
}
var methodSymbol = invocationOperation.TargetMethod;
var methodSymbol = invocationOperation.TargetMethod;
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
var displayString = SymbolDisplay.ToDisplayString(
methodSymbol,
SymbolDisplayFormat.CSharpShortErrorMessageFormat
.WithParameterOptions(SymbolDisplayParameterOptions.None)
.WithGenericsOptions(SymbolDisplayGenericsOptions.None));
context.ReportDiagnostic(
Diagnostic.Create(
_diagnosticDescriptor,
invocationOperation.Syntax.GetLocation(),
displayString));
}, OperationKind.Invocation);
}
context.ReportDiagnostic(
Diagnostic.Create(
_diagnosticDescriptor,
invocationOperation.Syntax.GetLocation(),
displayString));
}, OperationKind.Invocation);
}
}
}

View File

@ -1,15 +1,14 @@
namespace Spectre.Console.Analyzer
{
internal static class Constants
{
internal const string StaticInstance = "AnsiConsole";
internal const string SpectreConsole = "Spectre.Console";
namespace Spectre.Console.Analyzer;
internal static readonly string[] LiveRenderables =
{
"Spectre.Console.LiveDisplay",
"Spectre.Console.Progress",
"Spectre.Console.Status",
};
}
}
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

@ -3,77 +3,76 @@ using Microsoft.CodeAnalysis;
using static Microsoft.CodeAnalysis.DiagnosticSeverity;
using static Spectre.Console.Analyzer.Descriptors.Category;
namespace Spectre.Console.Analyzer
namespace Spectre.Console.Analyzer;
/// <summary>
/// Code analysis descriptors.
/// </summary>
public static class Descriptors
{
/// <summary>
/// Code analysis descriptors.
/// </summary>
public static class Descriptors
internal enum Category
{
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.");
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

@ -7,110 +7,109 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Spectre.Console.Analyzer.CodeActions
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>
/// Code action to change calls to System.Console to AnsiConsole.
/// Initializes a new instance of the <see cref="SwitchToAnsiConsoleAction"/> class.
/// </summary>
public class SwitchToAnsiConsoleAction : CodeAction
/// <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)
{
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 originalCaller = ((MemberAccessExpressionSyntax)_originalInvocation.Expression).Name.ToString();
var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var root = (CompilationUnitSyntax)await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
// If there is an ansiConsole 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 ansiConsoleParameterDeclaration = GetAnsiConsoleParameterDeclaration();
var ansiConsoleFieldIdentifier = GetAnsiConsoleFieldDeclaration();
var ansiConsoleIdentifier = ansiConsoleParameterDeclaration ??
ansiConsoleFieldIdentifier ??
Constants.StaticInstance;
// Replace the System.Console call with a call to the identifier above.
var newRoot = root.ReplaceNode(
_originalInvocation,
GetImportedSpectreCall(originalCaller, ansiConsoleIdentifier));
// If we are calling the static instance and Spectre isn't imported yet we should do so.
if (ansiConsoleIdentifier == Constants.StaticInstance && root.Usings.ToList().All(i => i.Name.ToString() != Constants.SpectreConsole))
{
newRoot = newRoot.AddUsings(Syntax.SpectreUsing);
}
return _document.WithSyntaxRoot(newRoot);
}
private string? GetAnsiConsoleParameterDeclaration()
{
return _originalInvocation
.Ancestors().OfType<MethodDeclarationSyntax>()
.First()
.ParameterList.Parameters
.FirstOrDefault(i => i.Type.NormalizeWhitespace().ToString() == "IAnsiConsole")
?.Identifier.Text;
}
private string? GetAnsiConsoleFieldDeclaration()
{
// let's look to see if our call is in a static method.
// if so we'll only want to look for static IAnsiConsoles
// and vice-versa if we aren't.
var isStatic = _originalInvocation
.Ancestors()
.OfType<MethodDeclarationSyntax>()
.First()
.Modifiers.Any(i => i.Kind() == SyntaxKind.StaticKeyword);
return _originalInvocation
.Ancestors().OfType<ClassDeclarationSyntax>()
.First()
.Members
.OfType<FieldDeclarationSyntax>()
.FirstOrDefault(i =>
i.Declaration.Type.NormalizeWhitespace().ToString() == "IAnsiConsole" &&
(!isStatic ^ i.Modifiers.Any(modifier => modifier.Kind() == SyntaxKind.StaticKeyword)))
?.Declaration.Variables.First().Identifier.Text;
}
private ExpressionSyntax GetImportedSpectreCall(string originalCaller, string ansiConsoleIdentifier)
{
return ExpressionStatement(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(ansiConsoleIdentifier),
IdentifierName(originalCaller)))
.WithArgumentList(_originalInvocation.ArgumentList)
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia())
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia()))
.Expression;
}
_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 originalCaller = ((MemberAccessExpressionSyntax)_originalInvocation.Expression).Name.ToString();
var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var root = (CompilationUnitSyntax)await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
// If there is an ansiConsole 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 ansiConsoleParameterDeclaration = GetAnsiConsoleParameterDeclaration();
var ansiConsoleFieldIdentifier = GetAnsiConsoleFieldDeclaration();
var ansiConsoleIdentifier = ansiConsoleParameterDeclaration ??
ansiConsoleFieldIdentifier ??
Constants.StaticInstance;
// Replace the System.Console call with a call to the identifier above.
var newRoot = root.ReplaceNode(
_originalInvocation,
GetImportedSpectreCall(originalCaller, ansiConsoleIdentifier));
// If we are calling the static instance and Spectre isn't imported yet we should do so.
if (ansiConsoleIdentifier == Constants.StaticInstance && root.Usings.ToList().All(i => i.Name.ToString() != Constants.SpectreConsole))
{
newRoot = newRoot.AddUsings(Syntax.SpectreUsing);
}
return _document.WithSyntaxRoot(newRoot);
}
private string? GetAnsiConsoleParameterDeclaration()
{
return _originalInvocation
.Ancestors().OfType<MethodDeclarationSyntax>()
.First()
.ParameterList.Parameters
.FirstOrDefault(i => i.Type.NormalizeWhitespace().ToString() == "IAnsiConsole")
?.Identifier.Text;
}
private string? GetAnsiConsoleFieldDeclaration()
{
// let's look to see if our call is in a static method.
// if so we'll only want to look for static IAnsiConsoles
// and vice-versa if we aren't.
var isStatic = _originalInvocation
.Ancestors()
.OfType<MethodDeclarationSyntax>()
.First()
.Modifiers.Any(i => i.Kind() == SyntaxKind.StaticKeyword);
return _originalInvocation
.Ancestors().OfType<ClassDeclarationSyntax>()
.First()
.Members
.OfType<FieldDeclarationSyntax>()
.FirstOrDefault(i =>
i.Declaration.Type.NormalizeWhitespace().ToString() == "IAnsiConsole" &&
(!isStatic ^ i.Modifiers.Any(modifier => modifier.Kind() == SyntaxKind.StaticKeyword)))
?.Declaration.Variables.First().Identifier.Text;
}
private ExpressionSyntax GetImportedSpectreCall(string originalCaller, string ansiConsoleIdentifier)
{
return ExpressionStatement(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName(ansiConsoleIdentifier),
IdentifierName(originalCaller)))
.WithArgumentList(_originalInvocation.ArgumentList)
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia())
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia()))
.Expression;
}
}

View File

@ -6,30 +6,29 @@ using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Spectre.Console.Analyzer.CodeActions;
namespace Spectre.Console.Analyzer.FixProviders
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
{
/// <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)
{
/// <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);
var methodDeclaration = root.FindNode(context.Span).FirstAncestorOrSelf<InvocationExpressionSyntax>();
context.RegisterCodeFix(
new SwitchToAnsiConsoleAction(context.Document, methodDeclaration, "Convert static AnsiConsole calls to local instance."),
context.Diagnostics);
}
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var methodDeclaration = root.FindNode(context.Span).FirstAncestorOrSelf<InvocationExpressionSyntax>();
context.RegisterCodeFix(
new SwitchToAnsiConsoleAction(context.Document, methodDeclaration, "Convert static AnsiConsole calls to local instance."),
context.Diagnostics);
}
}
}

View File

@ -6,30 +6,29 @@ using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Spectre.Console.Analyzer.CodeActions;
namespace Spectre.Console.Analyzer.FixProviders
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
{
/// <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)
{
/// <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);
var methodDeclaration = root.FindNode(context.Span).FirstAncestorOrSelf<InvocationExpressionSyntax>();
context.RegisterCodeFix(
new SwitchToAnsiConsoleAction(context.Document, methodDeclaration, "Convert static call to AnsiConsole to Spectre.Console.AnsiConsole"),
context.Diagnostics);
}
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var methodDeclaration = root.FindNode(context.Span).FirstAncestorOrSelf<InvocationExpressionSyntax>();
context.RegisterCodeFix(
new SwitchToAnsiConsoleAction(context.Document, methodDeclaration, "Convert static call to AnsiConsole to Spectre.Console.AnsiConsole"),
context.Diagnostics);
}
}
}

View File

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

View File

@ -6,139 +6,138 @@ using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using Spectre.Console.Rendering;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// Represents a renderable image.
/// </summary>
public sealed class CanvasImage : Renderable
{
private static readonly IResampler _defaultResampler = KnownResamplers.Bicubic;
/// <summary>
/// Represents a renderable image.
/// Gets the image width.
/// </summary>
public sealed class CanvasImage : Renderable
public int Width => Image.Width;
/// <summary>
/// Gets the image height.
/// </summary>
public int Height => Image.Height;
/// <summary>
/// Gets or sets the render width of the canvas.
/// </summary>
public int? MaxWidth { get; set; }
/// <summary>
/// Gets or sets the render width of the canvas.
/// </summary>
public int PixelWidth { get; set; } = 2;
/// <summary>
/// Gets or sets the <see cref="IResampler"/> that should
/// be used when scaling the image. Defaults to bicubic sampling.
/// </summary>
public IResampler? Resampler { get; set; }
internal SixLabors.ImageSharp.Image<Rgba32> Image { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CanvasImage"/> class.
/// </summary>
/// <param name="filename">The image filename.</param>
public CanvasImage(string filename)
{
private static readonly IResampler _defaultResampler = KnownResamplers.Bicubic;
/// <summary>
/// Gets the image width.
/// </summary>
public int Width => Image.Width;
/// <summary>
/// Gets the image height.
/// </summary>
public int Height => Image.Height;
/// <summary>
/// Gets or sets the render width of the canvas.
/// </summary>
public int? MaxWidth { get; set; }
/// <summary>
/// Gets or sets the render width of the canvas.
/// </summary>
public int PixelWidth { get; set; } = 2;
/// <summary>
/// Gets or sets the <see cref="IResampler"/> that should
/// be used when scaling the image. Defaults to bicubic sampling.
/// </summary>
public IResampler? Resampler { get; set; }
internal SixLabors.ImageSharp.Image<Rgba32> Image { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CanvasImage"/> class.
/// </summary>
/// <param name="filename">The image filename.</param>
public CanvasImage(string filename)
{
Image = SixLabors.ImageSharp.Image.Load<Rgba32>(filename);
}
/// <summary>
/// Initializes a new instance of the <see cref="CanvasImage"/> class.
/// </summary>
/// <param name="data">Buffer containing an image.</param>
public CanvasImage(ReadOnlySpan<byte> data)
{
Image = SixLabors.ImageSharp.Image.Load<Rgba32>(data);
}
/// <summary>
/// Initializes a new instance of the <see cref="CanvasImage"/> class.
/// </summary>
/// <param name="data">Stream containing an image.</param>
public CanvasImage(Stream data)
{
Image = SixLabors.ImageSharp.Image.Load<Rgba32>(data);
}
/// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth)
{
if (PixelWidth < 0)
{
throw new InvalidOperationException("Pixel width must be greater than zero.");
}
var width = MaxWidth ?? Width;
if (maxWidth < width * PixelWidth)
{
return new Measurement(maxWidth, maxWidth);
}
return new Measurement(width * PixelWidth, width * PixelWidth);
}
/// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
var image = Image;
var width = Width;
var height = Height;
// Got a max width?
if (MaxWidth != null)
{
height = (int)(height * ((float)MaxWidth.Value) / Width);
width = MaxWidth.Value;
}
// Exceed the max width when we take pixel width into account?
if (width * PixelWidth > maxWidth)
{
height = (int)(height * (maxWidth / (float)(width * PixelWidth)));
width = maxWidth / PixelWidth;
}
// Need to rescale the pixel buffer?
if (width != Width || height != Height)
{
var resampler = Resampler ?? _defaultResampler;
image = image.Clone(); // Clone the original image
image.Mutate(i => i.Resize(width, height, resampler));
}
var canvas = new Canvas(width, height)
{
MaxWidth = MaxWidth,
PixelWidth = PixelWidth,
Scale = false,
};
for (var y = 0; y < image.Height; y++)
{
for (var x = 0; x < image.Width; x++)
{
if (image[x, y].A == 0)
{
continue;
}
canvas.SetPixel(x, y, new Color(
image[x, y].R, image[x, y].G, image[x, y].B));
}
}
return ((IRenderable)canvas).Render(context, maxWidth);
}
Image = SixLabors.ImageSharp.Image.Load<Rgba32>(filename);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="CanvasImage"/> class.
/// </summary>
/// <param name="data">Buffer containing an image.</param>
public CanvasImage(ReadOnlySpan<byte> data)
{
Image = SixLabors.ImageSharp.Image.Load<Rgba32>(data);
}
/// <summary>
/// Initializes a new instance of the <see cref="CanvasImage"/> class.
/// </summary>
/// <param name="data">Stream containing an image.</param>
public CanvasImage(Stream data)
{
Image = SixLabors.ImageSharp.Image.Load<Rgba32>(data);
}
/// <inheritdoc/>
protected override Measurement Measure(RenderContext context, int maxWidth)
{
if (PixelWidth < 0)
{
throw new InvalidOperationException("Pixel width must be greater than zero.");
}
var width = MaxWidth ?? Width;
if (maxWidth < width * PixelWidth)
{
return new Measurement(maxWidth, maxWidth);
}
return new Measurement(width * PixelWidth, width * PixelWidth);
}
/// <inheritdoc/>
protected override IEnumerable<Segment> Render(RenderContext context, int maxWidth)
{
var image = Image;
var width = Width;
var height = Height;
// Got a max width?
if (MaxWidth != null)
{
height = (int)(height * ((float)MaxWidth.Value) / Width);
width = MaxWidth.Value;
}
// Exceed the max width when we take pixel width into account?
if (width * PixelWidth > maxWidth)
{
height = (int)(height * (maxWidth / (float)(width * PixelWidth)));
width = maxWidth / PixelWidth;
}
// Need to rescale the pixel buffer?
if (width != Width || height != Height)
{
var resampler = Resampler ?? _defaultResampler;
image = image.Clone(); // Clone the original image
image.Mutate(i => i.Resize(width, height, resampler));
}
var canvas = new Canvas(width, height)
{
MaxWidth = MaxWidth,
PixelWidth = PixelWidth,
Scale = false,
};
for (var y = 0; y < image.Height; y++)
{
for (var x = 0; x < image.Width; x++)
{
if (image[x, y].A == 0)
{
continue;
}
canvas.SetPixel(x, y, new Color(
image[x, y].R, image[x, y].G, image[x, y].B));
}
}
return ((IRenderable)canvas).Render(context, maxWidth);
}
}

View File

@ -1,135 +1,134 @@
using System;
using SixLabors.ImageSharp.Processing;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// Contains extension methods for <see cref="CanvasImage"/>.
/// </summary>
public static class CanvasImageExtensions
{
/// <summary>
/// Contains extension methods for <see cref="CanvasImage"/>.
/// Sets the maximum width of the rendered image.
/// </summary>
public static class CanvasImageExtensions
/// <param name="image">The canvas image.</param>
/// <param name="maxWidth">The maximum width.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage MaxWidth(this CanvasImage image, int? maxWidth)
{
/// <summary>
/// Sets the maximum width of the rendered image.
/// </summary>
/// <param name="image">The canvas image.</param>
/// <param name="maxWidth">The maximum width.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage MaxWidth(this CanvasImage image, int? maxWidth)
if (image is null)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
image.MaxWidth = maxWidth;
return image;
throw new ArgumentNullException(nameof(image));
}
/// <summary>
/// Disables the maximum width of the rendered image.
/// </summary>
/// <param name="image">The canvas image.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage NoMaxWidth(this CanvasImage image)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
image.MaxWidth = null;
return image;
}
/// <summary>
/// Sets the pixel width.
/// </summary>
/// <param name="image">The canvas image.</param>
/// <param name="width">The pixel width.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage PixelWidth(this CanvasImage image, int width)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
image.PixelWidth = width;
return image;
}
/// <summary>
/// Mutates the underlying image.
/// </summary>
/// <param name="image">The canvas image.</param>
/// <param name="action">The action that mutates the underlying image.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage Mutate(this CanvasImage image, Action<IImageProcessingContext> action)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
if (action is null)
{
throw new ArgumentNullException(nameof(action));
}
image.Image.Mutate(action);
return image;
}
/// <summary>
/// Uses a bicubic sampler that implements the bicubic kernel algorithm W(x).
/// </summary>
/// <param name="image">The canvas image.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage BicubicResampler(this CanvasImage image)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
image.Resampler = KnownResamplers.Bicubic;
return image;
}
/// <summary>
/// Uses a bilinear sampler. This interpolation algorithm
/// can be used where perfect image transformation with pixel matching is impossible,
/// so that one can calculate and assign appropriate intensity values to pixels.
/// </summary>
/// <param name="image">The canvas image.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage BilinearResampler(this CanvasImage image)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
image.Resampler = KnownResamplers.Triangle;
return image;
}
/// <summary>
/// Uses a Nearest-Neighbour sampler that implements the nearest neighbor algorithm.
/// This uses a very fast, unscaled filter which will select the closest pixel to
/// the new pixels position.
/// </summary>
/// <param name="image">The canvas image.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage NearestNeighborResampler(this CanvasImage image)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
image.Resampler = KnownResamplers.NearestNeighbor;
return image;
}
image.MaxWidth = maxWidth;
return image;
}
}
/// <summary>
/// Disables the maximum width of the rendered image.
/// </summary>
/// <param name="image">The canvas image.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage NoMaxWidth(this CanvasImage image)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
image.MaxWidth = null;
return image;
}
/// <summary>
/// Sets the pixel width.
/// </summary>
/// <param name="image">The canvas image.</param>
/// <param name="width">The pixel width.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage PixelWidth(this CanvasImage image, int width)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
image.PixelWidth = width;
return image;
}
/// <summary>
/// Mutates the underlying image.
/// </summary>
/// <param name="image">The canvas image.</param>
/// <param name="action">The action that mutates the underlying image.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage Mutate(this CanvasImage image, Action<IImageProcessingContext> action)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
if (action is null)
{
throw new ArgumentNullException(nameof(action));
}
image.Image.Mutate(action);
return image;
}
/// <summary>
/// Uses a bicubic sampler that implements the bicubic kernel algorithm W(x).
/// </summary>
/// <param name="image">The canvas image.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage BicubicResampler(this CanvasImage image)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
image.Resampler = KnownResamplers.Bicubic;
return image;
}
/// <summary>
/// Uses a bilinear sampler. This interpolation algorithm
/// can be used where perfect image transformation with pixel matching is impossible,
/// so that one can calculate and assign appropriate intensity values to pixels.
/// </summary>
/// <param name="image">The canvas image.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage BilinearResampler(this CanvasImage image)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
image.Resampler = KnownResamplers.Triangle;
return image;
}
/// <summary>
/// Uses a Nearest-Neighbour sampler that implements the nearest neighbor algorithm.
/// This uses a very fast, unscaled filter which will select the closest pixel to
/// the new pixels position.
/// </summary>
/// <param name="image">The canvas image.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static CanvasImage NearestNeighborResampler(this CanvasImage image)
{
if (image is null)
{
throw new ArgumentNullException(nameof(image));
}
image.Resampler = KnownResamplers.NearestNeighbor;
return image;
}
}

View File

@ -1,28 +1,27 @@
using System;
using Spectre.Console.Cli;
namespace Spectre.Console.Testing
namespace Spectre.Console.Testing;
/// <summary>
/// A <see cref="ICommandInterceptor"/> that triggers a callback when invoked.
/// </summary>
public sealed class CallbackCommandInterceptor : ICommandInterceptor
{
private readonly Action<CommandContext, CommandSettings> _callback;
/// <summary>
/// A <see cref="ICommandInterceptor"/> that triggers a callback when invoked.
/// Initializes a new instance of the <see cref="CallbackCommandInterceptor"/> class.
/// </summary>
public sealed class CallbackCommandInterceptor : ICommandInterceptor
/// <param name="callback">The callback to call when the interceptor is invoked.</param>
public CallbackCommandInterceptor(Action<CommandContext, CommandSettings> callback)
{
private readonly Action<CommandContext, CommandSettings> _callback;
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
}
/// <summary>
/// Initializes a new instance of the <see cref="CallbackCommandInterceptor"/> class.
/// </summary>
/// <param name="callback">The callback to call when the interceptor is invoked.</param>
public CallbackCommandInterceptor(Action<CommandContext, CommandSettings> callback)
{
_callback = callback ?? throw new ArgumentNullException(nameof(callback));
}
/// <inheritdoc/>
public void Intercept(CommandContext context, CommandSettings settings)
{
_callback(context, settings);
}
/// <inheritdoc/>
public void Intercept(CommandContext context, CommandSettings settings)
{
_callback(context, settings);
}
}

View File

@ -1,29 +1,28 @@
using System;
using Spectre.Console.Cli;
namespace Spectre.Console.Testing
namespace Spectre.Console.Testing;
/// <summary>
/// Represents a <see cref="CommandApp"/> runtime failure.
/// </summary>
public sealed class CommandAppFailure
{
/// <summary>
/// Represents a <see cref="CommandApp"/> runtime failure.
/// Gets the exception that was thrown.
/// </summary>
public sealed class CommandAppFailure
public Exception Exception { get; }
/// <summary>
/// Gets the console output.
/// </summary>
public string Output { get; }
internal CommandAppFailure(Exception exception, string output)
{
/// <summary>
/// Gets the exception that was thrown.
/// </summary>
public Exception Exception { get; }
/// <summary>
/// Gets the console output.
/// </summary>
public string Output { get; }
internal CommandAppFailure(Exception exception, string output)
{
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
Output = output.NormalizeLineEndings()
.TrimLines()
.Trim();
}
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
Output = output.NormalizeLineEndings()
.TrimLines()
.Trim();
}
}

View File

@ -1,43 +1,42 @@
using Spectre.Console.Cli;
namespace Spectre.Console.Testing
namespace Spectre.Console.Testing;
/// <summary>
/// Represents the result of a completed <see cref="CommandApp"/> run.
/// </summary>
public sealed class CommandAppResult
{
/// <summary>
/// Represents the result of a completed <see cref="CommandApp"/> run.
/// Gets the exit code.
/// </summary>
public sealed class CommandAppResult
public int ExitCode { get; }
/// <summary>
/// Gets the console output.
/// </summary>
public string Output { get; }
/// <summary>
/// Gets the command context.
/// </summary>
public CommandContext? Context { get; }
/// <summary>
/// Gets the command settings.
/// </summary>
public CommandSettings? Settings { get; }
internal CommandAppResult(int exitCode, string output, CommandContext? context, CommandSettings? settings)
{
/// <summary>
/// Gets the exit code.
/// </summary>
public int ExitCode { get; }
ExitCode = exitCode;
Output = output ?? string.Empty;
Context = context;
Settings = settings;
/// <summary>
/// Gets the console output.
/// </summary>
public string Output { get; }
/// <summary>
/// Gets the command context.
/// </summary>
public CommandContext? Context { get; }
/// <summary>
/// Gets the command settings.
/// </summary>
public CommandSettings? Settings { get; }
internal CommandAppResult(int exitCode, string output, CommandContext? context, CommandSettings? settings)
{
ExitCode = exitCode;
Output = output ?? string.Empty;
Context = context;
Settings = settings;
Output = Output
.NormalizeLineEndings()
.TrimLines()
.Trim();
}
Output = Output
.NormalizeLineEndings()
.TrimLines()
.Trim();
}
}

View File

@ -1,135 +1,134 @@
using System;
using Spectre.Console.Cli;
namespace Spectre.Console.Testing
namespace Spectre.Console.Testing;
/// <summary>
/// A <see cref="CommandApp"/> test harness.
/// </summary>
public sealed class CommandAppTester
{
private Action<CommandApp>? _appConfiguration;
private Action<IConfigurator>? _configuration;
/// <summary>
/// A <see cref="CommandApp"/> test harness.
/// Initializes a new instance of the <see cref="CommandAppTester"/> class.
/// </summary>
public sealed class CommandAppTester
/// <param name="registrar">The registrar.</param>
public CommandAppTester(ITypeRegistrar? registrar = null)
{
private Action<CommandApp>? _appConfiguration;
private Action<IConfigurator>? _configuration;
Registrar = registrar;
}
/// <summary>
/// Initializes a new instance of the <see cref="CommandAppTester"/> class.
/// </summary>
/// <param name="registrar">The registrar.</param>
public CommandAppTester(ITypeRegistrar? registrar = null)
/// <summary>
/// Gets or sets the Registrar to use in the CommandApp.
/// </summary>
public ITypeRegistrar? Registrar { get; set; }
/// <summary>
/// Sets the default command.
/// </summary>
/// <typeparam name="T">The default command type.</typeparam>
public void SetDefaultCommand<T>()
where T : class, ICommand
{
_appConfiguration = (app) => app.SetDefaultCommand<T>();
}
/// <summary>
/// Configures the command application.
/// </summary>
/// <param name="action">The configuration action.</param>
public void Configure(Action<IConfigurator> action)
{
if (_configuration != null)
{
Registrar = registrar;
throw new InvalidOperationException("The command app harnest have already been configured.");
}
/// <summary>
/// Gets or sets the Registrar to use in the CommandApp.
/// </summary>
public ITypeRegistrar? Registrar { get; set; }
_configuration = action;
}
/// <summary>
/// Sets the default command.
/// </summary>
/// <typeparam name="T">The default command type.</typeparam>
public void SetDefaultCommand<T>()
where T : class, ICommand
/// <summary>
/// Runs the command application and expects an exception of a specific type to be thrown.
/// </summary>
/// <typeparam name="T">The expected exception type.</typeparam>
/// <param name="args">The arguments.</param>
/// <returns>The information about the failure.</returns>
public CommandAppFailure RunAndCatch<T>(params string[] args)
where T : Exception
{
var console = new TestConsole().Width(int.MaxValue);
try
{
_appConfiguration = (app) => app.SetDefaultCommand<T>();
Run(args, console, c => c.PropagateExceptions());
throw new InvalidOperationException("Expected an exception to be thrown, but there was none.");
}
/// <summary>
/// Configures the command application.
/// </summary>
/// <param name="action">The configuration action.</param>
public void Configure(Action<IConfigurator> action)
catch (T ex)
{
if (_configuration != null)
if (ex is CommandAppException commandAppException && commandAppException.Pretty != null)
{
throw new InvalidOperationException("The command app harnest have already been configured.");
console.Write(commandAppException.Pretty);
}
else
{
console.WriteLine(ex.Message);
}
_configuration = action;
return new CommandAppFailure(ex, console.Output);
}
/// <summary>
/// Runs the command application and expects an exception of a specific type to be thrown.
/// </summary>
/// <typeparam name="T">The expected exception type.</typeparam>
/// <param name="args">The arguments.</param>
/// <returns>The information about the failure.</returns>
public CommandAppFailure RunAndCatch<T>(params string[] args)
where T : Exception
catch (Exception ex)
{
var console = new TestConsole().Width(int.MaxValue);
try
{
Run(args, console, c => c.PropagateExceptions());
throw new InvalidOperationException("Expected an exception to be thrown, but there was none.");
}
catch (T ex)
{
if (ex is CommandAppException commandAppException && commandAppException.Pretty != null)
{
console.Write(commandAppException.Pretty);
}
else
{
console.WriteLine(ex.Message);
}
return new CommandAppFailure(ex, console.Output);
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Expected an exception of type '{typeof(T).FullName}' to be thrown, "
+ $"but received {ex.GetType().FullName}.");
}
}
/// <summary>
/// Runs the command application.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The result.</returns>
public CommandAppResult Run(params string[] args)
{
var console = new TestConsole().Width(int.MaxValue);
return Run(args, console);
}
private CommandAppResult Run(string[] args, TestConsole console, Action<IConfigurator>? config = null)
{
CommandContext? context = null;
CommandSettings? settings = null;
var app = new CommandApp(Registrar);
_appConfiguration?.Invoke(app);
if (_configuration != null)
{
app.Configure(_configuration);
}
if (config != null)
{
app.Configure(config);
}
app.Configure(c => c.ConfigureConsole(console));
app.Configure(c => c.SetInterceptor(new CallbackCommandInterceptor((ctx, s) =>
{
context = ctx;
settings = s;
})));
var result = app.Run(args);
var output = console.Output
.NormalizeLineEndings()
.TrimLines()
.Trim();
return new CommandAppResult(result, output, context, settings);
throw new InvalidOperationException(
$"Expected an exception of type '{typeof(T).FullName}' to be thrown, "
+ $"but received {ex.GetType().FullName}.");
}
}
/// <summary>
/// Runs the command application.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The result.</returns>
public CommandAppResult Run(params string[] args)
{
var console = new TestConsole().Width(int.MaxValue);
return Run(args, console);
}
private CommandAppResult Run(string[] args, TestConsole console, Action<IConfigurator>? config = null)
{
CommandContext? context = null;
CommandSettings? settings = null;
var app = new CommandApp(Registrar);
_appConfiguration?.Invoke(app);
if (_configuration != null)
{
app.Configure(_configuration);
}
if (config != null)
{
app.Configure(config);
}
app.Configure(c => c.ConfigureConsole(console));
app.Configure(c => c.SetInterceptor(new CallbackCommandInterceptor((ctx, s) =>
{
context = ctx;
settings = s;
})));
var result = app.Run(args);
var output = console.Output
.NormalizeLineEndings()
.TrimLines()
.Trim();
return new CommandAppResult(result, output, context, settings);
}
}

View File

@ -1,191 +1,190 @@
using System;
using Spectre.Console.Cli;
namespace Spectre.Console.Testing
namespace Spectre.Console.Testing;
/// <summary>
/// This is a utility class for implementors of
/// <see cref="ITypeRegistrar"/> and corresponding <see cref="ITypeResolver"/>.
/// </summary>
public sealed class TypeRegistrarBaseTests
{
private readonly Func<ITypeRegistrar> _registrarFactory;
/// <summary>
/// This is a utility class for implementors of
/// <see cref="ITypeRegistrar"/> and corresponding <see cref="ITypeResolver"/>.
/// Initializes a new instance of the <see cref="TypeRegistrarBaseTests"/> class.
/// </summary>
public sealed class TypeRegistrarBaseTests
/// <param name="registrarFactory">The factory to create a new, clean <see cref="ITypeRegistrar"/> to be used for each test.</param>
public TypeRegistrarBaseTests(Func<ITypeRegistrar> registrarFactory)
{
private readonly Func<ITypeRegistrar> _registrarFactory;
_registrarFactory = registrarFactory;
}
/// <summary>
/// Initializes a new instance of the <see cref="TypeRegistrarBaseTests"/> class.
/// </summary>
/// <param name="registrarFactory">The factory to create a new, clean <see cref="ITypeRegistrar"/> to be used for each test.</param>
public TypeRegistrarBaseTests(Func<ITypeRegistrar> registrarFactory)
/// <summary>
/// Runs all tests.
/// </summary>
/// <exception cref="TestFailedException">This exception is raised, if a test fails.</exception>
public void RunAllTests()
{
var testCases = new Action<ITypeRegistrar>[]
{
_registrarFactory = registrarFactory;
}
/// <summary>
/// Runs all tests.
/// </summary>
/// <exception cref="TestFailedException">This exception is raised, if a test fails.</exception>
public void RunAllTests()
{
var testCases = new Action<ITypeRegistrar>[]
{
RegistrationsCanBeResolved,
InstanceRegistrationsCanBeResolved,
LazyRegistrationsCanBeResolved,
ResolvingNotRegisteredServiceReturnsNull,
ResolvingNullTypeReturnsNull,
};
};
foreach (var test in testCases)
{
test(_registrarFactory());
}
}
private static void ResolvingNullTypeReturnsNull(ITypeRegistrar registrar)
foreach (var test in testCases)
{
// Given no registration
var resolver = registrar.Build();
try
{
// When
var actual = resolver.Resolve(null);
// Then
if (actual != null)
{
throw new TestFailedException(
$"Expected the resolver to resolve null, since null was requested as the service type. Actually resolved {actual.GetType().Name}.");
}
}
catch (Exception ex)
{
throw new TestFailedException(
$"Expected the resolver not to throw, but caught {ex.GetType().Name}.", ex);
}
}
private static void ResolvingNotRegisteredServiceReturnsNull(ITypeRegistrar registrar)
{
// Given no registration
var resolver = registrar.Build();
try
{
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (actual != null)
{
throw new TestFailedException(
$"Expected the resolver to resolve null, since no service was registered. Actually resolved {actual.GetType().Name}.");
}
}
catch (Exception ex)
{
throw new TestFailedException(
$"Expected the resolver not to throw, but caught {ex.GetType().Name}.", ex);
}
}
private static void RegistrationsCanBeResolved(ITypeRegistrar registrar)
{
// Given
registrar.Register(typeof(IMockService), typeof(MockService));
var resolver = registrar.Build();
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (actual == null)
{
throw new TestFailedException(
$"Expected the resolver to resolve an instance of {nameof(MockService)}. Actually resolved null.");
}
if (actual is not MockService)
{
throw new TestFailedException(
$"Expected the resolver to resolve an instance of {nameof(MockService)}. Actually resolved {actual.GetType().Name}.");
}
}
private static void InstanceRegistrationsCanBeResolved(ITypeRegistrar registrar)
{
// Given
var instance = new MockService();
registrar.RegisterInstance(typeof(IMockService), instance);
var resolver = registrar.Build();
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (!ReferenceEquals(actual, instance))
{
throw new TestFailedException(
"Expected the resolver to resolve exactly the registered instance.");
}
}
private static void LazyRegistrationsCanBeResolved(ITypeRegistrar registrar)
{
// Given
var instance = new MockService();
var factoryCalled = false;
registrar.RegisterLazy(typeof(IMockService), () =>
{
factoryCalled = true;
return instance;
});
var resolver = registrar.Build();
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (!factoryCalled)
{
throw new TestFailedException(
"Expected the factory to be called, to resolve the lazy registration.");
}
if (!ReferenceEquals(actual, instance))
{
throw new TestFailedException(
"Expected the resolver to return exactly the result of the lazy-registered factory.");
}
}
/// <summary>
/// internal use only.
/// </summary>
private interface IMockService
{
}
private class MockService : IMockService
{
}
/// <summary>
/// Exception, to be raised when a test fails.
/// </summary>
public sealed class TestFailedException : Exception
{
/// <inheritdoc cref="Exception" />
public TestFailedException(string message)
: base(message)
{
}
/// <inheritdoc cref="Exception" />
public TestFailedException(string message, Exception inner)
: base(message, inner)
{
}
test(_registrarFactory());
}
}
}
private static void ResolvingNullTypeReturnsNull(ITypeRegistrar registrar)
{
// Given no registration
var resolver = registrar.Build();
try
{
// When
var actual = resolver.Resolve(null);
// Then
if (actual != null)
{
throw new TestFailedException(
$"Expected the resolver to resolve null, since null was requested as the service type. Actually resolved {actual.GetType().Name}.");
}
}
catch (Exception ex)
{
throw new TestFailedException(
$"Expected the resolver not to throw, but caught {ex.GetType().Name}.", ex);
}
}
private static void ResolvingNotRegisteredServiceReturnsNull(ITypeRegistrar registrar)
{
// Given no registration
var resolver = registrar.Build();
try
{
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (actual != null)
{
throw new TestFailedException(
$"Expected the resolver to resolve null, since no service was registered. Actually resolved {actual.GetType().Name}.");
}
}
catch (Exception ex)
{
throw new TestFailedException(
$"Expected the resolver not to throw, but caught {ex.GetType().Name}.", ex);
}
}
private static void RegistrationsCanBeResolved(ITypeRegistrar registrar)
{
// Given
registrar.Register(typeof(IMockService), typeof(MockService));
var resolver = registrar.Build();
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (actual == null)
{
throw new TestFailedException(
$"Expected the resolver to resolve an instance of {nameof(MockService)}. Actually resolved null.");
}
if (actual is not MockService)
{
throw new TestFailedException(
$"Expected the resolver to resolve an instance of {nameof(MockService)}. Actually resolved {actual.GetType().Name}.");
}
}
private static void InstanceRegistrationsCanBeResolved(ITypeRegistrar registrar)
{
// Given
var instance = new MockService();
registrar.RegisterInstance(typeof(IMockService), instance);
var resolver = registrar.Build();
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (!ReferenceEquals(actual, instance))
{
throw new TestFailedException(
"Expected the resolver to resolve exactly the registered instance.");
}
}
private static void LazyRegistrationsCanBeResolved(ITypeRegistrar registrar)
{
// Given
var instance = new MockService();
var factoryCalled = false;
registrar.RegisterLazy(typeof(IMockService), () =>
{
factoryCalled = true;
return instance;
});
var resolver = registrar.Build();
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (!factoryCalled)
{
throw new TestFailedException(
"Expected the factory to be called, to resolve the lazy registration.");
}
if (!ReferenceEquals(actual, instance))
{
throw new TestFailedException(
"Expected the resolver to return exactly the result of the lazy-registered factory.");
}
}
/// <summary>
/// internal use only.
/// </summary>
private interface IMockService
{
}
private class MockService : IMockService
{
}
/// <summary>
/// Exception, to be raised when a test fails.
/// </summary>
public sealed class TestFailedException : Exception
{
/// <inheritdoc cref="Exception" />
public TestFailedException(string message)
: base(message)
{
}
/// <inheritdoc cref="Exception" />
public TestFailedException(string message, Exception inner)
: base(message, inner)
{
}
}
}

View File

@ -1,47 +1,46 @@
using System.Collections.Generic;
namespace Spectre.Console.Testing
namespace Spectre.Console.Testing;
/// <summary>
/// Contains extensions for <see cref="string"/>.
/// </summary>
public static class StringExtensions
{
/// <summary>
/// Contains extensions for <see cref="string"/>.
/// Returns a new string with all lines trimmed of trailing whitespace.
/// </summary>
public static class StringExtensions
/// <param name="value">The string to trim.</param>
/// <returns>A new string with all lines trimmed of trailing whitespace.</returns>
public static string TrimLines(this string value)
{
/// <summary>
/// Returns a new string with all lines trimmed of trailing whitespace.
/// </summary>
/// <param name="value">The string to trim.</param>
/// <returns>A new string with all lines trimmed of trailing whitespace.</returns>
public static string TrimLines(this string value)
if (value is null)
{
if (value is null)
{
return string.Empty;
}
var result = new List<string>();
foreach (var line in value.NormalizeLineEndings().Split(new[] { '\n' }))
{
result.Add(line.TrimEnd());
}
return string.Join("\n", result);
}
/// <summary>
/// Returns a new string with normalized line endings.
/// </summary>
/// <param name="value">The string to normalize line endings for.</param>
/// <returns>A new string with normalized line endings.</returns>
public static string NormalizeLineEndings(this string value)
{
if (value != null)
{
value = value.Replace("\r\n", "\n");
return value.Replace("\r", string.Empty);
}
return string.Empty;
}
var result = new List<string>();
foreach (var line in value.NormalizeLineEndings().Split(new[] { '\n' }))
{
result.Add(line.TrimEnd());
}
return string.Join("\n", result);
}
/// <summary>
/// Returns a new string with normalized line endings.
/// </summary>
/// <param name="value">The string to normalize line endings for.</param>
/// <returns>A new string with normalized line endings.</returns>
public static string NormalizeLineEndings(this string value)
{
if (value != null)
{
value = value.Replace("\r\n", "\n");
return value.Replace("\r", string.Empty);
}
return string.Empty;
}
}

View File

@ -1,25 +1,24 @@
namespace Spectre.Console.Testing
namespace Spectre.Console.Testing;
/// <summary>
/// Contains extensions for <see cref="Style"/>.
/// </summary>
public static class StyleExtensions
{
/// <summary>
/// Contains extensions for <see cref="Style"/>.
/// Sets the foreground or background color of the specified style.
/// </summary>
public static class StyleExtensions
/// <param name="style">The style.</param>
/// <param name="color">The color.</param>
/// <param name="foreground">Whether or not to set the foreground color.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Style SetColor(this Style style, Color color, bool foreground)
{
/// <summary>
/// Sets the foreground or background color of the specified style.
/// </summary>
/// <param name="style">The style.</param>
/// <param name="color">The color.</param>
/// <param name="foreground">Whether or not to set the foreground color.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static Style SetColor(this Style style, Color color, bool foreground)
if (foreground)
{
if (foreground)
{
return style.Foreground(color);
}
return style.Background(color);
return style.Foreground(color);
}
return style.Background(color);
}
}

View File

@ -1,17 +1,16 @@
namespace Spectre.Console.Testing
namespace Spectre.Console.Testing;
internal sealed class NoopCursor : IAnsiConsoleCursor
{
internal sealed class NoopCursor : IAnsiConsoleCursor
public void Move(CursorDirection direction, int steps)
{
public void Move(CursorDirection direction, int steps)
{
}
}
public void SetPosition(int column, int line)
{
}
public void SetPosition(int column, int line)
{
}
public void Show(bool show)
{
}
public void Show(bool show)
{
}
}

View File

@ -1,18 +1,17 @@
using System;
using System.Threading.Tasks;
namespace Spectre.Console.Testing
{
internal sealed class NoopExclusivityMode : IExclusivityMode
{
public T Run<T>(Func<T> func)
{
return func();
}
namespace Spectre.Console.Testing;
public async Task<T> RunAsync<T>(Func<Task<T>> func)
{
return await func().ConfigureAwait(false);
}
internal sealed class NoopExclusivityMode : IExclusivityMode
{
public T Run<T>(Func<T> func)
{
return func();
}
public async Task<T> RunAsync<T>(Func<Task<T>> func)
{
return await func().ConfigureAwait(false);
}
}

View File

@ -1,40 +1,39 @@
using Spectre.Console.Rendering;
namespace Spectre.Console.Testing
namespace Spectre.Console.Testing;
/// <summary>
/// Represents fake capabilities useful in tests.
/// </summary>
public sealed class TestCapabilities : IReadOnlyCapabilities
{
/// <inheritdoc/>
public ColorSystem ColorSystem { get; set; } = ColorSystem.TrueColor;
/// <inheritdoc/>
public bool Ansi { get; set; }
/// <inheritdoc/>
public bool Links { get; set; }
/// <inheritdoc/>
public bool Legacy { get; set; }
/// <inheritdoc/>
public bool IsTerminal { get; set; }
/// <inheritdoc/>
public bool Interactive { get; set; }
/// <inheritdoc/>
public bool Unicode { get; set; }
/// <summary>
/// Represents fake capabilities useful in tests.
/// Creates a <see cref="RenderContext"/> with the same capabilities as this instace.
/// </summary>
public sealed class TestCapabilities : IReadOnlyCapabilities
/// <returns>A <see cref="RenderContext"/> with the same capabilities as this instace.</returns>
public RenderContext CreateRenderContext()
{
/// <inheritdoc/>
public ColorSystem ColorSystem { get; set; } = ColorSystem.TrueColor;
/// <inheritdoc/>
public bool Ansi { get; set; }
/// <inheritdoc/>
public bool Links { get; set; }
/// <inheritdoc/>
public bool Legacy { get; set; }
/// <inheritdoc/>
public bool IsTerminal { get; set; }
/// <inheritdoc/>
public bool Interactive { get; set; }
/// <inheritdoc/>
public bool Unicode { get; set; }
/// <summary>
/// Creates a <see cref="RenderContext"/> with the same capabilities as this instace.
/// </summary>
/// <returns>A <see cref="RenderContext"/> with the same capabilities as this instace.</returns>
public RenderContext CreateRenderContext()
{
return new RenderContext(this);
}
return new RenderContext(this);
}
}

View File

@ -3,120 +3,119 @@ using System.Collections.Generic;
using System.IO;
using Spectre.Console.Rendering;
namespace Spectre.Console.Testing
namespace Spectre.Console.Testing;
/// <summary>
/// A testable console.
/// </summary>
public sealed class TestConsole : IAnsiConsole, IDisposable
{
private readonly IAnsiConsole _console;
private readonly StringWriter _writer;
private IAnsiConsoleCursor? _cursor;
/// <inheritdoc/>
public Profile Profile => _console.Profile;
/// <inheritdoc/>
public IExclusivityMode ExclusivityMode => _console.ExclusivityMode;
/// <summary>
/// A testable console.
/// Gets the console input.
/// </summary>
public sealed class TestConsole : IAnsiConsole, IDisposable
public TestConsoleInput Input { get; }
/// <inheritdoc/>
public RenderPipeline Pipeline => _console.Pipeline;
/// <inheritdoc/>
public IAnsiConsoleCursor Cursor => _cursor ?? _console.Cursor;
/// <inheritdoc/>
IAnsiConsoleInput IAnsiConsole.Input => Input;
/// <summary>
/// Gets the console output.
/// </summary>
public string Output => _writer.ToString();
/// <summary>
/// Gets the console output lines.
/// </summary>
public IReadOnlyList<string> Lines => Output.NormalizeLineEndings().TrimEnd('\n').Split(new char[] { '\n' });
/// <summary>
/// Gets or sets a value indicating whether or not VT/ANSI sequences
/// should be emitted to the console.
/// </summary>
public bool EmitAnsiSequences { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="TestConsole"/> class.
/// </summary>
public TestConsole()
{
private readonly IAnsiConsole _console;
private readonly StringWriter _writer;
private IAnsiConsoleCursor? _cursor;
_writer = new StringWriter();
_cursor = new NoopCursor();
/// <inheritdoc/>
public Profile Profile => _console.Profile;
Input = new TestConsoleInput();
EmitAnsiSequences = false;
/// <inheritdoc/>
public IExclusivityMode ExclusivityMode => _console.ExclusivityMode;
/// <summary>
/// Gets the console input.
/// </summary>
public TestConsoleInput Input { get; }
/// <inheritdoc/>
public RenderPipeline Pipeline => _console.Pipeline;
/// <inheritdoc/>
public IAnsiConsoleCursor Cursor => _cursor ?? _console.Cursor;
/// <inheritdoc/>
IAnsiConsoleInput IAnsiConsole.Input => Input;
/// <summary>
/// Gets the console output.
/// </summary>
public string Output => _writer.ToString();
/// <summary>
/// Gets the console output lines.
/// </summary>
public IReadOnlyList<string> Lines => Output.NormalizeLineEndings().TrimEnd('\n').Split(new char[] { '\n' });
/// <summary>
/// Gets or sets a value indicating whether or not VT/ANSI sequences
/// should be emitted to the console.
/// </summary>
public bool EmitAnsiSequences { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="TestConsole"/> class.
/// </summary>
public TestConsole()
var factory = new AnsiConsoleFactory();
_console = factory.Create(new AnsiConsoleSettings
{
_writer = new StringWriter();
_cursor = new NoopCursor();
Input = new TestConsoleInput();
EmitAnsiSequences = false;
var factory = new AnsiConsoleFactory();
_console = factory.Create(new AnsiConsoleSettings
Ansi = AnsiSupport.Yes,
ColorSystem = (ColorSystemSupport)ColorSystem.TrueColor,
Out = new AnsiConsoleOutput(_writer),
Interactive = InteractionSupport.No,
ExclusivityMode = new NoopExclusivityMode(),
Enrichment = new ProfileEnrichment
{
Ansi = AnsiSupport.Yes,
ColorSystem = (ColorSystemSupport)ColorSystem.TrueColor,
Out = new AnsiConsoleOutput(_writer),
Interactive = InteractionSupport.No,
ExclusivityMode = new NoopExclusivityMode(),
Enrichment = new ProfileEnrichment
UseDefaultEnrichers = false,
},
});
_console.Profile.Width = 80;
_console.Profile.Height = 24;
_console.Profile.Capabilities.Ansi = true;
_console.Profile.Capabilities.Unicode = true;
}
/// <inheritdoc/>
public void Dispose()
{
_writer.Dispose();
}
/// <inheritdoc/>
public void Clear(bool home)
{
_console.Clear(home);
}
/// <inheritdoc/>
public void Write(IRenderable renderable)
{
if (EmitAnsiSequences)
{
_console.Write(renderable);
}
else
{
foreach (var segment in renderable.GetSegments(this))
{
if (segment.IsControlCode)
{
UseDefaultEnrichers = false,
},
});
_console.Profile.Width = 80;
_console.Profile.Height = 24;
_console.Profile.Capabilities.Ansi = true;
_console.Profile.Capabilities.Unicode = true;
}
/// <inheritdoc/>
public void Dispose()
{
_writer.Dispose();
}
/// <inheritdoc/>
public void Clear(bool home)
{
_console.Clear(home);
}
/// <inheritdoc/>
public void Write(IRenderable renderable)
{
if (EmitAnsiSequences)
{
_console.Write(renderable);
}
else
{
foreach (var segment in renderable.GetSegments(this))
{
if (segment.IsControlCode)
{
continue;
}
Profile.Out.Writer.Write(segment.Text);
continue;
}
}
}
internal void SetCursor(IAnsiConsoleCursor? cursor)
{
_cursor = cursor;
Profile.Out.Writer.Write(segment.Text);
}
}
}
internal void SetCursor(IAnsiConsoleCursor? cursor)
{
_cursor = cursor;
}
}

View File

@ -1,67 +1,66 @@
namespace Spectre.Console.Testing
namespace Spectre.Console.Testing;
/// <summary>
/// Contains extensions for <see cref="TestConsole"/>.
/// </summary>
public static class TestConsoleExtensions
{
/// <summary>
/// Contains extensions for <see cref="TestConsole"/>.
/// Sets the console's color system.
/// </summary>
public static class TestConsoleExtensions
/// <param name="console">The console.</param>
/// <param name="colors">The color system to use.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TestConsole Colors(this TestConsole console, ColorSystem colors)
{
/// <summary>
/// Sets the console's color system.
/// </summary>
/// <param name="console">The console.</param>
/// <param name="colors">The color system to use.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TestConsole Colors(this TestConsole console, ColorSystem colors)
{
console.Profile.Capabilities.ColorSystem = colors;
return console;
}
console.Profile.Capabilities.ColorSystem = colors;
return console;
}
/// <summary>
/// Sets whether or not ANSI is supported.
/// </summary>
/// <param name="console">The console.</param>
/// <param name="enable">Whether or not VT/ANSI control codes are supported.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TestConsole SupportsAnsi(this TestConsole console, bool enable)
{
console.Profile.Capabilities.Ansi = enable;
return console;
}
/// <summary>
/// Sets whether or not ANSI is supported.
/// </summary>
/// <param name="console">The console.</param>
/// <param name="enable">Whether or not VT/ANSI control codes are supported.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TestConsole SupportsAnsi(this TestConsole console, bool enable)
{
console.Profile.Capabilities.Ansi = enable;
return console;
}
/// <summary>
/// Makes the console interactive.
/// </summary>
/// <param name="console">The console.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TestConsole Interactive(this TestConsole console)
{
console.Profile.Capabilities.Interactive = true;
return console;
}
/// <summary>
/// Makes the console interactive.
/// </summary>
/// <param name="console">The console.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TestConsole Interactive(this TestConsole console)
{
console.Profile.Capabilities.Interactive = true;
return console;
}
/// <summary>
/// Sets the console width.
/// </summary>
/// <param name="console">The console.</param>
/// <param name="width">The console width.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TestConsole Width(this TestConsole console, int width)
{
console.Profile.Width = width;
return console;
}
/// <summary>
/// Sets the console width.
/// </summary>
/// <param name="console">The console.</param>
/// <param name="width">The console width.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TestConsole Width(this TestConsole console, int width)
{
console.Profile.Width = width;
return console;
}
/// <summary>
/// Turns on emitting of VT/ANSI sequences.
/// </summary>
/// <param name="console">The console.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TestConsole EmitAnsiSequences(this TestConsole console)
{
console.SetCursor(null);
console.EmitAnsiSequences = true;
return console;
}
/// <summary>
/// Turns on emitting of VT/ANSI sequences.
/// </summary>
/// <param name="console">The console.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TestConsole EmitAnsiSequences(this TestConsole console)
{
console.SetCursor(null);
console.EmitAnsiSequences = true;
return console;
}
}

View File

@ -3,90 +3,89 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Spectre.Console.Testing
namespace Spectre.Console.Testing;
/// <summary>
/// Represents a testable console input mechanism.
/// </summary>
public sealed class TestConsoleInput : IAnsiConsoleInput
{
private readonly Queue<ConsoleKeyInfo> _input;
/// <summary>
/// Represents a testable console input mechanism.
/// Initializes a new instance of the <see cref="TestConsoleInput"/> class.
/// </summary>
public sealed class TestConsoleInput : IAnsiConsoleInput
public TestConsoleInput()
{
private readonly Queue<ConsoleKeyInfo> _input;
_input = new Queue<ConsoleKeyInfo>();
}
/// <summary>
/// Initializes a new instance of the <see cref="TestConsoleInput"/> class.
/// </summary>
public TestConsoleInput()
/// <summary>
/// Pushes the specified text to the input queue.
/// </summary>
/// <param name="input">The input string.</param>
public void PushText(string input)
{
if (input is null)
{
_input = new Queue<ConsoleKeyInfo>();
throw new ArgumentNullException(nameof(input));
}
/// <summary>
/// Pushes the specified text to the input queue.
/// </summary>
/// <param name="input">The input string.</param>
public void PushText(string input)
foreach (var character in input)
{
if (input is null)
{
throw new ArgumentNullException(nameof(input));
}
foreach (var character in input)
{
PushCharacter(character);
}
}
/// <summary>
/// Pushes the specified text followed by 'Enter' to the input queue.
/// </summary>
/// <param name="input">The input.</param>
public void PushTextWithEnter(string input)
{
PushText(input);
PushKey(ConsoleKey.Enter);
}
/// <summary>
/// Pushes the specified character to the input queue.
/// </summary>
/// <param name="input">The input.</param>
public void PushCharacter(char input)
{
var control = char.IsUpper(input);
_input.Enqueue(new ConsoleKeyInfo(input, (ConsoleKey)input, false, false, control));
}
/// <summary>
/// Pushes the specified key to the input queue.
/// </summary>
/// <param name="input">The input.</param>
public void PushKey(ConsoleKey input)
{
_input.Enqueue(new ConsoleKeyInfo((char)input, input, false, false, false));
}
/// <inheritdoc/>
public bool IsKeyAvailable()
{
return _input.Count > 0;
}
/// <inheritdoc/>
public ConsoleKeyInfo? ReadKey(bool intercept)
{
if (_input.Count == 0)
{
throw new InvalidOperationException("No input available.");
}
return _input.Dequeue();
}
/// <inheritdoc/>
public Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
return Task.FromResult(ReadKey(intercept));
PushCharacter(character);
}
}
/// <summary>
/// Pushes the specified text followed by 'Enter' to the input queue.
/// </summary>
/// <param name="input">The input.</param>
public void PushTextWithEnter(string input)
{
PushText(input);
PushKey(ConsoleKey.Enter);
}
/// <summary>
/// Pushes the specified character to the input queue.
/// </summary>
/// <param name="input">The input.</param>
public void PushCharacter(char input)
{
var control = char.IsUpper(input);
_input.Enqueue(new ConsoleKeyInfo(input, (ConsoleKey)input, false, false, control));
}
/// <summary>
/// Pushes the specified key to the input queue.
/// </summary>
/// <param name="input">The input.</param>
public void PushKey(ConsoleKey input)
{
_input.Enqueue(new ConsoleKeyInfo((char)input, input, false, false, false));
}
/// <inheritdoc/>
public bool IsKeyAvailable()
{
return _input.Count > 0;
}
/// <inheritdoc/>
public ConsoleKeyInfo? ReadKey(bool intercept)
{
if (_input.Count == 0)
{
throw new InvalidOperationException("No input available.");
}
return _input.Dequeue();
}
/// <inheritdoc/>
public Task<ConsoleKeyInfo?> ReadKeyAsync(bool intercept, CancellationToken cancellationToken)
{
return Task.FromResult(ReadKey(intercept));
}
}

View File

@ -1,30 +1,29 @@
using System;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// Writes an exception to the console.
/// </summary>
public static partial class AnsiConsole
/// <param name="exception">The exception to write to the console.</param>
/// <param name="format">The exception format options.</param>
public static void WriteException(Exception exception, ExceptionFormats format = ExceptionFormats.Default)
{
/// <summary>
/// Writes an exception to the console.
/// </summary>
/// <param name="exception">The exception to write to the console.</param>
/// <param name="format">The exception format options.</param>
public static void WriteException(Exception exception, ExceptionFormats format = ExceptionFormats.Default)
{
Console.WriteException(exception, format);
}
/// <summary>
/// Writes an exception to the console.
/// </summary>
/// <param name="exception">The exception to write to the console.</param>
/// <param name="settings">The exception settings.</param>
public static void WriteException(Exception exception, ExceptionSettings settings)
{
Console.WriteException(exception, settings);
}
Console.WriteException(exception, format);
}
}
/// <summary>
/// Writes an exception to the console.
/// </summary>
/// <param name="exception">The exception to write to the console.</param>
/// <param name="settings">The exception settings.</param>
public static void WriteException(Exception exception, ExceptionSettings settings)
{
Console.WriteException(exception, settings);
}
}

View File

@ -1,20 +1,19 @@
using Spectre.Console.Rendering;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// Creates a new <see cref="LiveDisplay"/> instance.
/// </summary>
public static partial class AnsiConsole
/// <param name="target">The target renderable to update.</param>
/// <returns>A <see cref="LiveDisplay"/> instance.</returns>
public static LiveDisplay Live(IRenderable target)
{
/// <summary>
/// Creates a new <see cref="LiveDisplay"/> instance.
/// </summary>
/// <param name="target">The target renderable to update.</param>
/// <returns>A <see cref="LiveDisplay"/> instance.</returns>
public static LiveDisplay Live(IRenderable target)
{
return Console.Live(target);
}
return Console.Live(target);
}
}
}

View File

@ -1,70 +1,69 @@
using System;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// Writes the specified markup to the console.
/// </summary>
public static partial class AnsiConsole
/// <param name="value">The value to write.</param>
public static void Markup(string value)
{
/// <summary>
/// Writes the specified markup to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Markup(string value)
{
Console.Markup(value);
}
/// <summary>
/// Writes the specified markup to the console.
/// </summary>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Markup(string format, params object[] args)
{
Console.Markup(format, args);
}
/// <summary>
/// Writes the specified markup to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Markup(IFormatProvider provider, string format, params object[] args)
{
Console.Markup(provider, format, args);
}
/// <summary>
/// Writes the specified markup, followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void MarkupLine(string value)
{
Console.MarkupLine(value);
}
/// <summary>
/// Writes the specified markup, followed by the current line terminator, to the console.
/// </summary>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void MarkupLine(string format, params object[] args)
{
Console.MarkupLine(format, args);
}
/// <summary>
/// Writes the specified markup, followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void MarkupLine(IFormatProvider provider, string format, params object[] args)
{
Console.MarkupLine(provider, format, args);
}
Console.Markup(value);
}
}
/// <summary>
/// Writes the specified markup to the console.
/// </summary>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Markup(string format, params object[] args)
{
Console.Markup(format, args);
}
/// <summary>
/// Writes the specified markup to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Markup(IFormatProvider provider, string format, params object[] args)
{
Console.Markup(provider, format, args);
}
/// <summary>
/// Writes the specified markup, followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void MarkupLine(string value)
{
Console.MarkupLine(value);
}
/// <summary>
/// Writes the specified markup, followed by the current line terminator, to the console.
/// </summary>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void MarkupLine(string format, params object[] args)
{
Console.MarkupLine(format, args);
}
/// <summary>
/// Writes the specified markup, followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void MarkupLine(IFormatProvider provider, string format, params object[] args)
{
Console.MarkupLine(provider, format, args);
}
}

View File

@ -1,26 +1,25 @@
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// Creates a new <see cref="Progress"/> instance.
/// </summary>
public static partial class AnsiConsole
/// <returns>A <see cref="Progress"/> instance.</returns>
public static Progress Progress()
{
/// <summary>
/// Creates a new <see cref="Progress"/> instance.
/// </summary>
/// <returns>A <see cref="Progress"/> instance.</returns>
public static Progress Progress()
{
return Console.Progress();
}
/// <summary>
/// Creates a new <see cref="Status"/> instance.
/// </summary>
/// <returns>A <see cref="Status"/> instance.</returns>
public static Status Status()
{
return Console.Status();
}
return Console.Progress();
}
}
/// <summary>
/// Creates a new <see cref="Status"/> instance.
/// </summary>
/// <returns>A <see cref="Status"/> instance.</returns>
public static Status Status()
{
return Console.Status();
}
}

View File

@ -1,66 +1,65 @@
using System;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// Displays a prompt to the user.
/// </summary>
public static partial class AnsiConsole
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="prompt">The prompt to display.</param>
/// <returns>The prompt input result.</returns>
public static T Prompt<T>(IPrompt<T> prompt)
{
/// <summary>
/// Displays a prompt to the user.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="prompt">The prompt to display.</param>
/// <returns>The prompt input result.</returns>
public static T Prompt<T>(IPrompt<T> prompt)
if (prompt is null)
{
if (prompt is null)
{
throw new ArgumentNullException(nameof(prompt));
}
return prompt.Show(Console);
throw new ArgumentNullException(nameof(prompt));
}
/// <summary>
/// Displays a prompt to the user.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="prompt">The prompt markup text.</param>
/// <returns>The prompt input result.</returns>
public static T Ask<T>(string prompt)
{
return new TextPrompt<T>(prompt).Show(Console);
}
/// <summary>
/// Displays a prompt to the user with a given default.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="prompt">The prompt markup text.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The prompt input result.</returns>
public static T Ask<T>(string prompt, T defaultValue)
{
return new TextPrompt<T>(prompt)
.DefaultValue(defaultValue)
.Show(Console);
}
/// <summary>
/// Displays a prompt with two choices, yes or no.
/// </summary>
/// <param name="prompt">The prompt markup text.</param>
/// <param name="defaultValue">Specifies the default answer.</param>
/// <returns><c>true</c> if the user selected "yes", otherwise <c>false</c>.</returns>
public static bool Confirm(string prompt, bool defaultValue = true)
{
return new ConfirmationPrompt(prompt)
{
DefaultValue = defaultValue,
}
.Show(Console);
}
return prompt.Show(Console);
}
}
/// <summary>
/// Displays a prompt to the user.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="prompt">The prompt markup text.</param>
/// <returns>The prompt input result.</returns>
public static T Ask<T>(string prompt)
{
return new TextPrompt<T>(prompt).Show(Console);
}
/// <summary>
/// Displays a prompt to the user with a given default.
/// </summary>
/// <typeparam name="T">The prompt result type.</typeparam>
/// <param name="prompt">The prompt markup text.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>The prompt input result.</returns>
public static T Ask<T>(string prompt, T defaultValue)
{
return new TextPrompt<T>(prompt)
.DefaultValue(defaultValue)
.Show(Console);
}
/// <summary>
/// Displays a prompt with two choices, yes or no.
/// </summary>
/// <param name="prompt">The prompt markup text.</param>
/// <param name="defaultValue">Specifies the default answer.</param>
/// <returns><c>true</c> if the user selected "yes", otherwise <c>false</c>.</returns>
public static bool Confirm(string prompt, bool defaultValue = true)
{
return new ConfirmationPrompt(prompt)
{
DefaultValue = defaultValue,
}
.Show(Console);
}
}

View File

@ -1,70 +1,69 @@
using System;
using Spectre.Console.Rendering;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// Starts recording the console output.
/// </summary>
public static partial class AnsiConsole
public static void Record()
{
/// <summary>
/// Starts recording the console output.
/// </summary>
public static void Record()
if (_recorder == null)
{
if (_recorder == null)
{
_recorder = new Recorder(Console);
}
}
/// <summary>
/// Exports all recorded console output as text.
/// </summary>
/// <returns>The recorded output as text.</returns>
public static string ExportText()
{
if (_recorder == null)
{
throw new InvalidOperationException("Cannot export text since a recording hasn't been started.");
}
return _recorder.ExportText();
}
/// <summary>
/// Exports all recorded console output as HTML text.
/// </summary>
/// <returns>The recorded output as HTML text.</returns>
public static string ExportHtml()
{
if (_recorder == null)
{
throw new InvalidOperationException("Cannot export HTML since a recording hasn't been started.");
}
return _recorder.ExportHtml();
}
/// <summary>
/// Exports all recorded console output using a custom encoder.
/// </summary>
/// <param name="encoder">The encoder to use.</param>
/// <returns>The recorded output.</returns>
public static string ExportCustom(IAnsiConsoleEncoder encoder)
{
if (_recorder == null)
{
throw new InvalidOperationException("Cannot export HTML since a recording hasn't been started.");
}
if (encoder is null)
{
throw new ArgumentNullException(nameof(encoder));
}
return _recorder.Export(encoder);
_recorder = new Recorder(Console);
}
}
}
/// <summary>
/// Exports all recorded console output as text.
/// </summary>
/// <returns>The recorded output as text.</returns>
public static string ExportText()
{
if (_recorder == null)
{
throw new InvalidOperationException("Cannot export text since a recording hasn't been started.");
}
return _recorder.ExportText();
}
/// <summary>
/// Exports all recorded console output as HTML text.
/// </summary>
/// <returns>The recorded output as HTML text.</returns>
public static string ExportHtml()
{
if (_recorder == null)
{
throw new InvalidOperationException("Cannot export HTML since a recording hasn't been started.");
}
return _recorder.ExportHtml();
}
/// <summary>
/// Exports all recorded console output using a custom encoder.
/// </summary>
/// <param name="encoder">The encoder to use.</param>
/// <returns>The recorded output.</returns>
public static string ExportCustom(IAnsiConsoleEncoder encoder)
{
if (_recorder == null)
{
throw new InvalidOperationException("Cannot export HTML since a recording hasn't been started.");
}
if (encoder is null)
{
throw new ArgumentNullException(nameof(encoder));
}
return _recorder.Export(encoder);
}
}

View File

@ -1,35 +1,34 @@
using System;
using Spectre.Console.Rendering;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// Renders the specified object to the console.
/// </summary>
public static partial class AnsiConsole
/// <param name="renderable">The object to render.</param>
[Obsolete("Consider using AnsiConsole.Write instead.")]
public static void Render(IRenderable renderable)
{
/// <summary>
/// Renders the specified object to the console.
/// </summary>
/// <param name="renderable">The object to render.</param>
[Obsolete("Consider using AnsiConsole.Write instead.")]
public static void Render(IRenderable renderable)
{
Write(renderable);
}
/// <summary>
/// Renders the specified <see cref="IRenderable"/> to the console.
/// </summary>
/// <param name="renderable">The object to render.</param>
public static void Write(IRenderable renderable)
{
if (renderable is null)
{
throw new ArgumentNullException(nameof(renderable));
}
Console.Write(renderable);
}
Write(renderable);
}
}
/// <summary>
/// Renders the specified <see cref="IRenderable"/> to the console.
/// </summary>
/// <param name="renderable">The object to render.</param>
public static void Write(IRenderable renderable)
{
if (renderable is null)
{
throw new ArgumentNullException(nameof(renderable));
}
Console.Write(renderable);
}
}

View File

@ -1,19 +1,18 @@
using System;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// Switches to an alternate screen buffer if the terminal supports it.
/// </summary>
public static partial class AnsiConsole
/// <param name="action">The action to execute within the alternate screen buffer.</param>
public static void AlternateScreen(Action action)
{
/// <summary>
/// Switches to an alternate screen buffer if the terminal supports it.
/// </summary>
/// <param name="action">The action to execute within the alternate screen buffer.</param>
public static void AlternateScreen(Action action)
{
Console.AlternateScreen(action);
}
Console.AlternateScreen(action);
}
}
}

View File

@ -1,63 +1,62 @@
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
internal static Style CurrentStyle { get; private set; } = Style.Plain;
internal static bool Created { get; private set; }
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// Gets or sets the foreground color.
/// </summary>
public static partial class AnsiConsole
public static Color Foreground
{
internal static Style CurrentStyle { get; private set; } = Style.Plain;
internal static bool Created { get; private set; }
/// <summary>
/// Gets or sets the foreground color.
/// </summary>
public static Color Foreground
{
get => CurrentStyle.Foreground;
set => CurrentStyle = CurrentStyle.Foreground(value);
}
/// <summary>
/// Gets or sets the background color.
/// </summary>
public static Color Background
{
get => CurrentStyle.Background;
set => CurrentStyle = CurrentStyle.Background(value);
}
/// <summary>
/// Gets or sets the text decoration.
/// </summary>
public static Decoration Decoration
{
get => CurrentStyle.Decoration;
set => CurrentStyle = CurrentStyle.Decoration(value);
}
/// <summary>
/// Resets colors and text decorations.
/// </summary>
public static void Reset()
{
ResetColors();
ResetDecoration();
}
/// <summary>
/// Resets the current applied text decorations.
/// </summary>
public static void ResetDecoration()
{
Decoration = Decoration.None;
}
/// <summary>
/// Resets the current applied foreground and background colors.
/// </summary>
public static void ResetColors()
{
CurrentStyle = Style.Plain;
}
get => CurrentStyle.Foreground;
set => CurrentStyle = CurrentStyle.Foreground(value);
}
}
/// <summary>
/// Gets or sets the background color.
/// </summary>
public static Color Background
{
get => CurrentStyle.Background;
set => CurrentStyle = CurrentStyle.Background(value);
}
/// <summary>
/// Gets or sets the text decoration.
/// </summary>
public static Decoration Decoration
{
get => CurrentStyle.Decoration;
set => CurrentStyle = CurrentStyle.Decoration(value);
}
/// <summary>
/// Resets colors and text decorations.
/// </summary>
public static void Reset()
{
ResetColors();
ResetDecoration();
}
/// <summary>
/// Resets the current applied text decorations.
/// </summary>
public static void ResetDecoration()
{
Decoration = Decoration.None;
}
/// <summary>
/// Resets the current applied foreground and background colors.
/// </summary>
public static void ResetColors()
{
CurrentStyle = Style.Plain;
}
}

View File

@ -1,253 +1,252 @@
using System;
using System.Globalization;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// Writes the specified string value to the console.
/// </summary>
public static partial class AnsiConsole
/// <param name="value">The value to write.</param>
public static void Write(string value)
{
/// <summary>
/// Writes the specified string value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(string value)
Write(value, CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 32-bit
/// signed integer value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(int value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 32-bit
/// signed integer value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, int value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 32-bit
/// unsigned integer value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(uint value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 32-bit
/// unsigned integer value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, uint value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 64-bit
/// signed integer value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(long value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 64-bit
/// signed integer value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, long value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 64-bit
/// unsigned integer value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(ulong value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 64-bit
/// unsigned integer value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, ulong value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified single-precision
/// floating-point value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(float value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified single-precision
/// floating-point value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, float value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified double-precision
/// floating-point value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(double value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified double-precision
/// floating-point value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, double value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified decimal value, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(decimal value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified decimal value, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, decimal value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified boolean value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(bool value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified boolean value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, bool value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the specified Unicode character to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(char value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the specified Unicode character to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, char value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the specified array of Unicode characters to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(char[] value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the specified array of Unicode characters to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, char[] value)
{
if (value is null)
{
Write(value, CurrentStyle);
throw new ArgumentNullException(nameof(value));
}
/// <summary>
/// Writes the text representation of the specified 32-bit
/// signed integer value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(int value)
for (var index = 0; index < value.Length; index++)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 32-bit
/// signed integer value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, int value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 32-bit
/// unsigned integer value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(uint value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 32-bit
/// unsigned integer value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, uint value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 64-bit
/// signed integer value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(long value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 64-bit
/// signed integer value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, long value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 64-bit
/// unsigned integer value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(ulong value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 64-bit
/// unsigned integer value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, ulong value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified single-precision
/// floating-point value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(float value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified single-precision
/// floating-point value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, float value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified double-precision
/// floating-point value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(double value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified double-precision
/// floating-point value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, double value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified decimal value, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(decimal value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified decimal value, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, decimal value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified boolean value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(bool value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified boolean value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, bool value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the specified Unicode character to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(char value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the specified Unicode character to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, char value)
{
Console.Write(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the specified array of Unicode characters to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void Write(char[] value)
{
Write(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the specified array of Unicode characters to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void Write(IFormatProvider provider, char[] value)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
for (var index = 0; index < value.Length; index++)
{
Console.Write(value[index].ToString(provider), CurrentStyle);
}
}
/// <summary>
/// Writes the text representation of the specified array of objects,
/// to the console using the specified format information.
/// </summary>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Write(string format, params object[] args)
{
Write(CultureInfo.CurrentCulture, format, args);
}
/// <summary>
/// Writes the text representation of the specified array of objects,
/// to the console using the specified format information.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Write(IFormatProvider provider, string format, params object[] args)
{
Console.Write(string.Format(provider, format, args), CurrentStyle);
Console.Write(value[index].ToString(provider), CurrentStyle);
}
}
}
/// <summary>
/// Writes the text representation of the specified array of objects,
/// to the console using the specified format information.
/// </summary>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Write(string format, params object[] args)
{
Write(CultureInfo.CurrentCulture, format, args);
}
/// <summary>
/// Writes the text representation of the specified array of objects,
/// to the console using the specified format information.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void Write(IFormatProvider provider, string format, params object[] args)
{
Console.Write(string.Format(provider, format, args), CurrentStyle);
}
}

View File

@ -1,273 +1,272 @@
using System;
using System.Globalization;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// Writes an empty line to the console.
/// </summary>
public static partial class AnsiConsole
public static void WriteLine()
{
/// <summary>
/// Writes an empty line to the console.
/// </summary>
public static void WriteLine()
{
Console.WriteLine();
}
/// <summary>
/// Writes the specified string value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(string value)
{
Console.WriteLine(value, CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 32-bit signed integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(int value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 32-bit signed integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, int value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 32-bit unsigned integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(uint value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 32-bit unsigned integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, uint value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 64-bit signed integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(long value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 64-bit signed integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, long value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 64-bit unsigned integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(ulong value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 64-bit unsigned integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, ulong value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified single-precision floating-point
/// value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(float value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified single-precision floating-point
/// value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, float value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified double-precision floating-point
/// value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(double value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified double-precision floating-point
/// value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, double value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified decimal value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(decimal value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified decimal value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, decimal value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified boolean value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(bool value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified boolean value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, bool value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the specified Unicode character, followed by the current
/// line terminator, value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(char value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the specified Unicode character, followed by the current
/// line terminator, value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, char value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the specified array of Unicode characters, followed by the current
/// line terminator, value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(char[] value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the specified array of Unicode characters, followed by the current
/// line terminator, value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, char[] value)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
for (var index = 0; index < value.Length; index++)
{
Console.Write(value[index].ToString(provider), CurrentStyle);
}
Console.WriteLine();
}
/// <summary>
/// Writes the text representation of the specified array of objects,
/// followed by the current line terminator, to the console
/// using the specified format information.
/// </summary>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void WriteLine(string format, params object[] args)
{
WriteLine(CultureInfo.CurrentCulture, format, args);
}
/// <summary>
/// Writes the text representation of the specified array of objects,
/// followed by the current line terminator, to the console
/// using the specified format information.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void WriteLine(IFormatProvider provider, string format, params object[] args)
{
Console.WriteLine(string.Format(provider, format, args), CurrentStyle);
}
Console.WriteLine();
}
}
/// <summary>
/// Writes the specified string value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(string value)
{
Console.WriteLine(value, CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 32-bit signed integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(int value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 32-bit signed integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, int value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 32-bit unsigned integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(uint value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 32-bit unsigned integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, uint value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 64-bit signed integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(long value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 64-bit signed integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, long value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified 64-bit unsigned integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(ulong value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified 64-bit unsigned integer value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, ulong value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified single-precision floating-point
/// value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(float value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified single-precision floating-point
/// value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, float value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified double-precision floating-point
/// value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(double value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified double-precision floating-point
/// value, followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, double value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified decimal value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(decimal value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified decimal value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, decimal value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the text representation of the specified boolean value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(bool value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the text representation of the specified boolean value,
/// followed by the current line terminator, to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, bool value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the specified Unicode character, followed by the current
/// line terminator, value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(char value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the specified Unicode character, followed by the current
/// line terminator, value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, char value)
{
Console.WriteLine(value.ToString(provider), CurrentStyle);
}
/// <summary>
/// Writes the specified array of Unicode characters, followed by the current
/// line terminator, value to the console.
/// </summary>
/// <param name="value">The value to write.</param>
public static void WriteLine(char[] value)
{
WriteLine(CultureInfo.CurrentCulture, value);
}
/// <summary>
/// Writes the specified array of Unicode characters, followed by the current
/// line terminator, value to the console.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="value">The value to write.</param>
public static void WriteLine(IFormatProvider provider, char[] value)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
for (var index = 0; index < value.Length; index++)
{
Console.Write(value[index].ToString(provider), CurrentStyle);
}
Console.WriteLine();
}
/// <summary>
/// Writes the text representation of the specified array of objects,
/// followed by the current line terminator, to the console
/// using the specified format information.
/// </summary>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void WriteLine(string format, params object[] args)
{
WriteLine(CultureInfo.CurrentCulture, format, args);
}
/// <summary>
/// Writes the text representation of the specified array of objects,
/// followed by the current line terminator, to the console
/// using the specified format information.
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <param name="format">A composite format string.</param>
/// <param name="args">An array of objects to write.</param>
public static void WriteLine(IFormatProvider provider, string format, params object[] args)
{
Console.WriteLine(string.Format(provider, format, args), CurrentStyle);
}
}

View File

@ -1,78 +1,77 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
private static Recorder? _recorder;
private static Lazy<IAnsiConsole> _console = new Lazy<IAnsiConsole>(
() =>
{
var console = Create(new AnsiConsoleSettings
{
Ansi = AnsiSupport.Detect,
ColorSystem = ColorSystemSupport.Detect,
Out = new AnsiConsoleOutput(System.Console.Out),
});
namespace Spectre.Console;
Created = true;
return console;
/// <summary>
/// A console capable of writing ANSI escape sequences.
/// </summary>
public static partial class AnsiConsole
{
private static Recorder? _recorder;
private static Lazy<IAnsiConsole> _console = new Lazy<IAnsiConsole>(
() =>
{
var console = Create(new AnsiConsoleSettings
{
Ansi = AnsiSupport.Detect,
ColorSystem = ColorSystemSupport.Detect,
Out = new AnsiConsoleOutput(System.Console.Out),
});
/// <summary>
/// Gets or sets the underlying <see cref="IAnsiConsole"/>.
/// </summary>
public static IAnsiConsole Console
Created = true;
return console;
});
/// <summary>
/// Gets or sets the underlying <see cref="IAnsiConsole"/>.
/// </summary>
public static IAnsiConsole Console
{
get
{
get
{
return _recorder ?? _console.Value;
}
set
{
_console = new Lazy<IAnsiConsole>(() => value);
if (_recorder != null)
{
// Recreate the recorder
_recorder = _recorder.Clone(value);
}
Created = true;
}
return _recorder ?? _console.Value;
}
/// <summary>
/// Gets the <see cref="IAnsiConsoleCursor"/>.
/// </summary>
public static IAnsiConsoleCursor Cursor => _recorder?.Cursor ?? _console.Value.Cursor;
/// <summary>
/// Gets the console profile.
/// </summary>
public static Profile Profile => Console.Profile;
/// <summary>
/// Creates a new <see cref="IAnsiConsole"/> instance
/// from the provided settings.
/// </summary>
/// <param name="settings">The settings to use.</param>
/// <returns>An <see cref="IAnsiConsole"/> instance.</returns>
public static IAnsiConsole Create(AnsiConsoleSettings settings)
set
{
var factory = new AnsiConsoleFactory();
return factory.Create(settings);
}
_console = new Lazy<IAnsiConsole>(() => value);
/// <summary>
/// Clears the console.
/// </summary>
public static void Clear()
{
Console.Clear();
if (_recorder != null)
{
// Recreate the recorder
_recorder = _recorder.Clone(value);
}
Created = true;
}
}
}
/// <summary>
/// Gets the <see cref="IAnsiConsoleCursor"/>.
/// </summary>
public static IAnsiConsoleCursor Cursor => _recorder?.Cursor ?? _console.Value.Cursor;
/// <summary>
/// Gets the console profile.
/// </summary>
public static Profile Profile => Console.Profile;
/// <summary>
/// Creates a new <see cref="IAnsiConsole"/> instance
/// from the provided settings.
/// </summary>
/// <param name="settings">The settings to use.</param>
/// <returns>An <see cref="IAnsiConsole"/> instance.</returns>
public static IAnsiConsole Create(AnsiConsoleSettings settings)
{
var factory = new AnsiConsoleFactory();
return factory.Create(settings);
}
/// <summary>
/// Clears the console.
/// </summary>
public static void Clear()
{
Console.Clear();
}
}

View File

@ -3,110 +3,109 @@ using System.Runtime.InteropServices;
using Spectre.Console.Enrichment;
using Spectre.Console.Internal;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// Factory for creating an ANSI console.
/// </summary>
public sealed class AnsiConsoleFactory
{
/// <summary>
/// Factory for creating an ANSI console.
/// Creates an ANSI console.
/// </summary>
public sealed class AnsiConsoleFactory
/// <param name="settings">The settings.</param>
/// <returns>An implementation of <see cref="IAnsiConsole"/>.</returns>
public IAnsiConsole Create(AnsiConsoleSettings settings)
{
/// <summary>
/// Creates an ANSI console.
/// </summary>
/// <param name="settings">The settings.</param>
/// <returns>An implementation of <see cref="IAnsiConsole"/>.</returns>
public IAnsiConsole Create(AnsiConsoleSettings settings)
if (settings is null)
{
if (settings is null)
{
throw new ArgumentNullException(nameof(settings));
}
var output = settings.Out ?? new AnsiConsoleOutput(System.Console.Out);
if (output.Writer == null)
{
throw new InvalidOperationException("Output writer was null");
}
// Detect if the terminal support ANSI or not
var (supportsAnsi, legacyConsole) = DetectAnsi(settings, output.Writer);
// Use console encoding or fall back to provided encoding
var encoding = output.Writer.IsStandardOut() || output.Writer.IsStandardError()
? System.Console.OutputEncoding : output.Writer.Encoding;
// Get the color system
var colorSystem = settings.ColorSystem == ColorSystemSupport.Detect
? ColorSystemDetector.Detect(supportsAnsi)
: (ColorSystem)settings.ColorSystem;
// Get whether or not we consider the terminal interactive
var interactive = settings.Interactive == InteractionSupport.Yes;
if (settings.Interactive == InteractionSupport.Detect)
{
interactive = Environment.UserInteractive;
}
var profile = new Profile(output, encoding);
profile.Capabilities.ColorSystem = colorSystem;
profile.Capabilities.Ansi = supportsAnsi;
profile.Capabilities.Links = supportsAnsi && !legacyConsole;
profile.Capabilities.Legacy = legacyConsole;
profile.Capabilities.Interactive = interactive;
profile.Capabilities.Unicode = encoding.EncodingName.ContainsExact("Unicode");
profile.Capabilities.AlternateBuffer = supportsAnsi && !legacyConsole;
// Enrich the profile
ProfileEnricher.Enrich(
profile,
settings.Enrichment,
settings.EnvironmentVariables);
return new AnsiConsoleFacade(
profile,
settings.ExclusivityMode ?? new DefaultExclusivityMode());
throw new ArgumentNullException(nameof(settings));
}
private static (bool Ansi, bool Legacy) DetectAnsi(AnsiConsoleSettings settings, System.IO.TextWriter buffer)
var output = settings.Out ?? new AnsiConsoleOutput(System.Console.Out);
if (output.Writer == null)
{
var supportsAnsi = settings.Ansi == AnsiSupport.Yes;
var legacyConsole = false;
throw new InvalidOperationException("Output writer was null");
}
if (settings.Ansi == AnsiSupport.Detect)
// Detect if the terminal support ANSI or not
var (supportsAnsi, legacyConsole) = DetectAnsi(settings, output.Writer);
// Use console encoding or fall back to provided encoding
var encoding = output.Writer.IsStandardOut() || output.Writer.IsStandardError()
? System.Console.OutputEncoding : output.Writer.Encoding;
// Get the color system
var colorSystem = settings.ColorSystem == ColorSystemSupport.Detect
? ColorSystemDetector.Detect(supportsAnsi)
: (ColorSystem)settings.ColorSystem;
// Get whether or not we consider the terminal interactive
var interactive = settings.Interactive == InteractionSupport.Yes;
if (settings.Interactive == InteractionSupport.Detect)
{
interactive = Environment.UserInteractive;
}
var profile = new Profile(output, encoding);
profile.Capabilities.ColorSystem = colorSystem;
profile.Capabilities.Ansi = supportsAnsi;
profile.Capabilities.Links = supportsAnsi && !legacyConsole;
profile.Capabilities.Legacy = legacyConsole;
profile.Capabilities.Interactive = interactive;
profile.Capabilities.Unicode = encoding.EncodingName.ContainsExact("Unicode");
profile.Capabilities.AlternateBuffer = supportsAnsi && !legacyConsole;
// Enrich the profile
ProfileEnricher.Enrich(
profile,
settings.Enrichment,
settings.EnvironmentVariables);
return new AnsiConsoleFacade(
profile,
settings.ExclusivityMode ?? new DefaultExclusivityMode());
}
private static (bool Ansi, bool Legacy) DetectAnsi(AnsiConsoleSettings settings, System.IO.TextWriter buffer)
{
var supportsAnsi = settings.Ansi == AnsiSupport.Yes;
var legacyConsole = false;
if (settings.Ansi == AnsiSupport.Detect)
{
(supportsAnsi, legacyConsole) = AnsiDetector.Detect(buffer.IsStandardError(), true);
// Check whether or not this is a legacy console from the existing instance (if any).
// We need to do this because once we upgrade the console to support ENABLE_VIRTUAL_TERMINAL_PROCESSING
// on Windows, there is no way of detecting whether or not we're running on a legacy console or not.
if (AnsiConsole.Created && !legacyConsole && (buffer.IsStandardOut() || buffer.IsStandardError()) && AnsiConsole.Profile.Capabilities.Legacy)
{
(supportsAnsi, legacyConsole) = AnsiDetector.Detect(buffer.IsStandardError(), true);
// Check whether or not this is a legacy console from the existing instance (if any).
// We need to do this because once we upgrade the console to support ENABLE_VIRTUAL_TERMINAL_PROCESSING
// on Windows, there is no way of detecting whether or not we're running on a legacy console or not.
if (AnsiConsole.Created && !legacyConsole && (buffer.IsStandardOut() || buffer.IsStandardError()) && AnsiConsole.Profile.Capabilities.Legacy)
{
legacyConsole = AnsiConsole.Profile.Capabilities.Legacy;
}
legacyConsole = AnsiConsole.Profile.Capabilities.Legacy;
}
else
}
else
{
if (buffer.IsStandardOut() || buffer.IsStandardError())
{
if (buffer.IsStandardOut() || buffer.IsStandardError())
// Are we running on Windows?
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Are we running on Windows?
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
// Not the first console we're creating?
if (AnsiConsole.Created)
{
// Not the first console we're creating?
if (AnsiConsole.Created)
{
legacyConsole = AnsiConsole.Profile.Capabilities.Legacy;
}
else
{
// Try detecting whether or not this is a legacy console
(_, legacyConsole) = AnsiDetector.Detect(buffer.IsStandardError(), false);
}
legacyConsole = AnsiConsole.Profile.Capabilities.Legacy;
}
else
{
// Try detecting whether or not this is a legacy console
(_, legacyConsole) = AnsiDetector.Detect(buffer.IsStandardError(), false);
}
}
}
return (supportsAnsi, legacyConsole);
}
return (supportsAnsi, legacyConsole);
}
}
}

View File

@ -2,57 +2,56 @@ using System;
using System.IO;
using System.Text;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// Represents console output.
/// </summary>
public sealed class AnsiConsoleOutput : IAnsiConsoleOutput
{
/// <summary>
/// Represents console output.
/// </summary>
public sealed class AnsiConsoleOutput : IAnsiConsoleOutput
/// <inheritdoc/>
public TextWriter Writer { get; }
/// <inheritdoc/>
public bool IsTerminal
{
/// <inheritdoc/>
public TextWriter Writer { get; }
/// <inheritdoc/>
public bool IsTerminal
get
{
get
if (Writer.IsStandardOut())
{
if (Writer.IsStandardOut())
{
return !System.Console.IsOutputRedirected;
}
if (Writer.IsStandardError())
{
return !System.Console.IsErrorRedirected;
}
return false;
return !System.Console.IsOutputRedirected;
}
}
/// <inheritdoc/>
public int Width => ConsoleHelper.GetSafeWidth(Constants.DefaultTerminalWidth);
/// <inheritdoc/>
public int Height => ConsoleHelper.GetSafeHeight(Constants.DefaultTerminalWidth);
/// <summary>
/// Initializes a new instance of the <see cref="AnsiConsoleOutput"/> class.
/// </summary>
/// <param name="writer">The output writer.</param>
public AnsiConsoleOutput(TextWriter writer)
{
Writer = writer ?? throw new ArgumentNullException(nameof(writer));
}
/// <inheritdoc/>
public void SetEncoding(Encoding encoding)
{
if (Writer.IsStandardOut() || Writer.IsStandardError())
if (Writer.IsStandardError())
{
System.Console.OutputEncoding = encoding;
return !System.Console.IsErrorRedirected;
}
return false;
}
}
}
/// <inheritdoc/>
public int Width => ConsoleHelper.GetSafeWidth(Constants.DefaultTerminalWidth);
/// <inheritdoc/>
public int Height => ConsoleHelper.GetSafeHeight(Constants.DefaultTerminalWidth);
/// <summary>
/// Initializes a new instance of the <see cref="AnsiConsoleOutput"/> class.
/// </summary>
/// <param name="writer">The output writer.</param>
public AnsiConsoleOutput(TextWriter writer)
{
Writer = writer ?? throw new ArgumentNullException(nameof(writer));
}
/// <inheritdoc/>
public void SetEncoding(Encoding encoding)
{
if (Writer.IsStandardOut() || Writer.IsStandardError())
{
System.Console.OutputEncoding = encoding;
}
}
}

View File

@ -1,56 +1,55 @@
using System.Collections.Generic;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// Settings used when building a <see cref="IAnsiConsole"/>.
/// </summary>
public sealed class AnsiConsoleSettings
{
/// <summary>
/// Settings used when building a <see cref="IAnsiConsole"/>.
/// Gets or sets a value indicating whether or
/// not ANSI escape sequences are supported.
/// </summary>
public sealed class AnsiConsoleSettings
public AnsiSupport Ansi { get; set; }
/// <summary>
/// Gets or sets the color system to use.
/// </summary>
public ColorSystemSupport ColorSystem { get; set; } = ColorSystemSupport.Detect;
/// <summary>
/// Gets or sets the out buffer.
/// </summary>
public IAnsiConsoleOutput? Out { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not the
/// terminal is interactive or not.
/// </summary>
public InteractionSupport Interactive { get; set; }
/// <summary>
/// Gets or sets the exclusivity mode.
/// </summary>
public IExclusivityMode? ExclusivityMode { get; set; }
/// <summary>
/// Gets or sets the profile enrichments settings.
/// </summary>
public ProfileEnrichment Enrichment { get; set; }
/// <summary>
/// Gets or sets the environment variables.
/// If not value is provided the default environment variables will be used.
/// </summary>
public Dictionary<string, string>? EnvironmentVariables { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="AnsiConsoleSettings"/> class.
/// </summary>
public AnsiConsoleSettings()
{
/// <summary>
/// Gets or sets a value indicating whether or
/// not ANSI escape sequences are supported.
/// </summary>
public AnsiSupport Ansi { get; set; }
/// <summary>
/// Gets or sets the color system to use.
/// </summary>
public ColorSystemSupport ColorSystem { get; set; } = ColorSystemSupport.Detect;
/// <summary>
/// Gets or sets the out buffer.
/// </summary>
public IAnsiConsoleOutput? Out { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not the
/// terminal is interactive or not.
/// </summary>
public InteractionSupport Interactive { get; set; }
/// <summary>
/// Gets or sets the exclusivity mode.
/// </summary>
public IExclusivityMode? ExclusivityMode { get; set; }
/// <summary>
/// Gets or sets the profile enrichments settings.
/// </summary>
public ProfileEnrichment Enrichment { get; set; }
/// <summary>
/// Gets or sets the environment variables.
/// If not value is provided the default environment variables will be used.
/// </summary>
public Dictionary<string, string>? EnvironmentVariables { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="AnsiConsoleSettings"/> class.
/// </summary>
public AnsiConsoleSettings()
{
Enrichment = new ProfileEnrichment();
}
Enrichment = new ProfileEnrichment();
}
}
}

View File

@ -1,24 +1,23 @@
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// Determines ANSI escape sequence support.
/// </summary>
public enum AnsiSupport
{
/// <summary>
/// Determines ANSI escape sequence support.
/// ANSI escape sequence support should
/// be detected by the system.
/// </summary>
public enum AnsiSupport
{
/// <summary>
/// ANSI escape sequence support should
/// be detected by the system.
/// </summary>
Detect = 0,
Detect = 0,
/// <summary>
/// ANSI escape sequences are supported.
/// </summary>
Yes = 1,
/// <summary>
/// ANSI escape sequences are supported.
/// </summary>
Yes = 1,
/// <summary>
/// ANSI escape sequences are not supported.
/// </summary>
No = 2,
}
}
/// <summary>
/// ANSI escape sequences are not supported.
/// </summary>
No = 2,
}

View File

@ -1,42 +1,41 @@
using System.Diagnostics.CodeAnalysis;
using Spectre.Console.Rendering;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// Represents a border.
/// </summary>
public abstract partial class BoxBorder
{
/// <summary>
/// Represents a border.
/// Gets an invisible border.
/// </summary>
public abstract partial class BoxBorder
{
/// <summary>
/// Gets an invisible border.
/// </summary>
public static BoxBorder None { get; } = new NoBoxBorder();
public static BoxBorder None { get; } = new NoBoxBorder();
/// <summary>
/// Gets an ASCII border.
/// </summary>
public static BoxBorder Ascii { get; } = new AsciiBoxBorder();
/// <summary>
/// Gets an ASCII border.
/// </summary>
public static BoxBorder Ascii { get; } = new AsciiBoxBorder();
/// <summary>
/// Gets a double border.
/// </summary>
[SuppressMessage("Naming", "CA1720:Identifier contains type name")]
public static BoxBorder Double { get; } = new DoubleBoxBorder();
/// <summary>
/// Gets a double border.
/// </summary>
[SuppressMessage("Naming", "CA1720:Identifier contains type name")]
public static BoxBorder Double { get; } = new DoubleBoxBorder();
/// <summary>
/// Gets a heavy border.
/// </summary>
public static BoxBorder Heavy { get; } = new HeavyBoxBorder();
/// <summary>
/// Gets a heavy border.
/// </summary>
public static BoxBorder Heavy { get; } = new HeavyBoxBorder();
/// <summary>
/// Gets a rounded border.
/// </summary>
public static BoxBorder Rounded { get; } = new RoundedBoxBorder();
/// <summary>
/// Gets a rounded border.
/// </summary>
public static BoxBorder Rounded { get; } = new RoundedBoxBorder();
/// <summary>
/// Gets a square border.
/// </summary>
public static BoxBorder Square { get; } = new SquareBoxBorder();
}
}
/// <summary>
/// Gets a square border.
/// </summary>
public static BoxBorder Square { get; } = new SquareBoxBorder();
}

View File

@ -1,22 +1,21 @@
using Spectre.Console.Rendering;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// Represents a border.
/// </summary>
public abstract partial class BoxBorder
{
/// <summary>
/// Represents a border.
/// Gets the safe border for this border or <c>null</c> if none exist.
/// </summary>
public abstract partial class BoxBorder
{
/// <summary>
/// Gets the safe border for this border or <c>null</c> if none exist.
/// </summary>
public virtual BoxBorder? SafeBorder { get; }
public virtual BoxBorder? SafeBorder { get; }
/// <summary>
/// Gets the string representation of the specified border part.
/// </summary>
/// <param name="part">The part to get the character representation for.</param>
/// <returns>A character representation of the specified border part.</returns>
public abstract string GetPart(BoxBorderPart part);
}
}
/// <summary>
/// Gets the string representation of the specified border part.
/// </summary>
/// <param name="part">The part to get the character representation for.</param>
/// <returns>A character representation of the specified border part.</returns>
public abstract string GetPart(BoxBorderPart part);
}

View File

@ -1,73 +1,72 @@
using System;
namespace Spectre.Console
namespace Spectre.Console;
/// <summary>
/// Represents console capabilities.
/// </summary>
public sealed class Capabilities : IReadOnlyCapabilities
{
private readonly IAnsiConsoleOutput _out;
/// <summary>
/// Represents console capabilities.
/// Gets or sets the color system.
/// </summary>
public sealed class Capabilities : IReadOnlyCapabilities
public ColorSystem ColorSystem { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not
/// the console supports VT/ANSI control codes.
/// </summary>
public bool Ansi { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not
/// the console support links.
/// </summary>
public bool Links { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not
/// this is a legacy console (cmd.exe) on an OS
/// prior to Windows 10.
/// </summary>
/// <remarks>
/// Only relevant when running on Microsoft Windows.
/// </remarks>
public bool Legacy { get; set; }
/// <summary>
/// Gets a value indicating whether or not
/// the output is a terminal.
/// </summary>
[Obsolete("Use Profile.Out.IsTerminal instead")]
public bool IsTerminal => _out.IsTerminal;
/// <summary>
/// Gets or sets a value indicating whether
/// or not the console supports interaction.
/// </summary>
public bool Interactive { get; set; }
/// <summary>
/// Gets or sets a value indicating whether
/// or not the console supports Unicode.
/// </summary>
public bool Unicode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether
/// or not the console supports alternate buffers.
/// </summary>
public bool AlternateBuffer { get; set; }
/// <summary>
/// Initializes a new instance of the
/// <see cref="Capabilities"/> class.
/// </summary>
internal Capabilities(IAnsiConsoleOutput @out)
{
private readonly IAnsiConsoleOutput _out;
/// <summary>
/// Gets or sets the color system.
/// </summary>
public ColorSystem ColorSystem { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not
/// the console supports VT/ANSI control codes.
/// </summary>
public bool Ansi { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not
/// the console support links.
/// </summary>
public bool Links { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not
/// this is a legacy console (cmd.exe) on an OS
/// prior to Windows 10.
/// </summary>
/// <remarks>
/// Only relevant when running on Microsoft Windows.
/// </remarks>
public bool Legacy { get; set; }
/// <summary>
/// Gets a value indicating whether or not
/// the output is a terminal.
/// </summary>
[Obsolete("Use Profile.Out.IsTerminal instead")]
public bool IsTerminal => _out.IsTerminal;
/// <summary>
/// Gets or sets a value indicating whether
/// or not the console supports interaction.
/// </summary>
public bool Interactive { get; set; }
/// <summary>
/// Gets or sets a value indicating whether
/// or not the console supports Unicode.
/// </summary>
public bool Unicode { get; set; }
/// <summary>
/// Gets or sets a value indicating whether
/// or not the console supports alternate buffers.
/// </summary>
public bool AlternateBuffer { get; set; }
/// <summary>
/// Initializes a new instance of the
/// <see cref="Capabilities"/> class.
/// </summary>
internal Capabilities(IAnsiConsoleOutput @out)
{
_out = @out ?? throw new ArgumentNullException(nameof(@out));
}
_out = @out ?? throw new ArgumentNullException(nameof(@out));
}
}
}

View File

@ -1,53 +1,52 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// An attribute representing a command argument.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class CommandArgumentAttribute : Attribute
{
/// <summary>
/// An attribute representing a command argument.
/// Gets the argument position.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class CommandArgumentAttribute : Attribute
/// <value>The argument position.</value>
public int Position { get; }
/// <summary>
/// Gets the value name of the argument.
/// </summary>
/// <value>The value name of the argument.</value>
public string ValueName { get; }
/// <summary>
/// Gets a value indicating whether the argument is required.
/// </summary>
/// <value>
/// <c>true</c> if the argument is required; otherwise, <c>false</c>.
/// </value>
public bool IsRequired { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandArgumentAttribute"/> class.
/// </summary>
/// <param name="position">The argument position.</param>
/// <param name="template">The argument template.</param>
public CommandArgumentAttribute(int position, string template)
{
/// <summary>
/// Gets the argument position.
/// </summary>
/// <value>The argument position.</value>
public int Position { get; }
/// <summary>
/// Gets the value name of the argument.
/// </summary>
/// <value>The value name of the argument.</value>
public string ValueName { get; }
/// <summary>
/// Gets a value indicating whether the argument is required.
/// </summary>
/// <value>
/// <c>true</c> if the argument is required; otherwise, <c>false</c>.
/// </value>
public bool IsRequired { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandArgumentAttribute"/> class.
/// </summary>
/// <param name="position">The argument position.</param>
/// <param name="template">The argument template.</param>
public CommandArgumentAttribute(int position, string template)
if (template == null)
{
if (template == null)
{
throw new ArgumentNullException(nameof(template));
}
// Parse the option template.
var result = TemplateParser.ParseArgumentTemplate(template);
// Assign the result.
Position = position;
ValueName = result.Value;
IsRequired = result.Required;
throw new ArgumentNullException(nameof(template));
}
// Parse the option template.
var result = TemplateParser.ParseArgumentTemplate(template);
// Assign the result.
Position = position;
ValueName = result.Value;
IsRequired = result.Required;
}
}
}

View File

@ -2,69 +2,68 @@ using System;
using System.Collections.Generic;
using System.Linq;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// An attribute representing a command option.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class CommandOptionAttribute : Attribute
{
/// <summary>
/// An attribute representing a command option.
/// Gets the long names of the option.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class CommandOptionAttribute : Attribute
/// <value>The option's long names.</value>
public IReadOnlyList<string> LongNames { get; }
/// <summary>
/// Gets the short names of the option.
/// </summary>
/// <value>The option's short names.</value>
public IReadOnlyList<string> ShortNames { get; }
/// <summary>
/// Gets the value name of the option.
/// </summary>
/// <value>The option's value name.</value>
public string? ValueName { get; }
/// <summary>
/// Gets a value indicating whether the value is optional.
/// </summary>
public bool ValueIsOptional { get; }
/// <summary>
/// Gets or sets a value indicating whether this option is hidden from the help text.
/// </summary>
public bool IsHidden { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandOptionAttribute"/> class.
/// </summary>
/// <param name="template">The option template.</param>
public CommandOptionAttribute(string template)
{
/// <summary>
/// Gets the long names of the option.
/// </summary>
/// <value>The option's long names.</value>
public IReadOnlyList<string> LongNames { get; }
/// <summary>
/// Gets the short names of the option.
/// </summary>
/// <value>The option's short names.</value>
public IReadOnlyList<string> ShortNames { get; }
/// <summary>
/// Gets the value name of the option.
/// </summary>
/// <value>The option's value name.</value>
public string? ValueName { get; }
/// <summary>
/// Gets a value indicating whether the value is optional.
/// </summary>
public bool ValueIsOptional { get; }
/// <summary>
/// Gets or sets a value indicating whether this option is hidden from the help text.
/// </summary>
public bool IsHidden { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandOptionAttribute"/> class.
/// </summary>
/// <param name="template">The option template.</param>
public CommandOptionAttribute(string template)
if (template == null)
{
if (template == null)
{
throw new ArgumentNullException(nameof(template));
}
// Parse the option template.
var result = TemplateParser.ParseOptionTemplate(template);
// Assign the result.
LongNames = result.LongNames;
ShortNames = result.ShortNames;
ValueName = result.Value;
ValueIsOptional = result.ValueIsOptional;
throw new ArgumentNullException(nameof(template));
}
internal bool IsMatch(string name)
{
return
ShortNames.Contains(name, StringComparer.Ordinal) ||
LongNames.Contains(name, StringComparer.Ordinal);
}
// Parse the option template.
var result = TemplateParser.ParseOptionTemplate(template);
// Assign the result.
LongNames = result.LongNames;
ShortNames = result.ShortNames;
ValueName = result.Value;
ValueIsOptional = result.ValueIsOptional;
}
}
internal bool IsMatch(string name)
{
return
ShortNames.Contains(name, StringComparer.Ordinal) ||
LongNames.Contains(name, StringComparer.Ordinal);
}
}

View File

@ -1,31 +1,30 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Specifies what type to use as a pair deconstructor for
/// the property this attribute is bound to.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class PairDeconstructorAttribute : Attribute
{
/// <summary>
/// Specifies what type to use as a pair deconstructor for
/// the property this attribute is bound to.
/// Gets the <see cref="string"/> that represents the type of the
/// pair deconstructor class to use for data conversion for the
/// object this attribute is bound to.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class PairDeconstructorAttribute : Attribute
{
/// <summary>
/// Gets the <see cref="string"/> that represents the type of the
/// pair deconstructor class to use for data conversion for the
/// object this attribute is bound to.
/// </summary>
public Type Type { get; }
public Type Type { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PairDeconstructorAttribute"/> class.
/// </summary>
/// <param name="type">
/// A System.Type that represents the type of the pair deconstructor
/// class to use for data conversion for the object this attribute is bound to.
/// </param>
public PairDeconstructorAttribute(Type type)
{
Type = type ?? throw new ArgumentNullException(nameof(type));
}
/// <summary>
/// Initializes a new instance of the <see cref="PairDeconstructorAttribute"/> class.
/// </summary>
/// <param name="type">
/// A System.Type that represents the type of the pair deconstructor
/// class to use for data conversion for the object this attribute is bound to.
/// </param>
public PairDeconstructorAttribute(Type type)
{
Type = type ?? throw new ArgumentNullException(nameof(type));
}
}
}

View File

@ -1,34 +1,33 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// A base class attribute used for parameter validation.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public abstract class ParameterValidationAttribute : Attribute
{
/// <summary>
/// A base class attribute used for parameter validation.
/// Gets the validation error message.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public abstract class ParameterValidationAttribute : Attribute
/// <value>The validation error message.</value>
public string ErrorMessage { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ParameterValidationAttribute"/> class.
/// </summary>
/// <param name="errorMessage">The validation error message.</param>
protected ParameterValidationAttribute(string errorMessage)
{
/// <summary>
/// Gets the validation error message.
/// </summary>
/// <value>The validation error message.</value>
public string ErrorMessage { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ParameterValidationAttribute"/> class.
/// </summary>
/// <param name="errorMessage">The validation error message.</param>
protected ParameterValidationAttribute(string errorMessage)
{
ErrorMessage = errorMessage;
}
/// <summary>
/// Validates the parameter value.
/// </summary>
/// <param name="context">The parameter context.</param>
/// <returns>The validation result.</returns>
public abstract ValidationResult Validate(CommandParameterContext context);
ErrorMessage = errorMessage;
}
/// <summary>
/// Validates the parameter value.
/// </summary>
/// <param name="context">The parameter context.</param>
/// <returns>The validation result.</returns>
public abstract ValidationResult Validate(CommandParameterContext context);
}

View File

@ -1,20 +1,19 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// A base class attribute used for parameter completion.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public abstract class ParameterValueProviderAttribute : Attribute
{
/// <summary>
/// A base class attribute used for parameter completion.
/// Gets a value for the parameter.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public abstract class ParameterValueProviderAttribute : Attribute
{
/// <summary>
/// Gets a value for the parameter.
/// </summary>
/// <param name="context">The parameter context.</param>
/// <param name="result">The resulting value.</param>
/// <returns><c>true</c> if a value was provided; otherwise, <c>false</c>.</returns>
public abstract bool TryGetValue(CommandParameterContext context, out object? result);
}
}
/// <param name="context">The parameter context.</param>
/// <param name="result">The resulting value.</param>
/// <returns><c>true</c> if a value was provided; otherwise, <c>false</c>.</returns>
public abstract bool TryGetValue(CommandParameterContext context, out object? result);
}

View File

@ -1,35 +1,34 @@
using System.Threading.Tasks;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Base class for an asynchronous command with no settings.
/// </summary>
public abstract class AsyncCommand : ICommand<EmptyCommandSettings>
{
/// <summary>
/// Base class for an asynchronous command with no settings.
/// Executes the command.
/// </summary>
public abstract class AsyncCommand : ICommand<EmptyCommandSettings>
/// <param name="context">The command context.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract Task<int> ExecuteAsync(CommandContext context);
/// <inheritdoc/>
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings)
{
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract Task<int> ExecuteAsync(CommandContext context);
return ExecuteAsync(context);
}
/// <inheritdoc/>
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings)
{
return ExecuteAsync(context);
}
/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
{
return ExecuteAsync(context);
}
/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
{
return ExecuteAsync(context);
}
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
{
return ValidationResult.Success();
}
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
{
return ValidationResult.Success();
}
}

View File

@ -1,51 +1,50 @@
using System.Diagnostics;
using System.Threading.Tasks;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Base class for an asynchronous command.
/// </summary>
/// <typeparam name="TSettings">The settings type.</typeparam>
public abstract class AsyncCommand<TSettings> : ICommand<TSettings>
where TSettings : CommandSettings
{
/// <summary>
/// Base class for an asynchronous command.
/// Validates the specified settings and remaining arguments.
/// </summary>
/// <typeparam name="TSettings">The settings type.</typeparam>
public abstract class AsyncCommand<TSettings> : ICommand<TSettings>
where TSettings : CommandSettings
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
public virtual ValidationResult Validate(CommandContext context, TSettings settings)
{
/// <summary>
/// Validates the specified settings and remaining arguments.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
public virtual ValidationResult Validate(CommandContext context, TSettings settings)
{
return ValidationResult.Success();
}
return ValidationResult.Success();
}
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract Task<int> ExecuteAsync(CommandContext context, TSettings settings);
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract Task<int> ExecuteAsync(CommandContext context, TSettings settings);
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
{
return Validate(context, (TSettings)settings);
}
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
{
return Validate(context, (TSettings)settings);
}
/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
{
Debug.Assert(settings is TSettings, "Command settings is of unexpected type.");
return ExecuteAsync(context, (TSettings)settings);
}
/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
{
Debug.Assert(settings is TSettings, "Command settings is of unexpected type.");
return ExecuteAsync(context, (TSettings)settings);
}
/// <inheritdoc/>
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings)
{
return ExecuteAsync(context, settings);
}
/// <inheritdoc/>
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings)
{
return ExecuteAsync(context, settings);
}
}

View File

@ -1,31 +1,30 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents case sensitivity.
/// </summary>
[Flags]
public enum CaseSensitivity
{
/// <summary>
/// Represents case sensitivity.
/// Nothing is case sensitive.
/// </summary>
[Flags]
public enum CaseSensitivity
{
/// <summary>
/// Nothing is case sensitive.
/// </summary>
None = 0,
None = 0,
/// <summary>
/// Long options are case sensitive.
/// </summary>
LongOptions = 1,
/// <summary>
/// Long options are case sensitive.
/// </summary>
LongOptions = 1,
/// <summary>
/// Commands are case sensitive.
/// </summary>
Commands = 2,
/// <summary>
/// Commands are case sensitive.
/// </summary>
Commands = 2,
/// <summary>
/// Everything is case sensitive.
/// </summary>
All = LongOptions | Commands,
}
}
/// <summary>
/// Everything is case sensitive.
/// </summary>
All = LongOptions | Commands,
}

View File

@ -1,36 +1,35 @@
using System.Threading.Tasks;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Base class for a command without settings.
/// </summary>
/// <seealso cref="AsyncCommand"/>
public abstract class Command : ICommand<EmptyCommandSettings>
{
/// <summary>
/// Base class for a command without settings.
/// Executes the command.
/// </summary>
/// <seealso cref="AsyncCommand"/>
public abstract class Command : ICommand<EmptyCommandSettings>
/// <param name="context">The command context.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract int Execute(CommandContext context);
/// <inheritdoc/>
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings)
{
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract int Execute(CommandContext context);
/// <inheritdoc/>
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings)
{
return Task.FromResult(Execute(context));
}
/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
{
return Task.FromResult(Execute(context));
}
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
{
return ValidationResult.Success();
}
return Task.FromResult(Execute(context));
}
}
/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
{
return Task.FromResult(Execute(context));
}
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
{
return ValidationResult.Success();
}
}

View File

@ -4,136 +4,136 @@ using System.Diagnostics;
using System.Threading.Tasks;
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// The entry point for a command line application.
/// </summary>
public sealed class CommandApp : ICommandApp
{
private readonly Configurator _configurator;
private readonly CommandExecutor _executor;
private bool _executed;
/// <summary>
/// The entry point for a command line application.
/// Initializes a new instance of the <see cref="CommandApp"/> class.
/// </summary>
public sealed class CommandApp : ICommandApp
/// <param name="registrar">The registrar.</param>
public CommandApp(ITypeRegistrar? registrar = null)
{
private readonly Configurator _configurator;
private readonly CommandExecutor _executor;
private bool _executed;
registrar ??= new DefaultTypeRegistrar();
/// <summary>
/// Initializes a new instance of the <see cref="CommandApp"/> class.
/// </summary>
/// <param name="registrar">The registrar.</param>
public CommandApp(ITypeRegistrar? registrar = null)
_configurator = new Configurator(registrar);
_executor = new CommandExecutor(registrar);
}
/// <summary>
/// Configures the command line application.
/// </summary>
/// <param name="configuration">The configuration.</param>
public void Configure(Action<IConfigurator> configuration)
{
if (configuration == null)
{
registrar ??= new DefaultTypeRegistrar();
_configurator = new Configurator(registrar);
_executor = new CommandExecutor(registrar);
throw new ArgumentNullException(nameof(configuration));
}
/// <summary>
/// Configures the command line application.
/// </summary>
/// <param name="configuration">The configuration.</param>
public void Configure(Action<IConfigurator> configuration)
configuration(_configurator);
}
/// <summary>
/// Sets the default command.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
public void SetDefaultCommand<TCommand>()
where TCommand : class, ICommand
{
GetConfigurator().SetDefaultCommand<TCommand>();
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public int Run(IEnumerable<string> args)
{
return RunAsync(args).GetAwaiter().GetResult();
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public async Task<int> RunAsync(IEnumerable<string> args)
{
try
{
if (configuration == null)
if (!_executed)
{
throw new ArgumentNullException(nameof(configuration));
// Add built-in (hidden) commands.
_configurator.AddBranch(CliConstants.Commands.Branch, cli =>
{
cli.HideBranch();
cli.AddCommand<VersionCommand>(CliConstants.Commands.Version);
cli.AddCommand<XmlDocCommand>(CliConstants.Commands.XmlDoc);
cli.AddCommand<ExplainCommand>(CliConstants.Commands.Explain);
});
_executed = true;
}
configuration(_configurator);
return await _executor
.Execute(_configurator, args)
.ConfigureAwait(false);
}
/// <summary>
/// Sets the default command.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
public void SetDefaultCommand<TCommand>()
where TCommand : class, ICommand
catch (Exception ex)
{
GetConfigurator().SetDefaultCommand<TCommand>();
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public int Run(IEnumerable<string> args)
{
return RunAsync(args).GetAwaiter().GetResult();
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public async Task<int> RunAsync(IEnumerable<string> args)
{
try
// Should we always propagate when debugging?
if (Debugger.IsAttached
&& ex is CommandAppException appException
&& appException.AlwaysPropagateWhenDebugging)
{
if (!_executed)
{
// Add built-in (hidden) commands.
_configurator.AddBranch(CliConstants.Commands.Branch, cli =>
{
cli.HideBranch();
cli.AddCommand<VersionCommand>(CliConstants.Commands.Version);
cli.AddCommand<XmlDocCommand>(CliConstants.Commands.XmlDoc);
cli.AddCommand<ExplainCommand>(CliConstants.Commands.Explain);
});
_executed = true;
}
return await _executor
.Execute(_configurator, args)
.ConfigureAwait(false);
}
catch (Exception ex)
{
// Should we always propagate when debugging?
if (Debugger.IsAttached
&& ex is CommandAppException appException
&& appException.AlwaysPropagateWhenDebugging)
{
throw;
}
if (_configurator.Settings.PropagateExceptions)
{
throw;
}
if (_configurator.Settings.ExceptionHandler != null)
{
return _configurator.Settings.ExceptionHandler(ex);
}
// Render the exception.
var pretty = GetRenderableErrorMessage(ex);
if (pretty != null)
{
_configurator.Settings.Console.SafeRender(pretty);
}
return -1;
}
}
internal Configurator GetConfigurator()
{
return _configurator;
}
private static List<IRenderable?>? GetRenderableErrorMessage(Exception ex, bool convert = true)
{
if (ex is CommandAppException renderable && renderable.Pretty != null)
{
return new List<IRenderable?> { renderable.Pretty };
throw;
}
if (convert)
if (_configurator.Settings.PropagateExceptions)
{
var converted = new List<IRenderable?>
throw;
}
if (_configurator.Settings.ExceptionHandler != null)
{
return _configurator.Settings.ExceptionHandler(ex);
}
// Render the exception.
var pretty = GetRenderableErrorMessage(ex);
if (pretty != null)
{
_configurator.Settings.Console.SafeRender(pretty);
}
return -1;
}
}
internal Configurator GetConfigurator()
{
return _configurator;
}
private static List<IRenderable?>? GetRenderableErrorMessage(Exception ex, bool convert = true)
{
if (ex is CommandAppException renderable && renderable.Pretty != null)
{
return new List<IRenderable?> { renderable.Pretty };
}
if (convert)
{
var converted = new List<IRenderable?>
{
new Composer()
.Text("[red]Error:[/]")
@ -142,20 +142,19 @@ namespace Spectre.Console.Cli
.LineBreak(),
};
// Got a renderable inner exception?
if (ex.InnerException != null)
// Got a renderable inner exception?
if (ex.InnerException != null)
{
var innerRenderable = GetRenderableErrorMessage(ex.InnerException, convert: false);
if (innerRenderable != null)
{
var innerRenderable = GetRenderableErrorMessage(ex.InnerException, convert: false);
if (innerRenderable != null)
{
converted.AddRange(innerRenderable);
}
converted.AddRange(innerRenderable);
}
return converted;
}
return null;
return converted;
}
return null;
}
}
}

View File

@ -1,30 +1,29 @@
using System;
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents errors that occur during application execution.
/// </summary>
public abstract class CommandAppException : Exception
{
/// <summary>
/// Represents errors that occur during application execution.
/// Gets the pretty formatted exception message.
/// </summary>
public abstract class CommandAppException : Exception
public IRenderable? Pretty { get; }
internal virtual bool AlwaysPropagateWhenDebugging => false;
internal CommandAppException(string message, IRenderable? pretty = null)
: base(message)
{
/// <summary>
/// Gets the pretty formatted exception message.
/// </summary>
public IRenderable? Pretty { get; }
Pretty = pretty;
}
internal virtual bool AlwaysPropagateWhenDebugging => false;
internal CommandAppException(string message, IRenderable? pretty = null)
: base(message)
{
Pretty = pretty;
}
internal CommandAppException(string message, Exception ex, IRenderable? pretty = null)
: base(message, ex)
{
Pretty = pretty;
}
internal CommandAppException(string message, Exception ex, IRenderable? pretty = null)
: base(message, ex)
{
Pretty = pretty;
}
}

View File

@ -2,54 +2,53 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// The entry point for a command line application with a default command.
/// </summary>
/// <typeparam name="TDefaultCommand">The type of the default command.</typeparam>
public sealed class CommandApp<TDefaultCommand> : ICommandApp
where TDefaultCommand : class, ICommand
{
private readonly CommandApp _app;
/// <summary>
/// The entry point for a command line application with a default command.
/// Initializes a new instance of the <see cref="CommandApp{TDefaultCommand}"/> class.
/// </summary>
/// <typeparam name="TDefaultCommand">The type of the default command.</typeparam>
public sealed class CommandApp<TDefaultCommand> : ICommandApp
where TDefaultCommand : class, ICommand
/// <param name="registrar">The registrar.</param>
public CommandApp(ITypeRegistrar? registrar = null)
{
private readonly CommandApp _app;
/// <summary>
/// Initializes a new instance of the <see cref="CommandApp{TDefaultCommand}"/> class.
/// </summary>
/// <param name="registrar">The registrar.</param>
public CommandApp(ITypeRegistrar? registrar = null)
{
_app = new CommandApp(registrar);
_app.GetConfigurator().SetDefaultCommand<TDefaultCommand>();
}
/// <summary>
/// Configures the command line application.
/// </summary>
/// <param name="configuration">The configuration.</param>
public void Configure(Action<IConfigurator> configuration)
{
_app.Configure(configuration);
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public int Run(IEnumerable<string> args)
{
return _app.Run(args);
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public Task<int> RunAsync(IEnumerable<string> args)
{
return _app.RunAsync(args);
}
_app = new CommandApp(registrar);
_app.GetConfigurator().SetDefaultCommand<TDefaultCommand>();
}
}
/// <summary>
/// Configures the command line application.
/// </summary>
/// <param name="configuration">The configuration.</param>
public void Configure(Action<IConfigurator> configuration)
{
_app.Configure(configuration);
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public int Run(IEnumerable<string> args)
{
return _app.Run(args);
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public Task<int> RunAsync(IEnumerable<string> args)
{
return _app.RunAsync(args);
}
}

View File

@ -2,80 +2,79 @@ using System;
using System.Linq;
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents errors that occur during configuration.
/// </summary>
public class CommandConfigurationException : CommandAppException
{
/// <summary>
/// Represents errors that occur during configuration.
/// </summary>
public class CommandConfigurationException : CommandAppException
internal override bool AlwaysPropagateWhenDebugging => true;
internal CommandConfigurationException(string message, IRenderable? pretty = null)
: base(message, pretty)
{
internal override bool AlwaysPropagateWhenDebugging => true;
internal CommandConfigurationException(string message, IRenderable? pretty = null)
: base(message, pretty)
{
}
internal CommandConfigurationException(string message, Exception ex, IRenderable? pretty = null)
: base(message, ex, pretty)
{
}
internal static CommandConfigurationException NoCommandConfigured()
{
return new CommandConfigurationException("No commands have been configured.");
}
internal static CommandConfigurationException CommandNameConflict(CommandInfo command, string alias)
{
return new CommandConfigurationException($"The alias '{alias}' for '{command.Name}' conflicts with another command.");
}
internal static CommandConfigurationException DuplicateOption(CommandInfo command, string[] options)
{
var keys = string.Join(", ", options.Select(x => x.Length > 1 ? $"--{x}" : $"-{x}"));
if (options.Length > 1)
{
return new CommandConfigurationException($"Options {keys} are duplicated in command '{command.Name}'.");
}
return new CommandConfigurationException($"Option {keys} is duplicated in command '{command.Name}'.");
}
internal static CommandConfigurationException BranchHasNoChildren(CommandInfo command)
{
throw new CommandConfigurationException($"The branch '{command.Name}' does not define any commands.");
}
internal static CommandConfigurationException TooManyVectorArguments(CommandInfo command)
{
throw new CommandConfigurationException($"The command '{command.Name}' specifies more than one vector argument.");
}
internal static CommandConfigurationException VectorArgumentNotSpecifiedLast(CommandInfo command)
{
throw new CommandConfigurationException($"The command '{command.Name}' specifies an argument vector that is not the last argument.");
}
internal static CommandConfigurationException OptionalOptionValueMustBeFlagWithValue(CommandOption option)
{
return new CommandConfigurationException($"The option '{option.GetOptionName()}' has an optional value but does not implement IFlagValue.");
}
internal static CommandConfigurationException OptionBothHasPairDeconstructorAndTypeParameter(CommandOption option)
{
return new CommandConfigurationException($"The option '{option.GetOptionName()}' is both marked as pair deconstructable and convertable.");
}
internal static CommandConfigurationException OptionTypeDoesNotSupportDeconstruction(CommandOption option)
{
return new CommandConfigurationException($"The option '{option.GetOptionName()}' is marked as " +
"pair deconstructable, but the underlying type does not support that.");
}
internal static CommandConfigurationException RequiredArgumentsCannotHaveDefaultValue(CommandArgument option)
{
return new CommandConfigurationException($"The required argument '{option.Value}' cannot have a default value.");
}
}
}
internal CommandConfigurationException(string message, Exception ex, IRenderable? pretty = null)
: base(message, ex, pretty)
{
}
internal static CommandConfigurationException NoCommandConfigured()
{
return new CommandConfigurationException("No commands have been configured.");
}
internal static CommandConfigurationException CommandNameConflict(CommandInfo command, string alias)
{
return new CommandConfigurationException($"The alias '{alias}' for '{command.Name}' conflicts with another command.");
}
internal static CommandConfigurationException DuplicateOption(CommandInfo command, string[] options)
{
var keys = string.Join(", ", options.Select(x => x.Length > 1 ? $"--{x}" : $"-{x}"));
if (options.Length > 1)
{
return new CommandConfigurationException($"Options {keys} are duplicated in command '{command.Name}'.");
}
return new CommandConfigurationException($"Option {keys} is duplicated in command '{command.Name}'.");
}
internal static CommandConfigurationException BranchHasNoChildren(CommandInfo command)
{
throw new CommandConfigurationException($"The branch '{command.Name}' does not define any commands.");
}
internal static CommandConfigurationException TooManyVectorArguments(CommandInfo command)
{
throw new CommandConfigurationException($"The command '{command.Name}' specifies more than one vector argument.");
}
internal static CommandConfigurationException VectorArgumentNotSpecifiedLast(CommandInfo command)
{
throw new CommandConfigurationException($"The command '{command.Name}' specifies an argument vector that is not the last argument.");
}
internal static CommandConfigurationException OptionalOptionValueMustBeFlagWithValue(CommandOption option)
{
return new CommandConfigurationException($"The option '{option.GetOptionName()}' has an optional value but does not implement IFlagValue.");
}
internal static CommandConfigurationException OptionBothHasPairDeconstructorAndTypeParameter(CommandOption option)
{
return new CommandConfigurationException($"The option '{option.GetOptionName()}' is both marked as pair deconstructable and convertable.");
}
internal static CommandConfigurationException OptionTypeDoesNotSupportDeconstruction(CommandOption option)
{
return new CommandConfigurationException($"The option '{option.GetOptionName()}' is marked as " +
"pair deconstructable, but the underlying type does not support that.");
}
internal static CommandConfigurationException RequiredArgumentsCannotHaveDefaultValue(CommandArgument option)
{
return new CommandConfigurationException($"The required argument '{option.Value}' cannot have a default value.");
}
}

View File

@ -1,45 +1,44 @@
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command context.
/// </summary>
public sealed class CommandContext
{
/// <summary>
/// Represents a command context.
/// Gets the remaining arguments.
/// </summary>
public sealed class CommandContext
/// <value>
/// The remaining arguments.
/// </value>
public IRemainingArguments Remaining { get; }
/// <summary>
/// Gets the name of the command.
/// </summary>
/// <value>
/// The name of the command.
/// </value>
public string Name { get; }
/// <summary>
/// Gets the data that was passed to the command during registration (if any).
/// </summary>
/// <value>
/// The command data.
/// </value>
public object? Data { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandContext"/> class.
/// </summary>
/// <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)
{
/// <summary>
/// Gets the remaining arguments.
/// </summary>
/// <value>
/// The remaining arguments.
/// </value>
public IRemainingArguments Remaining { get; }
/// <summary>
/// Gets the name of the command.
/// </summary>
/// <value>
/// The name of the command.
/// </value>
public string Name { get; }
/// <summary>
/// Gets the data that was passed to the command during registration (if any).
/// </summary>
/// <value>
/// The command data.
/// </value>
public object? Data { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandContext"/> class.
/// </summary>
/// <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)
{
Remaining = remaining ?? throw new System.ArgumentNullException(nameof(remaining));
Name = name ?? throw new System.ArgumentNullException(nameof(name));
Data = data;
}
Remaining = remaining ?? throw new System.ArgumentNullException(nameof(remaining));
Name = name ?? throw new System.ArgumentNullException(nameof(name));
Data = data;
}
}
}

View File

@ -2,52 +2,51 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Base class for a command.
/// </summary>
/// <typeparam name="TSettings">The settings type.</typeparam>
/// <seealso cref="AsyncCommand{TSettings}"/>
public abstract class Command<TSettings> : ICommand<TSettings>
where TSettings : CommandSettings
{
/// <summary>
/// Base class for a command.
/// Validates the specified settings and remaining arguments.
/// </summary>
/// <typeparam name="TSettings">The settings type.</typeparam>
/// <seealso cref="AsyncCommand{TSettings}"/>
public abstract class Command<TSettings> : ICommand<TSettings>
where TSettings : CommandSettings
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
public virtual ValidationResult Validate([NotNull] CommandContext context, [NotNull] TSettings settings)
{
/// <summary>
/// Validates the specified settings and remaining arguments.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
public virtual ValidationResult Validate([NotNull] CommandContext context, [NotNull] TSettings settings)
{
return ValidationResult.Success();
}
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract int Execute([NotNull] CommandContext context, [NotNull] TSettings settings);
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
{
return Validate(context, (TSettings)settings);
}
/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
{
Debug.Assert(settings is TSettings, "Command settings is of unexpected type.");
return Task.FromResult(Execute(context, (TSettings)settings));
}
/// <inheritdoc/>
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings)
{
return Task.FromResult(Execute(context, settings));
}
return ValidationResult.Success();
}
}
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract int Execute([NotNull] CommandContext context, [NotNull] TSettings settings);
/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
{
return Validate(context, (TSettings)settings);
}
/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
{
Debug.Assert(settings is TSettings, "Command settings is of unexpected type.");
return Task.FromResult(Execute(context, (TSettings)settings));
}
/// <inheritdoc/>
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings)
{
return Task.FromResult(Execute(context, settings));
}
}

View File

@ -1,38 +1,37 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a context for <see cref="ICommandParameterInfo"/> related operations.
/// </summary>
public sealed class CommandParameterContext
{
/// <summary>
/// Represents a context for <see cref="ICommandParameterInfo"/> related operations.
/// Gets the parameter.
/// </summary>
public sealed class CommandParameterContext
public ICommandParameterInfo Parameter { get; }
/// <summary>
/// Gets the type resolver.
/// </summary>
public ITypeResolver Resolver { get; }
/// <summary>
/// Gets tje parameter value.
/// </summary>
public object? Value { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandParameterContext"/> class.
/// </summary>
/// <param name="parameter">The parameter.</param>
/// <param name="resolver">The type resolver.</param>
/// <param name="value">The parameter value.</param>
public CommandParameterContext(ICommandParameterInfo parameter, ITypeResolver resolver, object? value)
{
/// <summary>
/// Gets the parameter.
/// </summary>
public ICommandParameterInfo Parameter { get; }
/// <summary>
/// Gets the type resolver.
/// </summary>
public ITypeResolver Resolver { get; }
/// <summary>
/// Gets tje parameter value.
/// </summary>
public object? Value { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandParameterContext"/> class.
/// </summary>
/// <param name="parameter">The parameter.</param>
/// <param name="resolver">The type resolver.</param>
/// <param name="value">The parameter value.</param>
public CommandParameterContext(ICommandParameterInfo parameter, ITypeResolver resolver, object? value)
{
Parameter = parameter ?? throw new ArgumentNullException(nameof(parameter));
Resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
Value = value;
}
Parameter = parameter ?? throw new ArgumentNullException(nameof(parameter));
Resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
Value = value;
}
}
}

View File

@ -3,125 +3,124 @@ using System.Collections.Generic;
using System.Globalization;
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents errors that occur during parsing.
/// </summary>
public sealed class CommandParseException : CommandRuntimeException
{
/// <summary>
/// Represents errors that occur during parsing.
/// </summary>
public sealed class CommandParseException : CommandRuntimeException
internal CommandParseException(string message, IRenderable? pretty = null)
: base(message, pretty)
{
internal CommandParseException(string message, IRenderable? pretty = null)
: base(message, pretty)
{
}
internal static CommandParseException CouldNotCreateSettings(Type settingsType)
{
return new CommandParseException($"Could not create settings of type '{settingsType.FullName}'.");
}
internal static CommandParseException CouldNotCreateCommand(Type? commandType)
{
if (commandType == null)
{
return new CommandParseException($"Could not create command. Command type is unknown.");
}
return new CommandParseException($"Could not create command of type '{commandType.FullName}'.");
}
internal static CommandParseException ExpectedTokenButFoundNull(CommandTreeToken.Kind expected)
{
return new CommandParseException($"Expected to find any token of type '{expected}' but found null instead.");
}
internal static CommandParseException ExpectedTokenButFoundOther(CommandTreeToken.Kind expected, CommandTreeToken.Kind found)
{
return new CommandParseException($"Expected to find token of type '{expected}' but found '{found}' instead.");
}
internal static CommandParseException OptionHasNoName(string input, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(input, token, "Option does not have a name.", "Did you forget the option name?");
}
internal static CommandParseException OptionValueWasExpected(string input, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(input, token, "Expected an option value.", "Did you forget the option value?");
}
internal static CommandParseException OptionHasNoValue(IEnumerable<string> args, CommandTreeToken token, CommandOption option)
{
return CommandLineParseExceptionFactory.Create(args, token, $"Option '{option.GetOptionName()}' is defined but no value has been provided.", "No value provided.");
}
internal static CommandParseException UnexpectedOption(IEnumerable<string> args, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(args, token, $"Unexpected option '{token.Value}'.", "Did you forget the command?");
}
internal static CommandParseException CannotAssignValueToFlag(IEnumerable<string> args, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(args, token, "Flags cannot be assigned a value.", "Can't assign value.");
}
internal static CommandParseException InvalidShortOptionName(string input, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(input, token, "Short option does not have a valid name.", "Not a valid name for a short option.");
}
internal static CommandParseException LongOptionNameIsMissing(TextBuffer reader, int position)
{
var token = new CommandTreeToken(CommandTreeToken.Kind.LongOption, position, string.Empty, "--");
return CommandLineParseExceptionFactory.Create(reader.Original, token, "Invalid long option name.", "Did you forget the option name?");
}
internal static CommandParseException LongOptionNameIsOneCharacter(TextBuffer reader, int position, string name)
{
var token = new CommandTreeToken(CommandTreeToken.Kind.LongOption, position, name, $"--{name}");
var reason = $"Did you mean -{name}?";
return CommandLineParseExceptionFactory.Create(reader.Original, token, "Invalid long option name.", reason);
}
internal static CommandParseException LongOptionNameStartWithDigit(TextBuffer reader, int position, string name)
{
var token = new CommandTreeToken(CommandTreeToken.Kind.LongOption, position, name, $"--{name}");
return CommandLineParseExceptionFactory.Create(reader.Original, token, "Invalid long option name.", "Option names cannot start with a digit.");
}
internal static CommandParseException LongOptionNameContainSymbol(TextBuffer reader, int position, char character)
{
var name = character.ToString(CultureInfo.InvariantCulture);
var token = new CommandTreeToken(CommandTreeToken.Kind.LongOption, position, name, name);
return CommandLineParseExceptionFactory.Create(reader.Original, token, "Invalid long option name.", "Invalid character.");
}
internal static CommandParseException UnterminatedQuote(string input, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(input, token, $"Encountered unterminated quoted string '{token.Value}'.", "Did you forget the closing quotation mark?");
}
internal static CommandParseException UnknownCommand(CommandModel model, CommandTree? node, IEnumerable<string> args, CommandTreeToken token)
{
var suggestion = CommandSuggestor.Suggest(model, node?.Command, token.Value);
var text = suggestion != null ? $"Did you mean '{suggestion.Name}'?" : "No such command.";
return CommandLineParseExceptionFactory.Create(args, token, $"Unknown command '{token.Value}'.", text);
}
internal static CommandParseException CouldNotMatchArgument(IEnumerable<string> args, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(args, token, $"Could not match '{token.Value}' with an argument.", "Could not match to argument.");
}
internal static CommandParseException UnknownOption(IEnumerable<string> args, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(args, token, $"Unknown option '{token.Value}'.", "Unknown option.");
}
internal static CommandParseException ValueIsNotInValidFormat(string value)
{
var text = $"[red]Error:[/] The value '[white]{value}[/]' is not in a correct format";
return new CommandParseException("Could not parse value", new Markup(text));
}
}
}
internal static CommandParseException CouldNotCreateSettings(Type settingsType)
{
return new CommandParseException($"Could not create settings of type '{settingsType.FullName}'.");
}
internal static CommandParseException CouldNotCreateCommand(Type? commandType)
{
if (commandType == null)
{
return new CommandParseException($"Could not create command. Command type is unknown.");
}
return new CommandParseException($"Could not create command of type '{commandType.FullName}'.");
}
internal static CommandParseException ExpectedTokenButFoundNull(CommandTreeToken.Kind expected)
{
return new CommandParseException($"Expected to find any token of type '{expected}' but found null instead.");
}
internal static CommandParseException ExpectedTokenButFoundOther(CommandTreeToken.Kind expected, CommandTreeToken.Kind found)
{
return new CommandParseException($"Expected to find token of type '{expected}' but found '{found}' instead.");
}
internal static CommandParseException OptionHasNoName(string input, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(input, token, "Option does not have a name.", "Did you forget the option name?");
}
internal static CommandParseException OptionValueWasExpected(string input, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(input, token, "Expected an option value.", "Did you forget the option value?");
}
internal static CommandParseException OptionHasNoValue(IEnumerable<string> args, CommandTreeToken token, CommandOption option)
{
return CommandLineParseExceptionFactory.Create(args, token, $"Option '{option.GetOptionName()}' is defined but no value has been provided.", "No value provided.");
}
internal static CommandParseException UnexpectedOption(IEnumerable<string> args, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(args, token, $"Unexpected option '{token.Value}'.", "Did you forget the command?");
}
internal static CommandParseException CannotAssignValueToFlag(IEnumerable<string> args, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(args, token, "Flags cannot be assigned a value.", "Can't assign value.");
}
internal static CommandParseException InvalidShortOptionName(string input, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(input, token, "Short option does not have a valid name.", "Not a valid name for a short option.");
}
internal static CommandParseException LongOptionNameIsMissing(TextBuffer reader, int position)
{
var token = new CommandTreeToken(CommandTreeToken.Kind.LongOption, position, string.Empty, "--");
return CommandLineParseExceptionFactory.Create(reader.Original, token, "Invalid long option name.", "Did you forget the option name?");
}
internal static CommandParseException LongOptionNameIsOneCharacter(TextBuffer reader, int position, string name)
{
var token = new CommandTreeToken(CommandTreeToken.Kind.LongOption, position, name, $"--{name}");
var reason = $"Did you mean -{name}?";
return CommandLineParseExceptionFactory.Create(reader.Original, token, "Invalid long option name.", reason);
}
internal static CommandParseException LongOptionNameStartWithDigit(TextBuffer reader, int position, string name)
{
var token = new CommandTreeToken(CommandTreeToken.Kind.LongOption, position, name, $"--{name}");
return CommandLineParseExceptionFactory.Create(reader.Original, token, "Invalid long option name.", "Option names cannot start with a digit.");
}
internal static CommandParseException LongOptionNameContainSymbol(TextBuffer reader, int position, char character)
{
var name = character.ToString(CultureInfo.InvariantCulture);
var token = new CommandTreeToken(CommandTreeToken.Kind.LongOption, position, name, name);
return CommandLineParseExceptionFactory.Create(reader.Original, token, "Invalid long option name.", "Invalid character.");
}
internal static CommandParseException UnterminatedQuote(string input, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(input, token, $"Encountered unterminated quoted string '{token.Value}'.", "Did you forget the closing quotation mark?");
}
internal static CommandParseException UnknownCommand(CommandModel model, CommandTree? node, IEnumerable<string> args, CommandTreeToken token)
{
var suggestion = CommandSuggestor.Suggest(model, node?.Command, token.Value);
var text = suggestion != null ? $"Did you mean '{suggestion.Name}'?" : "No such command.";
return CommandLineParseExceptionFactory.Create(args, token, $"Unknown command '{token.Value}'.", text);
}
internal static CommandParseException CouldNotMatchArgument(IEnumerable<string> args, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(args, token, $"Could not match '{token.Value}' with an argument.", "Could not match to argument.");
}
internal static CommandParseException UnknownOption(IEnumerable<string> args, CommandTreeToken token)
{
return CommandLineParseExceptionFactory.Create(args, token, $"Unknown option '{token.Value}'.", "Unknown option.");
}
internal static CommandParseException ValueIsNotInValidFormat(string value)
{
var text = $"[red]Error:[/] The value '[white]{value}[/]' is not in a correct format";
return new CommandParseException("Could not parse value", new Markup(text));
}
}

View File

@ -1,58 +1,57 @@
using System;
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents errors that occur during runtime.
/// </summary>
public class CommandRuntimeException : CommandAppException
{
/// <summary>
/// Represents errors that occur during runtime.
/// </summary>
public class CommandRuntimeException : CommandAppException
internal CommandRuntimeException(string message, IRenderable? pretty = null)
: base(message, pretty)
{
internal CommandRuntimeException(string message, IRenderable? pretty = null)
: base(message, pretty)
{
}
internal CommandRuntimeException(string message, Exception ex, IRenderable? pretty = null)
: base(message, ex, pretty)
{
}
internal static CommandRuntimeException CouldNotResolveType(Type type, Exception? ex = null)
{
var message = $"Could not resolve type '{type.FullName}'.";
if (ex != null)
{
// TODO: Show internal stuff here.
return new CommandRuntimeException(message, ex);
}
return new CommandRuntimeException(message);
}
internal static CommandRuntimeException MissingRequiredArgument(CommandTree node, CommandArgument argument)
{
if (node.Command.Name == CliConstants.DefaultCommandName)
{
return new CommandRuntimeException($"Missing required argument '{argument.Value}'.");
}
return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{argument.Value}'.");
}
internal static CommandRuntimeException NoConverterFound(CommandParameter parameter)
{
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");
}
internal static CommandRuntimeException ValidationFailed(ValidationResult result)
{
return new CommandRuntimeException(result.Message ?? "Unknown validation error.");
}
internal static Exception CouldNotGetSettingsType(Type commandType)
{
return new CommandRuntimeException($"Could not get settings type for command of type '{commandType.FullName}'.");
}
}
}
internal CommandRuntimeException(string message, Exception ex, IRenderable? pretty = null)
: base(message, ex, pretty)
{
}
internal static CommandRuntimeException CouldNotResolveType(Type type, Exception? ex = null)
{
var message = $"Could not resolve type '{type.FullName}'.";
if (ex != null)
{
// TODO: Show internal stuff here.
return new CommandRuntimeException(message, ex);
}
return new CommandRuntimeException(message);
}
internal static CommandRuntimeException MissingRequiredArgument(CommandTree node, CommandArgument argument)
{
if (node.Command.Name == CliConstants.DefaultCommandName)
{
return new CommandRuntimeException($"Missing required argument '{argument.Value}'.");
}
return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{argument.Value}'.");
}
internal static CommandRuntimeException NoConverterFound(CommandParameter parameter)
{
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");
}
internal static CommandRuntimeException ValidationFailed(ValidationResult result)
{
return new CommandRuntimeException(result.Message ?? "Unknown validation error.");
}
internal static Exception CouldNotGetSettingsType(Type commandType)
{
return new CommandRuntimeException($"Could not get settings type for command of type '{commandType.FullName}'.");
}
}

View File

@ -1,17 +1,16 @@
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Base class for command settings.
/// </summary>
public abstract class CommandSettings
{
/// <summary>
/// Base class for command settings.
/// Performs validation of the settings.
/// </summary>
public abstract class CommandSettings
/// <returns>The validation result.</returns>
public virtual ValidationResult Validate()
{
/// <summary>
/// Performs validation of the settings.
/// </summary>
/// <returns>The validation result.</returns>
public virtual ValidationResult Validate()
{
return ValidationResult.Success();
}
return ValidationResult.Success();
}
}
}

View File

@ -1,160 +1,159 @@
using System.Globalization;
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents errors related to parameter templates.
/// </summary>
public sealed class CommandTemplateException : CommandConfigurationException
{
/// <summary>
/// Represents errors related to parameter templates.
/// Gets the template that contains the error.
/// </summary>
public sealed class CommandTemplateException : CommandConfigurationException
public string Template { get; }
internal override bool AlwaysPropagateWhenDebugging => true;
internal CommandTemplateException(string message, string template, IRenderable pretty)
: base(message, pretty)
{
/// <summary>
/// Gets the template that contains the error.
/// </summary>
public string Template { get; }
internal override bool AlwaysPropagateWhenDebugging => true;
internal CommandTemplateException(string message, string template, IRenderable pretty)
: base(message, pretty)
{
Template = template;
}
internal static CommandTemplateException UnexpectedCharacter(string template, int position, char character)
{
return CommandLineTemplateExceptionFactory.Create(
template,
new TemplateToken(TemplateToken.Kind.Unknown, position, $"{character}", $"{character}"),
$"Encountered unexpected character '{character}'.",
"Unexpected character.");
}
internal static CommandTemplateException UnterminatedValueName(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(
template, token,
$"Encountered unterminated value name '{token.Value}'.",
"Unterminated value name.");
}
internal static CommandTemplateException ArgumentCannotContainOptions(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(
template, token,
"Arguments can not contain options.",
"Not permitted.");
}
internal static CommandTemplateException MultipleValuesAreNotSupported(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Multiple values are not supported.",
"Too many values.");
}
internal static CommandTemplateException ValuesMustHaveName(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Values without name are not allowed.",
"Missing value name.");
}
internal static CommandTemplateException OptionsMustHaveName(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Options without name are not allowed.",
"Missing option name.");
}
internal static CommandTemplateException OptionNamesCannotStartWithDigit(string template, TemplateToken token)
{
// Rewrite the token to point to the option name instead of the whole string.
token = new TemplateToken(
token.TokenKind,
token.TokenKind == TemplateToken.Kind.ShortName ? token.Position + 1 : token.Position + 2,
token.Value, token.Value);
return CommandLineTemplateExceptionFactory.Create(template, token,
"Option names cannot start with a digit.",
"Invalid option name.");
}
internal static CommandTemplateException InvalidCharacterInOptionName(string template, TemplateToken token, char character)
{
// Rewrite the token to point to the invalid character instead of the whole value.
var position = (token.TokenKind == TemplateToken.Kind.ShortName
? token.Position + 1
: token.Position + 2) + token.Value.OrdinalIndexOf(character);
token = new TemplateToken(
token.TokenKind, position,
token.Value, character.ToString(CultureInfo.InvariantCulture));
return CommandLineTemplateExceptionFactory.Create(template, token,
$"Encountered invalid character '{character}' in option name.",
"Invalid character.");
}
internal static CommandTemplateException LongOptionMustHaveMoreThanOneCharacter(string template, TemplateToken token)
{
// Rewrite the token to point to the option name instead of the whole option.
token = new TemplateToken(token.TokenKind, token.Position + 2, token.Value, token.Value);
return CommandLineTemplateExceptionFactory.Create(template, token,
"Long option names must consist of more than one character.",
"Invalid option name.");
}
internal static CommandTemplateException MultipleShortOptionNamesNotAllowed(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Multiple short option names are not supported.",
"Too many short options.");
}
internal static CommandTemplateException ShortOptionMustOnlyBeOneCharacter(string template, TemplateToken token)
{
// Rewrite the token to point to the option name instead of the whole option.
token = new TemplateToken(token.TokenKind, token.Position + 1, token.Value, token.Value);
return CommandLineTemplateExceptionFactory.Create(template, token,
"Short option names can not be longer than one character.",
"Invalid option name.");
}
internal static CommandTemplateException MultipleOptionValuesAreNotSupported(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Multiple option values are not supported.",
"Too many option values.");
}
internal static CommandTemplateException InvalidCharacterInValueName(string template, TemplateToken token, char character)
{
// Rewrite the token to point to the invalid character instead of the whole value.
token = new TemplateToken(
token.TokenKind,
token.Position + 1 + token.Value.OrdinalIndexOf(character),
token.Value, character.ToString(CultureInfo.InvariantCulture));
return CommandLineTemplateExceptionFactory.Create(template, token,
$"Encountered invalid character '{character}' in value name.",
"Invalid character.");
}
internal static CommandTemplateException MissingLongAndShortName(string template, TemplateToken? token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"No long or short name for option has been specified.",
"Missing option. Was this meant to be an argument?");
}
internal static CommandTemplateException ArgumentsMustHaveValueName(string template)
{
return CommandLineTemplateExceptionFactory.Create(template, null,
"Arguments must have a value name.",
"Missing value name.");
}
Template = template;
}
}
internal static CommandTemplateException UnexpectedCharacter(string template, int position, char character)
{
return CommandLineTemplateExceptionFactory.Create(
template,
new TemplateToken(TemplateToken.Kind.Unknown, position, $"{character}", $"{character}"),
$"Encountered unexpected character '{character}'.",
"Unexpected character.");
}
internal static CommandTemplateException UnterminatedValueName(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(
template, token,
$"Encountered unterminated value name '{token.Value}'.",
"Unterminated value name.");
}
internal static CommandTemplateException ArgumentCannotContainOptions(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(
template, token,
"Arguments can not contain options.",
"Not permitted.");
}
internal static CommandTemplateException MultipleValuesAreNotSupported(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Multiple values are not supported.",
"Too many values.");
}
internal static CommandTemplateException ValuesMustHaveName(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Values without name are not allowed.",
"Missing value name.");
}
internal static CommandTemplateException OptionsMustHaveName(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Options without name are not allowed.",
"Missing option name.");
}
internal static CommandTemplateException OptionNamesCannotStartWithDigit(string template, TemplateToken token)
{
// Rewrite the token to point to the option name instead of the whole string.
token = new TemplateToken(
token.TokenKind,
token.TokenKind == TemplateToken.Kind.ShortName ? token.Position + 1 : token.Position + 2,
token.Value, token.Value);
return CommandLineTemplateExceptionFactory.Create(template, token,
"Option names cannot start with a digit.",
"Invalid option name.");
}
internal static CommandTemplateException InvalidCharacterInOptionName(string template, TemplateToken token, char character)
{
// Rewrite the token to point to the invalid character instead of the whole value.
var position = (token.TokenKind == TemplateToken.Kind.ShortName
? token.Position + 1
: token.Position + 2) + token.Value.OrdinalIndexOf(character);
token = new TemplateToken(
token.TokenKind, position,
token.Value, character.ToString(CultureInfo.InvariantCulture));
return CommandLineTemplateExceptionFactory.Create(template, token,
$"Encountered invalid character '{character}' in option name.",
"Invalid character.");
}
internal static CommandTemplateException LongOptionMustHaveMoreThanOneCharacter(string template, TemplateToken token)
{
// Rewrite the token to point to the option name instead of the whole option.
token = new TemplateToken(token.TokenKind, token.Position + 2, token.Value, token.Value);
return CommandLineTemplateExceptionFactory.Create(template, token,
"Long option names must consist of more than one character.",
"Invalid option name.");
}
internal static CommandTemplateException MultipleShortOptionNamesNotAllowed(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Multiple short option names are not supported.",
"Too many short options.");
}
internal static CommandTemplateException ShortOptionMustOnlyBeOneCharacter(string template, TemplateToken token)
{
// Rewrite the token to point to the option name instead of the whole option.
token = new TemplateToken(token.TokenKind, token.Position + 1, token.Value, token.Value);
return CommandLineTemplateExceptionFactory.Create(template, token,
"Short option names can not be longer than one character.",
"Invalid option name.");
}
internal static CommandTemplateException MultipleOptionValuesAreNotSupported(string template, TemplateToken token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"Multiple option values are not supported.",
"Too many option values.");
}
internal static CommandTemplateException InvalidCharacterInValueName(string template, TemplateToken token, char character)
{
// Rewrite the token to point to the invalid character instead of the whole value.
token = new TemplateToken(
token.TokenKind,
token.Position + 1 + token.Value.OrdinalIndexOf(character),
token.Value, character.ToString(CultureInfo.InvariantCulture));
return CommandLineTemplateExceptionFactory.Create(template, token,
$"Encountered invalid character '{character}' in value name.",
"Invalid character.");
}
internal static CommandTemplateException MissingLongAndShortName(string template, TemplateToken? token)
{
return CommandLineTemplateExceptionFactory.Create(template, token,
"No long or short name for option has been specified.",
"Missing option. Was this meant to be an argument?");
}
internal static CommandTemplateException ArgumentsMustHaveValueName(string template)
{
return CommandLineTemplateExceptionFactory.Create(template, null,
"Arguments must have a value name.",
"Missing value name.");
}
}

View File

@ -1,262 +1,261 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Contains extensions for <see cref="IConfigurator"/>
/// and <see cref="IConfigurator{TSettings}"/>.
/// </summary>
public static class ConfiguratorExtensions
{
/// <summary>
/// Contains extensions for <see cref="IConfigurator"/>
/// and <see cref="IConfigurator{TSettings}"/>.
/// Sets the name of the application.
/// </summary>
public static class ConfiguratorExtensions
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the application.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetApplicationName(this IConfigurator configurator, string name)
{
/// <summary>
/// Sets the name of the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the application.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetApplicationName(this IConfigurator configurator, string name)
if (configurator == null)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ApplicationName = name;
return configurator;
throw new ArgumentNullException(nameof(configurator));
}
/// <summary>
/// Overrides the auto-detected version of the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="version">The version of application.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetApplicationVersion(this IConfigurator configurator, string version)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ApplicationVersion = version;
return configurator;
}
/// <summary>
/// Configures the console.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="console">The console.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator ConfigureConsole(this IConfigurator configurator, IAnsiConsole console)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.Console = console;
return configurator;
}
/// <summary>
/// Sets the parsing mode to strict.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator UseStrictParsing(this IConfigurator configurator)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.StrictParsing = true;
return configurator;
}
/// <summary>
/// Tells the command line application to propagate all
/// exceptions to the user.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator PropagateExceptions(this IConfigurator configurator)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.PropagateExceptions = true;
return configurator;
}
/// <summary>
/// Configures case sensitivity.
/// </summary>
/// <param name="configurator">The configuration.</param>
/// <param name="sensitivity">The case sensitivity.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator CaseSensitivity(this IConfigurator configurator, CaseSensitivity sensitivity)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.CaseSensitivity = sensitivity;
return configurator;
}
/// <summary>
/// Tells the command line application to validate all
/// examples before running the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator ValidateExamples(this IConfigurator configurator)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ValidateExamples = true;
return configurator;
}
/// <summary>
/// Sets the command interceptor to be used.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="interceptor">A <see cref="ICommandInterceptor"/>.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetInterceptor(this IConfigurator configurator, ICommandInterceptor interceptor)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.Interceptor = interceptor;
return configurator;
}
/// <summary>
/// Adds a command branch.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configuration.</param>
public static void AddBranch(
this IConfigurator configurator,
string name,
Action<IConfigurator<CommandSettings>> action)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.AddBranch(name, action);
}
/// <summary>
/// Adds a command branch.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configuration.</param>
public static void AddBranch<TSettings>(
this IConfigurator<TSettings> configurator,
string name,
Action<IConfigurator<TSettings>> action)
where TSettings : CommandSettings
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.AddBranch(name, action);
}
/// <summary>
/// Adds a command without settings that executes a delegate.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command.</param>
/// <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(
this IConfigurator configurator,
string name,
Func<CommandContext, int> func)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
return configurator.AddDelegate<EmptyCommandSettings>(name, (c, _) => func(c));
}
/// <summary>
/// Adds a command without settings that executes a delegate.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command.</param>
/// <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,
string name,
Func<CommandContext, int> func)
where TSettings : CommandSettings
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
return configurator.AddDelegate<TSettings>(name, (c, _) => func(c));
}
/// <summary>
/// Sets the ExceptionsHandler.
/// <para>Setting <see cref="ICommandAppSettings.ExceptionHandler"/> this way will use the
/// default exit code of -1.</para>
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="exceptionHandler">The Action that handles the exception.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Action<Exception> exceptionHandler)
{
return configurator.SetExceptionHandler(ex =>
{
exceptionHandler(ex);
return -1;
});
}
/// <summary>
/// Sets the ExceptionsHandler.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="exceptionHandler">The Action that handles the exception.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Func<Exception, int>? exceptionHandler)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ExceptionHandler = exceptionHandler;
return configurator;
}
configurator.Settings.ApplicationName = name;
return configurator;
}
}
/// <summary>
/// Overrides the auto-detected version of the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="version">The version of application.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetApplicationVersion(this IConfigurator configurator, string version)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ApplicationVersion = version;
return configurator;
}
/// <summary>
/// Configures the console.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="console">The console.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator ConfigureConsole(this IConfigurator configurator, IAnsiConsole console)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.Console = console;
return configurator;
}
/// <summary>
/// Sets the parsing mode to strict.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator UseStrictParsing(this IConfigurator configurator)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.StrictParsing = true;
return configurator;
}
/// <summary>
/// Tells the command line application to propagate all
/// exceptions to the user.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator PropagateExceptions(this IConfigurator configurator)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.PropagateExceptions = true;
return configurator;
}
/// <summary>
/// Configures case sensitivity.
/// </summary>
/// <param name="configurator">The configuration.</param>
/// <param name="sensitivity">The case sensitivity.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator CaseSensitivity(this IConfigurator configurator, CaseSensitivity sensitivity)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.CaseSensitivity = sensitivity;
return configurator;
}
/// <summary>
/// Tells the command line application to validate all
/// examples before running the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator ValidateExamples(this IConfigurator configurator)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ValidateExamples = true;
return configurator;
}
/// <summary>
/// Sets the command interceptor to be used.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="interceptor">A <see cref="ICommandInterceptor"/>.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetInterceptor(this IConfigurator configurator, ICommandInterceptor interceptor)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.Interceptor = interceptor;
return configurator;
}
/// <summary>
/// Adds a command branch.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configuration.</param>
public static void AddBranch(
this IConfigurator configurator,
string name,
Action<IConfigurator<CommandSettings>> action)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.AddBranch(name, action);
}
/// <summary>
/// Adds a command branch.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configuration.</param>
public static void AddBranch<TSettings>(
this IConfigurator<TSettings> configurator,
string name,
Action<IConfigurator<TSettings>> action)
where TSettings : CommandSettings
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.AddBranch(name, action);
}
/// <summary>
/// Adds a command without settings that executes a delegate.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command.</param>
/// <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(
this IConfigurator configurator,
string name,
Func<CommandContext, int> func)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
return configurator.AddDelegate<EmptyCommandSettings>(name, (c, _) => func(c));
}
/// <summary>
/// Adds a command without settings that executes a delegate.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command.</param>
/// <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,
string name,
Func<CommandContext, int> func)
where TSettings : CommandSettings
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
return configurator.AddDelegate<TSettings>(name, (c, _) => func(c));
}
/// <summary>
/// Sets the ExceptionsHandler.
/// <para>Setting <see cref="ICommandAppSettings.ExceptionHandler"/> this way will use the
/// default exit code of -1.</para>
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="exceptionHandler">The Action that handles the exception.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Action<Exception> exceptionHandler)
{
return configurator.SetExceptionHandler(ex =>
{
exceptionHandler(ex);
return -1;
});
}
/// <summary>
/// Sets the ExceptionsHandler.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="exceptionHandler">The Action that handles the exception.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Func<Exception, int>? exceptionHandler)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ExceptionHandler = exceptionHandler;
return configurator;
}
}

View File

@ -1,9 +1,8 @@
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents empty settings.
/// </summary>
public sealed class EmptyCommandSettings : CommandSettings
{
/// <summary>
/// Represents empty settings.
/// </summary>
public sealed class EmptyCommandSettings : CommandSettings
{
}
}
}

View File

@ -1,59 +1,58 @@
using System;
using System.Globalization;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Implementation of a flag with an optional value.
/// </summary>
/// <typeparam name="T">The flag's element type.</typeparam>
public sealed class FlagValue<T> : IFlagValue
{
/// <summary>
/// Implementation of a flag with an optional value.
/// Gets or sets a value indicating whether or not the flag was set or not.
/// </summary>
/// <typeparam name="T">The flag's element type.</typeparam>
public sealed class FlagValue<T> : IFlagValue
{
/// <summary>
/// Gets or sets a value indicating whether or not the flag was set or not.
/// </summary>
public bool IsSet { get; set; }
public bool IsSet { get; set; }
#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
/// <summary>
/// Gets or sets the flag's value.
/// </summary>
public T Value { get; set; }
/// <summary>
/// Gets or sets the flag's value.
/// </summary>
public T Value { get; set; }
#pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable.
/// <inheritdoc/>
Type IFlagValue.Type => typeof(T);
/// <inheritdoc/>
Type IFlagValue.Type => typeof(T);
/// <inheritdoc/>
object? IFlagValue.Value
/// <inheritdoc/>
object? IFlagValue.Value
{
get => Value;
set
{
get => Value;
set
{
#pragma warning disable CS8601 // Possible null reference assignment.
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
Value = (T)value;
Value = (T)value;
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning restore CS8601 // Possible null reference assignment.
}
}
/// <inheritdoc/>
public override string ToString()
{
var flag = (IFlagValue)this;
if (flag.Value != null)
{
return string.Format(
CultureInfo.InvariantCulture,
"Set={0}, Value={1}",
IsSet,
flag.Value.ToString());
}
return string.Format(
CultureInfo.InvariantCulture,
"Set={0}", IsSet);
}
}
}
/// <inheritdoc/>
public override string ToString()
{
var flag = (IFlagValue)this;
if (flag.Value != null)
{
return string.Format(
CultureInfo.InvariantCulture,
"Set={0}, Value={1}",
IsSet,
flag.Value.ToString());
}
return string.Format(
CultureInfo.InvariantCulture,
"Set={0}", IsSet);
}
}

View File

@ -1,26 +1,25 @@
using System.Threading.Tasks;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command.
/// </summary>
public interface ICommand
{
/// <summary>
/// Represents a command.
/// Validates the specified settings and remaining arguments.
/// </summary>
public interface ICommand
{
/// <summary>
/// Validates the specified settings and remaining arguments.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
ValidationResult Validate(CommandContext context, CommandSettings settings);
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
ValidationResult Validate(CommandContext context, CommandSettings settings);
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
Task<int> Execute(CommandContext context, CommandSettings settings);
}
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns>
Task<int> Execute(CommandContext context, CommandSettings settings);
}

View File

@ -2,31 +2,30 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command line application.
/// </summary>
public interface ICommandApp
{
/// <summary>
/// Represents a command line application.
/// Configures the command line application.
/// </summary>
public interface ICommandApp
{
/// <summary>
/// Configures the command line application.
/// </summary>
/// <param name="configuration">The configuration.</param>
void Configure(Action<IConfigurator> configuration);
/// <param name="configuration">The configuration.</param>
void Configure(Action<IConfigurator> configuration);
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
int Run(IEnumerable<string> args);
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
int Run(IEnumerable<string> args);
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
Task<int> RunAsync(IEnumerable<string> args);
}
/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
Task<int> RunAsync(IEnumerable<string> args);
}

View File

@ -1,64 +1,63 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command line application settings.
/// </summary>
public interface ICommandAppSettings
{
/// <summary>
/// Represents a command line application settings.
/// Gets or sets the application name.
/// </summary>
public interface ICommandAppSettings
{
/// <summary>
/// Gets or sets the application name.
/// </summary>
string? ApplicationName { get; set; }
string? ApplicationName { get; set; }
/// <summary>
/// Gets or sets the application version (use it to override auto-detected value).
/// </summary>
string? ApplicationVersion { get; set; }
/// <summary>
/// Gets or sets the application version (use it to override auto-detected value).
/// </summary>
string? ApplicationVersion { get; set; }
/// <summary>
/// Gets or sets the <see cref="IAnsiConsole"/>.
/// </summary>
IAnsiConsole? Console { get; set; }
/// <summary>
/// Gets or sets the <see cref="IAnsiConsole"/>.
/// </summary>
IAnsiConsole? Console { get; set; }
/// <summary>
/// Gets or sets the <see cref="ICommandInterceptor"/> used
/// to intercept settings before it's being sent to the command.
/// </summary>
ICommandInterceptor? Interceptor { get; set; }
/// <summary>
/// Gets or sets the <see cref="ICommandInterceptor"/> used
/// to intercept settings before it's being sent to the command.
/// </summary>
ICommandInterceptor? Interceptor { get; set; }
/// <summary>
/// Gets the type registrar.
/// </summary>
ITypeRegistrarFrontend Registrar { get; }
/// <summary>
/// Gets the type registrar.
/// </summary>
ITypeRegistrarFrontend Registrar { get; }
/// <summary>
/// Gets or sets case sensitivity.
/// </summary>
CaseSensitivity CaseSensitivity { get; set; }
/// <summary>
/// Gets or sets case sensitivity.
/// </summary>
CaseSensitivity CaseSensitivity { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not parsing is strict.
/// </summary>
bool StrictParsing { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not parsing is strict.
/// </summary>
bool StrictParsing { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not exceptions should be propagated.
/// <para>Setting this to <c>true</c> will disable default Exception handling and
/// any <see cref="ExceptionHandler"/>, if set.</para>
/// </summary>
bool PropagateExceptions { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not exceptions should be propagated.
/// <para>Setting this to <c>true</c> will disable default Exception handling and
/// any <see cref="ExceptionHandler"/>, if set.</para>
/// </summary>
bool PropagateExceptions { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not examples should be validated.
/// </summary>
bool ValidateExamples { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not examples should be validated.
/// </summary>
bool ValidateExamples { get; set; }
/// <summary>
/// Gets or sets a handler for Exceptions.
/// <para>This handler will not be called, if <see cref="PropagateExceptions"/> is set to <c>true</c>.</para>
/// </summary>
public Func<Exception, int>? ExceptionHandler { get; set; }
}
/// <summary>
/// Gets or sets a handler for Exceptions.
/// <para>This handler will not be called, if <see cref="PropagateExceptions"/> is set to <c>true</c>.</para>
/// </summary>
public Func<Exception, int>? ExceptionHandler { get; set; }
}

View File

@ -1,44 +1,43 @@
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command configurator.
/// </summary>
public interface ICommandConfigurator
{
/// <summary>
/// Represents a command configurator.
/// Adds an example of how to use the command.
/// </summary>
public interface ICommandConfigurator
{
/// <summary>
/// Adds an example of how to use the command.
/// </summary>
/// <param name="args">The example arguments.</param>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator WithExample(string[] args);
/// <param name="args">The example arguments.</param>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator WithExample(string[] args);
/// <summary>
/// Adds an alias (an alternative name) to the command being configured.
/// </summary>
/// <param name="name">The alias to add to the command being configured.</param>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator WithAlias(string name);
/// <summary>
/// Adds an alias (an alternative name) to the command being configured.
/// </summary>
/// <param name="name">The alias to add to the command being configured.</param>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator WithAlias(string name);
/// <summary>
/// Sets the description of the command.
/// </summary>
/// <param name="description">The command description.</param>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator WithDescription(string description);
/// <summary>
/// Sets the description of the command.
/// </summary>
/// <param name="description">The command description.</param>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator WithDescription(string description);
/// <summary>
/// Sets data that will be passed to the command via the <see cref="CommandContext"/>.
/// </summary>
/// <param name="data">The data to pass to the command.</param>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator WithData(object data);
/// <summary>
/// Sets data that will be passed to the command via the <see cref="CommandContext"/>.
/// </summary>
/// <param name="data">The data to pass to the command.</param>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator WithData(object data);
/// <summary>
/// Marks the command as hidden.
/// Hidden commands do not show up in help documentation or
/// generated XML models.
/// </summary>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator IsHidden();
}
/// <summary>
/// Marks the command as hidden.
/// Hidden commands do not show up in help documentation or
/// generated XML models.
/// </summary>
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
ICommandConfigurator IsHidden();
}

View File

@ -1,17 +1,16 @@
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command settings interceptor that
/// will intercept command settings before it's
/// passed to a command.
/// </summary>
public interface ICommandInterceptor
{
/// <summary>
/// Represents a command settings interceptor that
/// will intercept command settings before it's
/// passed to a command.
/// Intercepts command information before it's passed to a command.
/// </summary>
public interface ICommandInterceptor
{
/// <summary>
/// Intercepts command information before it's passed to a command.
/// </summary>
/// <param name="context">The intercepted <see cref="CommandContext"/>.</param>
/// <param name="settings">The intercepted <see cref="CommandSettings"/>.</param>
void Intercept(CommandContext context, CommandSettings settings);
}
}
/// <param name="context">The intercepted <see cref="CommandContext"/>.</param>
/// <param name="settings">The intercepted <see cref="CommandSettings"/>.</param>
void Intercept(CommandContext context, CommandSettings settings);
}

View File

@ -1,12 +1,11 @@
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command limiter.
/// </summary>
/// <typeparam name="TSettings">The type of the settings to limit to.</typeparam>
/// <seealso cref="ICommand" />
public interface ICommandLimiter<out TSettings> : ICommand
where TSettings : CommandSettings
{
/// <summary>
/// Represents a command limiter.
/// </summary>
/// <typeparam name="TSettings">The type of the settings to limit to.</typeparam>
/// <seealso cref="ICommand" />
public interface ICommandLimiter<out TSettings> : ICommand
where TSettings : CommandSettings
{
}
}
}

View File

@ -1,20 +1,19 @@
using System.Threading.Tasks;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command.
/// </summary>
/// <typeparam name="TSettings">The settings type.</typeparam>
public interface ICommand<TSettings> : ICommandLimiter<TSettings>
where TSettings : CommandSettings
{
/// <summary>
/// Represents a command.
/// Executes the command.
/// </summary>
/// <typeparam name="TSettings">The settings type.</typeparam>
public interface ICommand<TSettings> : ICommandLimiter<TSettings>
where TSettings : CommandSettings
{
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
Task<int> Execute(CommandContext context, TSettings settings);
}
}
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
Task<int> Execute(CommandContext context, TSettings settings);
}

View File

@ -1,27 +1,26 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a command parameter.
/// </summary>
public interface ICommandParameterInfo
{
/// <summary>
/// Represents a command parameter.
/// Gets the property name.
/// </summary>
public interface ICommandParameterInfo
{
/// <summary>
/// Gets the property name.
/// </summary>
/// <value>The property name.</value>
public string PropertyName { get; }
/// <value>The property name.</value>
public string PropertyName { get; }
/// <summary>
/// Gets the parameter type.
/// </summary>
public Type ParameterType { get; }
/// <summary>
/// Gets the parameter type.
/// </summary>
public Type ParameterType { get; }
/// <summary>
/// Gets the description.
/// </summary>
/// <value>The description.</value>
public string? Description { get; }
}
/// <summary>
/// Gets the description.
/// </summary>
/// <value>The description.</value>
public string? Description { get; }
}

View File

@ -1,49 +1,48 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a configurator.
/// </summary>
public interface IConfigurator
{
/// <summary>
/// Represents a configurator.
/// Gets the command app settings.
/// </summary>
public interface IConfigurator
{
/// <summary>
/// Gets the command app settings.
/// </summary>
public ICommandAppSettings Settings { get; }
public ICommandAppSettings Settings { get; }
/// <summary>
/// Adds an example of how to use the application.
/// </summary>
/// <param name="args">The example arguments.</param>
void AddExample(string[] args);
/// <summary>
/// Adds an example of how to use the application.
/// </summary>
/// <param name="args">The example arguments.</param>
void AddExample(string[] args);
/// <summary>
/// Adds a command.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommand;
/// <summary>
/// Adds a command.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommand;
/// <summary>
/// Adds a command that executes a delegate.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <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>
ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TSettings, int> func)
where TSettings : CommandSettings;
/// <summary>
/// Adds a command that executes a delegate.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <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>
ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TSettings, int> func)
where TSettings : CommandSettings;
/// <summary>
/// Adds a command branch.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configurator.</param>
void AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
where TSettings : CommandSettings;
}
/// <summary>
/// Adds a command branch.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configurator.</param>
void AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
where TSettings : CommandSettings;
}

View File

@ -1,59 +1,58 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a configurator for specific settings.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
public interface IConfigurator<in TSettings>
where TSettings : CommandSettings
{
/// <summary>
/// Represents a configurator for specific settings.
/// Sets the description of the branch.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
public interface IConfigurator<in TSettings>
where TSettings : CommandSettings
{
/// <summary>
/// Sets the description of the branch.
/// </summary>
/// <param name="description">The description of the branch.</param>
void SetDescription(string description);
/// <param name="description">The description of the branch.</param>
void SetDescription(string description);
/// <summary>
/// Adds an example of how to use the branch.
/// </summary>
/// <param name="args">The example arguments.</param>
void AddExample(string[] args);
/// <summary>
/// Adds an example of how to use the branch.
/// </summary>
/// <param name="args">The example arguments.</param>
void AddExample(string[] args);
/// <summary>
/// Marks the branch as hidden.
/// Hidden branches do not show up in help documentation or
/// generated XML models.
/// </summary>
void HideBranch();
/// <summary>
/// Marks the branch as hidden.
/// Hidden branches do not show up in help documentation or
/// generated XML models.
/// </summary>
void HideBranch();
/// <summary>
/// Adds a command.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommandLimiter<TSettings>;
/// <summary>
/// Adds a command.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommandLimiter<TSettings>;
/// <summary>
/// Adds a command that executes a delegate.
/// </summary>
/// <typeparam name="TDerivedSettings">The derived command setting type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <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>
ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, int> func)
where TDerivedSettings : TSettings;
/// <summary>
/// Adds a command that executes a delegate.
/// </summary>
/// <typeparam name="TDerivedSettings">The derived command setting type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <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>
ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, int> func)
where TDerivedSettings : TSettings;
/// <summary>
/// Adds a command branch.
/// </summary>
/// <typeparam name="TDerivedSettings">The derived command setting type.</typeparam>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configuration.</param>
void AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action)
where TDerivedSettings : TSettings;
}
}
/// <summary>
/// Adds a command branch.
/// </summary>
/// <typeparam name="TDerivedSettings">The derived command setting type.</typeparam>
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configuration.</param>
void AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action)
where TDerivedSettings : TSettings;
}

View File

@ -1,25 +1,24 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a flag with an optional value.
/// </summary>
public interface IFlagValue
{
/// <summary>
/// Represents a flag with an optional value.
/// Gets or sets a value indicating whether or not the flag was set or not.
/// </summary>
public interface IFlagValue
{
/// <summary>
/// Gets or sets a value indicating whether or not the flag was set or not.
/// </summary>
bool IsSet { get; set; }
bool IsSet { get; set; }
/// <summary>
/// Gets the flag's element type.
/// </summary>
Type Type { get; }
/// <summary>
/// Gets the flag's element type.
/// </summary>
Type Type { get; }
/// <summary>
/// Gets or sets the flag's value.
/// </summary>
object? Value { get; set; }
}
}
/// <summary>
/// Gets or sets the flag's value.
/// </summary>
object? Value { get; set; }
}

View File

@ -1,21 +1,20 @@
using System.Collections.Generic;
using System.Linq;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents the remaining arguments.
/// </summary>
public interface IRemainingArguments
{
/// <summary>
/// Represents the remaining arguments.
/// Gets the parsed remaining arguments.
/// </summary>
public interface IRemainingArguments
{
/// <summary>
/// Gets the parsed remaining arguments.
/// </summary>
ILookup<string, string?> Parsed { get; }
ILookup<string, string?> Parsed { get; }
/// <summary>
/// Gets the raw, non-parsed remaining arguments.
/// </summary>
IReadOnlyList<string> Raw { get; }
}
}
/// <summary>
/// Gets the raw, non-parsed remaining arguments.
/// </summary>
IReadOnlyList<string> Raw { get; }
}

View File

@ -1,38 +1,37 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a type registrar.
/// </summary>
public interface ITypeRegistrar
{
/// <summary>
/// Represents a type registrar.
/// Registers the specified service.
/// </summary>
public interface ITypeRegistrar
{
/// <summary>
/// Registers the specified service.
/// </summary>
/// <param name="service">The service.</param>
/// <param name="implementation">The implementation.</param>
void Register(Type service, Type implementation);
/// <param name="service">The service.</param>
/// <param name="implementation">The implementation.</param>
void Register(Type service, Type implementation);
/// <summary>
/// Registers the specified instance.
/// </summary>
/// <param name="service">The service.</param>
/// <param name="implementation">The implementation.</param>
void RegisterInstance(Type service, object implementation);
/// <summary>
/// Registers the specified instance.
/// </summary>
/// <param name="service">The service.</param>
/// <param name="implementation">The implementation.</param>
void RegisterInstance(Type service, object implementation);
/// <summary>
/// Registers the specified instance lazily.
/// </summary>
/// <param name="service">The service.</param>
/// <param name="factory">The factory that creates the implementation.</param>
void RegisterLazy(Type service, Func<object> factory);
/// <summary>
/// Registers the specified instance lazily.
/// </summary>
/// <param name="service">The service.</param>
/// <param name="factory">The factory that creates the implementation.</param>
void RegisterLazy(Type service, Func<object> factory);
/// <summary>
/// Builds the type resolver representing the registrations
/// specified in the current instance.
/// </summary>
/// <returns>A type resolver.</returns>
ITypeResolver Build();
}
}
/// <summary>
/// Builds the type resolver representing the registrations
/// specified in the current instance.
/// </summary>
/// <returns>A type resolver.</returns>
ITypeResolver Build();
}

View File

@ -1,32 +1,31 @@
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a user friendly frontend for a <see cref="ITypeRegistrar"/>.
/// </summary>
public interface ITypeRegistrarFrontend
{
/// <summary>
/// Represents a user friendly frontend for a <see cref="ITypeRegistrar"/>.
/// Registers the type with the type registrar as a singleton.
/// </summary>
public interface ITypeRegistrarFrontend
{
/// <summary>
/// Registers the type with the type registrar as a singleton.
/// </summary>
/// <typeparam name="TService">The exposed service type.</typeparam>
/// <typeparam name="TImplementation">The implementing type.</typeparam>
void Register<TService, TImplementation>()
where TImplementation : TService;
/// <typeparam name="TService">The exposed service type.</typeparam>
/// <typeparam name="TImplementation">The implementing type.</typeparam>
void Register<TService, TImplementation>()
where TImplementation : TService;
/// <summary>
/// Registers the specified instance with the type registrar as a singleton.
/// </summary>
/// <typeparam name="TImplementation">The type of the instance.</typeparam>
/// <param name="instance">The instance to register.</param>
void RegisterInstance<TImplementation>(TImplementation instance);
/// <summary>
/// Registers the specified instance with the type registrar as a singleton.
/// </summary>
/// <typeparam name="TImplementation">The type of the instance.</typeparam>
/// <param name="instance">The instance to register.</param>
void RegisterInstance<TImplementation>(TImplementation instance);
/// <summary>
/// Registers the specified instance with the type registrar as a singleton.
/// </summary>
/// <typeparam name="TService">The exposed service type.</typeparam>
/// <typeparam name="TImplementation"> implementing type.</typeparam>
/// <param name="instance">The instance to register.</param>
void RegisterInstance<TService, TImplementation>(TImplementation instance)
where TImplementation : TService;
}
}
/// <summary>
/// Registers the specified instance with the type registrar as a singleton.
/// </summary>
/// <typeparam name="TService">The exposed service type.</typeparam>
/// <typeparam name="TImplementation"> implementing type.</typeparam>
/// <param name="instance">The instance to register.</param>
void RegisterInstance<TService, TImplementation>(TImplementation instance)
where TImplementation : TService;
}

View File

@ -1,17 +1,16 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Represents a type resolver.
/// </summary>
public interface ITypeResolver
{
/// <summary>
/// Represents a type resolver.
/// Resolves an instance of the specified type.
/// </summary>
public interface ITypeResolver
{
/// <summary>
/// Resolves an instance of the specified type.
/// </summary>
/// <param name="type">The type to resolve.</param>
/// <returns>An instance of the specified type, or <c>null</c> if no registration for the specified type exists.</returns>
object? Resolve(Type? type);
}
}
/// <param name="type">The type to resolve.</param>
/// <returns>An instance of the specified type, or <c>null</c> if no registration for the specified type exists.</returns>
object? Resolve(Type? type);
}

View File

@ -2,54 +2,53 @@ using System;
using System.Collections.Generic;
using System.Reflection;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal static class CommandConstructorBinder
{
internal static class CommandConstructorBinder
public static CommandSettings CreateSettings(CommandValueLookup lookup, ConstructorInfo constructor, ITypeResolver resolver)
{
public static CommandSettings CreateSettings(CommandValueLookup lookup, ConstructorInfo constructor, ITypeResolver resolver)
if (constructor.DeclaringType == null)
{
if (constructor.DeclaringType == null)
{
throw new InvalidOperationException("Cannot create settings since constructor have no declaring type.");
}
var parameters = new List<object?>();
var mapped = new HashSet<Guid>();
foreach (var parameter in constructor.GetParameters())
{
if (lookup.TryGetParameterWithName(parameter.Name, out var result))
{
parameters.Add(result.Value);
mapped.Add(result.Parameter.Id);
}
else
{
var value = resolver.Resolve(parameter.ParameterType);
if (value == null)
{
throw CommandRuntimeException.CouldNotResolveType(parameter.ParameterType);
}
parameters.Add(value);
}
}
// Create the settings.
if (!(Activator.CreateInstance(constructor.DeclaringType, parameters.ToArray()) is CommandSettings settings))
{
throw new InvalidOperationException("Could not create settings");
}
// Try to do property injection for parameters that wasn't injected.
foreach (var (parameter, value) in lookup)
{
if (!mapped.Contains(parameter.Id) && parameter.Property.SetMethod != null)
{
parameter.Property.SetValue(settings, value);
}
}
return settings;
throw new InvalidOperationException("Cannot create settings since constructor have no declaring type.");
}
var parameters = new List<object?>();
var mapped = new HashSet<Guid>();
foreach (var parameter in constructor.GetParameters())
{
if (lookup.TryGetParameterWithName(parameter.Name, out var result))
{
parameters.Add(result.Value);
mapped.Add(result.Parameter.Id);
}
else
{
var value = resolver.Resolve(parameter.ParameterType);
if (value == null)
{
throw CommandRuntimeException.CouldNotResolveType(parameter.ParameterType);
}
parameters.Add(value);
}
}
// Create the settings.
if (!(Activator.CreateInstance(constructor.DeclaringType, parameters.ToArray()) is CommandSettings settings))
{
throw new InvalidOperationException("Could not create settings");
}
// Try to do property injection for parameters that wasn't injected.
foreach (var (parameter, value) in lookup)
{
if (!mapped.Contains(parameter.Id) && parameter.Property.SetMethod != null)
{
parameter.Property.SetValue(settings, value);
}
}
return settings;
}
}
}

View File

@ -1,44 +1,43 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal static class CommandPropertyBinder
{
internal static class CommandPropertyBinder
public static CommandSettings CreateSettings(CommandValueLookup lookup, Type settingsType, ITypeResolver resolver)
{
public static CommandSettings CreateSettings(CommandValueLookup lookup, Type settingsType, ITypeResolver resolver)
var settings = CreateSettings(resolver, settingsType);
foreach (var (parameter, value) in lookup)
{
var settings = CreateSettings(resolver, settingsType);
foreach (var (parameter, value) in lookup)
if (value != default)
{
if (value != default)
{
parameter.Property.SetValue(settings, value);
}
parameter.Property.SetValue(settings, value);
}
}
// Validate the settings.
var validationResult = settings.Validate();
if (!validationResult.Successful)
{
throw CommandRuntimeException.ValidationFailed(validationResult);
}
// Validate the settings.
var validationResult = settings.Validate();
if (!validationResult.Successful)
{
throw CommandRuntimeException.ValidationFailed(validationResult);
}
return settings;
}
private static CommandSettings CreateSettings(ITypeResolver resolver, Type settingsType)
{
if (resolver.Resolve(settingsType) is CommandSettings settings)
{
return settings;
}
private static CommandSettings CreateSettings(ITypeResolver resolver, Type settingsType)
if (Activator.CreateInstance(settingsType) is CommandSettings instance)
{
if (resolver.Resolve(settingsType) is CommandSettings settings)
{
return settings;
}
if (Activator.CreateInstance(settingsType) is CommandSettings instance)
{
return instance;
}
throw CommandParseException.CouldNotCreateSettings(settingsType);
return instance;
}
throw CommandParseException.CouldNotCreateSettings(settingsType);
}
}
}

View File

@ -1,118 +1,117 @@
using System;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal sealed class CommandValueBinder
{
internal sealed class CommandValueBinder
private readonly CommandValueLookup _lookup;
public CommandValueBinder(CommandValueLookup lookup)
{
private readonly CommandValueLookup _lookup;
_lookup = lookup;
}
public CommandValueBinder(CommandValueLookup lookup)
public void Bind(CommandParameter parameter, ITypeResolver resolver, object? value)
{
if (parameter.ParameterKind == ParameterKind.Pair)
{
_lookup = lookup;
value = GetLookup(parameter, resolver, value);
}
else if (parameter.ParameterKind == ParameterKind.Vector)
{
value = GetArray(parameter, value);
}
else if (parameter.ParameterKind == ParameterKind.FlagWithValue)
{
value = GetFlag(parameter, value);
}
public void Bind(CommandParameter parameter, ITypeResolver resolver, object? value)
_lookup.SetValue(parameter, value);
}
private object GetLookup(CommandParameter parameter, ITypeResolver resolver, object? value)
{
var genericTypes = parameter.Property.PropertyType.GetGenericArguments();
var multimap = (IMultiMap?)_lookup.GetValue(parameter);
if (multimap == null)
{
if (parameter.ParameterKind == ParameterKind.Pair)
{
value = GetLookup(parameter, resolver, value);
}
else if (parameter.ParameterKind == ParameterKind.Vector)
{
value = GetArray(parameter, value);
}
else if (parameter.ParameterKind == ParameterKind.FlagWithValue)
{
value = GetFlag(parameter, value);
}
_lookup.SetValue(parameter, value);
}
private object GetLookup(CommandParameter parameter, ITypeResolver resolver, object? value)
{
var genericTypes = parameter.Property.PropertyType.GetGenericArguments();
var multimap = (IMultiMap?)_lookup.GetValue(parameter);
multimap = Activator.CreateInstance(typeof(MultiMap<,>).MakeGenericType(genericTypes[0], genericTypes[1])) as IMultiMap;
if (multimap == null)
{
multimap = Activator.CreateInstance(typeof(MultiMap<,>).MakeGenericType(genericTypes[0], genericTypes[1])) as IMultiMap;
if (multimap == null)
{
throw new InvalidOperationException("Could not create multimap");
}
throw new InvalidOperationException("Could not create multimap");
}
// Create deconstructor.
var deconstructorType = parameter.PairDeconstructor?.Type ?? typeof(DefaultPairDeconstructor);
if (!(resolver.Resolve(deconstructorType) is IPairDeconstructor deconstructor))
{
if (!(Activator.CreateInstance(deconstructorType) is IPairDeconstructor activatedDeconstructor))
{
throw new InvalidOperationException($"Could not create pair deconstructor.");
}
deconstructor = activatedDeconstructor;
}
// Deconstruct and add to multimap.
var pair = deconstructor.Deconstruct(resolver, genericTypes[0], genericTypes[1], value as string);
if (pair.Key != null)
{
multimap.Add(pair);
}
return multimap;
}
private object GetArray(CommandParameter parameter, object? value)
// Create deconstructor.
var deconstructorType = parameter.PairDeconstructor?.Type ?? typeof(DefaultPairDeconstructor);
if (!(resolver.Resolve(deconstructorType) is IPairDeconstructor deconstructor))
{
// Add a new item to the array
var array = (Array?)_lookup.GetValue(parameter);
Array newArray;
var elementType = parameter.Property.PropertyType.GetElementType();
if (elementType == null)
if (!(Activator.CreateInstance(deconstructorType) is IPairDeconstructor activatedDeconstructor))
{
throw new InvalidOperationException("Could not get property type.");
throw new InvalidOperationException($"Could not create pair deconstructor.");
}
if (array == null)
{
newArray = Array.CreateInstance(elementType, 1);
}
else
{
newArray = Array.CreateInstance(elementType, array.Length + 1);
array.CopyTo(newArray, 0);
}
newArray.SetValue(value, newArray.Length - 1);
return newArray;
deconstructor = activatedDeconstructor;
}
private object GetFlag(CommandParameter parameter, object? value)
// Deconstruct and add to multimap.
var pair = deconstructor.Deconstruct(resolver, genericTypes[0], genericTypes[1], value as string);
if (pair.Key != null)
{
var flagValue = (IFlagValue?)_lookup.GetValue(parameter);
multimap.Add(pair);
}
return multimap;
}
private object GetArray(CommandParameter parameter, object? value)
{
// Add a new item to the array
var array = (Array?)_lookup.GetValue(parameter);
Array newArray;
var elementType = parameter.Property.PropertyType.GetElementType();
if (elementType == null)
{
throw new InvalidOperationException("Could not get property type.");
}
if (array == null)
{
newArray = Array.CreateInstance(elementType, 1);
}
else
{
newArray = Array.CreateInstance(elementType, array.Length + 1);
array.CopyTo(newArray, 0);
}
newArray.SetValue(value, newArray.Length - 1);
return newArray;
}
private object GetFlag(CommandParameter parameter, object? value)
{
var flagValue = (IFlagValue?)_lookup.GetValue(parameter);
if (flagValue == null)
{
flagValue = (IFlagValue?)Activator.CreateInstance(parameter.ParameterType);
if (flagValue == null)
{
flagValue = (IFlagValue?)Activator.CreateInstance(parameter.ParameterType);
if (flagValue == null)
{
throw new InvalidOperationException("Could not create flag value.");
}
throw new InvalidOperationException("Could not create flag value.");
}
if (value != null)
{
// Null means set, but not with a valid value.
flagValue.Value = value;
}
// If the parameter was mapped, then it's set.
flagValue.IsSet = true;
return flagValue;
}
if (value != null)
{
// Null means set, but not with a valid value.
flagValue.Value = value;
}
// If the parameter was mapped, then it's set.
flagValue.IsSet = true;
return flagValue;
}
}
}

View File

@ -3,61 +3,60 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal sealed class CommandValueLookup : IEnumerable<(CommandParameter Parameter, object? Value)>
{
internal sealed class CommandValueLookup : IEnumerable<(CommandParameter Parameter, object? Value)>
private readonly Dictionary<Guid, (CommandParameter Parameter, object? Value)> _lookup;
public CommandValueLookup()
{
private readonly Dictionary<Guid, (CommandParameter Parameter, object? Value)> _lookup;
_lookup = new Dictionary<Guid, (CommandParameter, object?)>();
}
public CommandValueLookup()
public IEnumerator<(CommandParameter Parameter, object? Value)> GetEnumerator()
{
foreach (var pair in _lookup)
{
_lookup = new Dictionary<Guid, (CommandParameter, object?)>();
yield return pair.Value;
}
}
public IEnumerator<(CommandParameter Parameter, object? Value)> GetEnumerator()
public bool HasParameterWithName(string? name)
{
if (name == null)
{
foreach (var pair in _lookup)
{
yield return pair.Value;
}
}
public bool HasParameterWithName(string? name)
{
if (name == null)
{
return false;
}
return _lookup.Values.Any(pair => pair.Parameter.PropertyName.Equals(name, StringComparison.OrdinalIgnoreCase));
}
public bool TryGetParameterWithName(string? name, out (CommandParameter Parameter, object? Value) result)
{
if (HasParameterWithName(name))
{
result = _lookup.Values.FirstOrDefault(pair => pair.Parameter.PropertyName.Equals(name, StringComparison.OrdinalIgnoreCase));
return true;
}
result = default;
return false;
}
public object? GetValue(CommandParameter parameter)
{
_lookup.TryGetValue(parameter.Id, out var result);
return result.Value;
}
public void SetValue(CommandParameter parameter, object? value)
{
_lookup[parameter.Id] = (parameter, value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
return _lookup.Values.Any(pair => pair.Parameter.PropertyName.Equals(name, StringComparison.OrdinalIgnoreCase));
}
}
public bool TryGetParameterWithName(string? name, out (CommandParameter Parameter, object? Value) result)
{
if (HasParameterWithName(name))
{
result = _lookup.Values.FirstOrDefault(pair => pair.Parameter.PropertyName.Equals(name, StringComparison.OrdinalIgnoreCase));
return true;
}
result = default;
return false;
}
public object? GetValue(CommandParameter parameter)
{
_lookup.TryGetValue(parameter.Id, out var result);
return result.Value;
}
public void SetValue(CommandParameter parameter, object? value)
{
_lookup[parameter.Id] = (parameter, value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

View File

@ -2,166 +2,165 @@ using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal static class CommandValueResolver
{
internal static class CommandValueResolver
public static CommandValueLookup GetParameterValues(CommandTree? tree, ITypeResolver resolver)
{
public static CommandValueLookup GetParameterValues(CommandTree? tree, ITypeResolver resolver)
var lookup = new CommandValueLookup();
var binder = new CommandValueBinder(lookup);
CommandValidator.ValidateRequiredParameters(tree);
while (tree != null)
{
var lookup = new CommandValueLookup();
var binder = new CommandValueBinder(lookup);
CommandValidator.ValidateRequiredParameters(tree);
while (tree != null)
// Process unmapped parameters.
foreach (var parameter in tree.Unmapped)
{
// Process unmapped parameters.
foreach (var parameter in tree.Unmapped)
// Got a value provider?
if (parameter.ValueProvider != null)
{
// Got a value provider?
if (parameter.ValueProvider != null)
var context = new CommandParameterContext(parameter, resolver, null);
if (parameter.ValueProvider.TryGetValue(context, out var result))
{
var context = new CommandParameterContext(parameter, resolver, null);
if (parameter.ValueProvider.TryGetValue(context, out var result))
{
result = ConvertValue(resolver, lookup, binder, parameter, result);
result = ConvertValue(resolver, lookup, binder, parameter, result);
lookup.SetValue(parameter, result);
CommandValidator.ValidateParameter(parameter, lookup, resolver);
continue;
}
lookup.SetValue(parameter, result);
CommandValidator.ValidateParameter(parameter, lookup, resolver);
continue;
}
if (parameter.IsFlagValue())
{
// Set the flag value to an empty, not set instance.
var instance = Activator.CreateInstance(parameter.ParameterType);
lookup.SetValue(parameter, instance);
}
else
{
// Is this an option with a default value?
if (parameter.DefaultValue != null)
{
var value = parameter.DefaultValue?.Value;
value = ConvertValue(resolver, lookup, binder, parameter, value);
binder.Bind(parameter, resolver, value);
CommandValidator.ValidateParameter(parameter, lookup, resolver);
}
else if (Nullable.GetUnderlyingType(parameter.ParameterType) != null ||
!parameter.ParameterType.IsValueType)
{
lookup.SetValue(parameter, null);
}
}
}
// Process mapped parameters.
foreach (var mapped in tree.Mapped)
{
if (mapped.Parameter.WantRawValue)
{
// Just try to assign the raw value.
binder.Bind(mapped.Parameter, resolver, mapped.Value);
}
else
{
if (mapped.Parameter.IsFlagValue() && mapped.Value == null)
{
if (mapped.Parameter is CommandOption option && option.DefaultValue != null)
{
// Set the default value.
binder.Bind(mapped.Parameter, resolver, option.DefaultValue?.Value);
}
else
{
// Set the flag but not the value.
binder.Bind(mapped.Parameter, resolver, null);
}
}
else
{
var converter = GetConverter(lookup, binder, resolver, mapped.Parameter);
if (converter == null)
{
throw CommandRuntimeException.NoConverterFound(mapped.Parameter);
}
// Assign the value to the parameter.
binder.Bind(mapped.Parameter, resolver, converter.ConvertFromInvariantString(mapped.Value));
}
}
// Got a value provider?
if (mapped.Parameter.ValueProvider != null)
{
var context = new CommandParameterContext(mapped.Parameter, resolver, mapped.Value);
if (mapped.Parameter.ValueProvider.TryGetValue(context, out var result))
{
lookup.SetValue(mapped.Parameter, result);
}
}
CommandValidator.ValidateParameter(mapped.Parameter, lookup, resolver);
}
tree = tree.Next;
}
return lookup;
}
private static object? ConvertValue(ITypeResolver resolver, CommandValueLookup lookup, CommandValueBinder binder, CommandParameter parameter, object? result)
{
if (result != null && result.GetType() != parameter.ParameterType)
{
var converter = GetConverter(lookup, binder, resolver, parameter);
if (converter != null)
{
result = converter.ConvertFrom(result);
}
}
return result;
}
[SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "It's OK")]
private static TypeConverter? GetConverter(CommandValueLookup lookup, CommandValueBinder binder, ITypeResolver resolver, CommandParameter parameter)
{
if (parameter.Converter == null)
{
if (parameter.ParameterType.IsArray)
{
// Return a converter for each array item (not the whole array)
return TypeDescriptor.GetConverter(parameter.ParameterType.GetElementType());
}
if (parameter.IsFlagValue())
{
// Is the optional value instanciated?
var value = lookup.GetValue(parameter) as IFlagValue;
if (value == null)
{
// Try to assign it with a null value.
// This will create the optional value instance without a value.
binder.Bind(parameter, resolver, null);
value = lookup.GetValue(parameter) as IFlagValue;
if (value == null)
{
throw new InvalidOperationException("Could not intialize optional value.");
}
}
// Return a converter for the flag element type.
return TypeDescriptor.GetConverter(value.Type);
// Set the flag value to an empty, not set instance.
var instance = Activator.CreateInstance(parameter.ParameterType);
lookup.SetValue(parameter, instance);
}
else
{
// Is this an option with a default value?
if (parameter.DefaultValue != null)
{
var value = parameter.DefaultValue?.Value;
value = ConvertValue(resolver, lookup, binder, parameter, value);
return TypeDescriptor.GetConverter(parameter.ParameterType);
binder.Bind(parameter, resolver, value);
CommandValidator.ValidateParameter(parameter, lookup, resolver);
}
else if (Nullable.GetUnderlyingType(parameter.ParameterType) != null ||
!parameter.ParameterType.IsValueType)
{
lookup.SetValue(parameter, null);
}
}
}
var type = Type.GetType(parameter.Converter.ConverterTypeName);
return resolver.Resolve(type) as TypeConverter;
// Process mapped parameters.
foreach (var mapped in tree.Mapped)
{
if (mapped.Parameter.WantRawValue)
{
// Just try to assign the raw value.
binder.Bind(mapped.Parameter, resolver, mapped.Value);
}
else
{
if (mapped.Parameter.IsFlagValue() && mapped.Value == null)
{
if (mapped.Parameter is CommandOption option && option.DefaultValue != null)
{
// Set the default value.
binder.Bind(mapped.Parameter, resolver, option.DefaultValue?.Value);
}
else
{
// Set the flag but not the value.
binder.Bind(mapped.Parameter, resolver, null);
}
}
else
{
var converter = GetConverter(lookup, binder, resolver, mapped.Parameter);
if (converter == null)
{
throw CommandRuntimeException.NoConverterFound(mapped.Parameter);
}
// Assign the value to the parameter.
binder.Bind(mapped.Parameter, resolver, converter.ConvertFromInvariantString(mapped.Value));
}
}
// Got a value provider?
if (mapped.Parameter.ValueProvider != null)
{
var context = new CommandParameterContext(mapped.Parameter, resolver, mapped.Value);
if (mapped.Parameter.ValueProvider.TryGetValue(context, out var result))
{
lookup.SetValue(mapped.Parameter, result);
}
}
CommandValidator.ValidateParameter(mapped.Parameter, lookup, resolver);
}
tree = tree.Next;
}
return lookup;
}
}
private static object? ConvertValue(ITypeResolver resolver, CommandValueLookup lookup, CommandValueBinder binder, CommandParameter parameter, object? result)
{
if (result != null && result.GetType() != parameter.ParameterType)
{
var converter = GetConverter(lookup, binder, resolver, parameter);
if (converter != null)
{
result = converter.ConvertFrom(result);
}
}
return result;
}
[SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "It's OK")]
private static TypeConverter? GetConverter(CommandValueLookup lookup, CommandValueBinder binder, ITypeResolver resolver, CommandParameter parameter)
{
if (parameter.Converter == null)
{
if (parameter.ParameterType.IsArray)
{
// Return a converter for each array item (not the whole array)
return TypeDescriptor.GetConverter(parameter.ParameterType.GetElementType());
}
if (parameter.IsFlagValue())
{
// Is the optional value instanciated?
var value = lookup.GetValue(parameter) as IFlagValue;
if (value == null)
{
// Try to assign it with a null value.
// This will create the optional value instance without a value.
binder.Bind(parameter, resolver, null);
value = lookup.GetValue(parameter) as IFlagValue;
if (value == null)
{
throw new InvalidOperationException("Could not intialize optional value.");
}
}
// Return a converter for the flag element type.
return TypeDescriptor.GetConverter(value.Type);
}
return TypeDescriptor.GetConverter(parameter.ParameterType);
}
var type = Type.GetType(parameter.Converter.ConverterTypeName);
return resolver.Resolve(type) as TypeConverter;
}
}

View File

@ -1,14 +1,13 @@
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
/// <summary>
/// Representation of a multi map.
/// </summary>
internal interface IMultiMap
{
/// <summary>
/// Representation of a multi map.
/// Adds a key and a value to the multi map.
/// </summary>
internal interface IMultiMap
{
/// <summary>
/// Adds a key and a value to the multi map.
/// </summary>
/// <param name="pair">The pair to add.</param>
void Add((object? Key, object? Value) pair);
}
}
/// <param name="pair">The pair to add.</param>
void Add((object? Key, object? Value) pair);
}

View File

@ -4,172 +4,171 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class MultiMap<TKey, TValue> : IMultiMap, ILookup<TKey, TValue>, IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>
where TKey : notnull
{
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class MultiMap<TKey, TValue> : IMultiMap, ILookup<TKey, TValue>, IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>
where TKey : notnull
private readonly IDictionary<TKey, MultiMapGrouping> _lookup;
private readonly IDictionary<TKey, TValue> _dictionary;
public int Count => _lookup.Count;
public bool IsReadOnly => false;
public ICollection<TKey> Keys => _lookup.Keys;
public ICollection<TValue> Values => _dictionary.Values;
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => _lookup.Keys;
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => _dictionary.Values;
TValue IReadOnlyDictionary<TKey, TValue>.this[TKey key] => _dictionary[key];
TValue IDictionary<TKey, TValue>.this[TKey key]
{
private readonly IDictionary<TKey, MultiMapGrouping> _lookup;
private readonly IDictionary<TKey, TValue> _dictionary;
public int Count => _lookup.Count;
public bool IsReadOnly => false;
public ICollection<TKey> Keys => _lookup.Keys;
public ICollection<TValue> Values => _dictionary.Values;
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => _lookup.Keys;
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => _dictionary.Values;
TValue IReadOnlyDictionary<TKey, TValue>.this[TKey key] => _dictionary[key];
TValue IDictionary<TKey, TValue>.this[TKey key]
get
{
get
return _dictionary[key];
}
set
{
Add(key, value);
}
}
public IEnumerable<TValue> this[TKey key]
{
get
{
if (_lookup.TryGetValue(key, out var group))
{
return _dictionary[key];
}
set
{
Add(key, value);
return group;
}
return Array.Empty<TValue>();
}
}
public MultiMap()
{
_lookup = new Dictionary<TKey, MultiMapGrouping>();
_dictionary = new Dictionary<TKey, TValue>();
}
private sealed class MultiMapGrouping : IGrouping<TKey, TValue>
{
private readonly List<TValue> _items;
public TKey Key { get; }
public MultiMapGrouping(TKey key, List<TValue> items)
{
Key = key;
_items = items;
}
public IEnumerable<TValue> this[TKey key]
public void Add(TValue value)
{
get
{
if (_lookup.TryGetValue(key, out var group))
{
return group;
}
return Array.Empty<TValue>();
}
_items.Add(value);
}
public MultiMap()
public IEnumerator<TValue> GetEnumerator()
{
_lookup = new Dictionary<TKey, MultiMapGrouping>();
_dictionary = new Dictionary<TKey, TValue>();
}
private sealed class MultiMapGrouping : IGrouping<TKey, TValue>
{
private readonly List<TValue> _items;
public TKey Key { get; }
public MultiMapGrouping(TKey key, List<TValue> items)
{
Key = key;
_items = items;
}
public void Add(TValue value)
{
_items.Add(value);
}
public IEnumerator<TValue> GetEnumerator()
{
return _items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public bool Contains(TKey key)
{
return _lookup.ContainsKey(key);
}
public IEnumerator<IGrouping<TKey, TValue>> GetEnumerator()
{
foreach (var group in _lookup.Values)
{
yield return group;
}
return _items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public void Add(TKey key, TValue value)
public bool Contains(TKey key)
{
return _lookup.ContainsKey(key);
}
public IEnumerator<IGrouping<TKey, TValue>> GetEnumerator()
{
foreach (var group in _lookup.Values)
{
if (!_lookup.ContainsKey(key))
{
_lookup[key] = new MultiMapGrouping(key, new List<TValue>());
}
_lookup[key].Add(value);
_dictionary[key] = value;
}
public bool ContainsKey(TKey key)
{
return Contains(key);
}
public bool Remove(TKey key)
{
return _lookup.Remove(key);
}
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
return _dictionary.TryGetValue(key, out value);
}
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
public void Clear()
{
_lookup.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return Contains(item.Key);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_dictionary.CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Remove(item.Key);
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return _dictionary.GetEnumerator();
}
public void Add((object? Key, object? Value) pair)
{
if (pair.Key != null)
{
#pragma warning disable CS8604 // Possible null reference argument of value.
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
Add((TKey)pair.Key, (TValue)pair.Value);
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning restore CS8604 // Possible null reference argument of value.
}
yield return group;
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(TKey key, TValue value)
{
if (!_lookup.ContainsKey(key))
{
_lookup[key] = new MultiMapGrouping(key, new List<TValue>());
}
_lookup[key].Add(value);
_dictionary[key] = value;
}
public bool ContainsKey(TKey key)
{
return Contains(key);
}
public bool Remove(TKey key)
{
return _lookup.Remove(key);
}
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
return _dictionary.TryGetValue(key, out value);
}
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
public void Clear()
{
_lookup.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return Contains(item.Key);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_dictionary.CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Remove(item.Key);
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return _dictionary.GetEnumerator();
}
public void Add((object? Key, object? Value) pair)
{
if (pair.Key != null)
{
#pragma warning disable CS8604 // Possible null reference argument of value.
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
Add((TKey)pair.Key, (TValue)pair.Value);
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning restore CS8604 // Possible null reference argument of value.
}
}
}

View File

@ -1,31 +1,30 @@
using System;
namespace Spectre.Console.Cli
{
internal static class CommandBinder
{
public static CommandSettings Bind(CommandTree? tree, Type settingsType, ITypeResolver resolver)
{
var lookup = CommandValueResolver.GetParameterValues(tree, resolver);
namespace Spectre.Console.Cli;
// Got a constructor with at least one name corresponding to a settings?
foreach (var constructor in settingsType.GetConstructors())
internal static class CommandBinder
{
public static CommandSettings Bind(CommandTree? tree, Type settingsType, ITypeResolver resolver)
{
var lookup = CommandValueResolver.GetParameterValues(tree, resolver);
// Got a constructor with at least one name corresponding to a settings?
foreach (var constructor in settingsType.GetConstructors())
{
var parameters = constructor.GetParameters();
if (parameters.Length > 0)
{
var parameters = constructor.GetParameters();
if (parameters.Length > 0)
foreach (var parameter in parameters)
{
foreach (var parameter in parameters)
if (lookup.HasParameterWithName(parameter?.Name))
{
if (lookup.HasParameterWithName(parameter?.Name))
{
// Use constructor injection.
return CommandConstructorBinder.CreateSettings(lookup, constructor, resolver);
}
// Use constructor injection.
return CommandConstructorBinder.CreateSettings(lookup, constructor, resolver);
}
}
}
return CommandPropertyBinder.CreateSettings(lookup, settingsType, resolver);
}
return CommandPropertyBinder.CreateSettings(lookup, settingsType, resolver);
}
}
}

View File

@ -4,115 +4,114 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Spectre.Console.Cli
{
internal sealed class CommandExecutor
{
private readonly ITypeRegistrar _registrar;
namespace Spectre.Console.Cli;
public CommandExecutor(ITypeRegistrar registrar)
internal sealed class CommandExecutor
{
private readonly ITypeRegistrar _registrar;
public CommandExecutor(ITypeRegistrar registrar)
{
_registrar = registrar ?? throw new ArgumentNullException(nameof(registrar));
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
}
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
{
if (configuration == null)
{
_registrar = registrar ?? throw new ArgumentNullException(nameof(registrar));
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
throw new ArgumentNullException(nameof(configuration));
}
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
// Create the command model.
var model = CommandModelBuilder.Build(configuration);
_registrar.RegisterInstance(typeof(CommandModel), model);
_registrar.RegisterDependencies(model);
// No default command?
if (model.DefaultCommand == null)
{
if (configuration == null)
// Got at least one argument?
var firstArgument = args.FirstOrDefault();
if (firstArgument != null)
{
throw new ArgumentNullException(nameof(configuration));
}
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
// Create the command model.
var model = CommandModelBuilder.Build(configuration);
_registrar.RegisterInstance(typeof(CommandModel), model);
_registrar.RegisterDependencies(model);
// No default command?
if (model.DefaultCommand == null)
{
// Got at least one argument?
var firstArgument = args.FirstOrDefault();
if (firstArgument != null)
// Asking for version? Kind of a hack, but it's alright.
// We should probably make this a bit better in the future.
if (firstArgument.Equals("--version", StringComparison.OrdinalIgnoreCase) ||
firstArgument.Equals("-v", StringComparison.OrdinalIgnoreCase))
{
// Asking for version? Kind of a hack, but it's alright.
// We should probably make this a bit better in the future.
if (firstArgument.Equals("--version", StringComparison.OrdinalIgnoreCase) ||
firstArgument.Equals("-v", StringComparison.OrdinalIgnoreCase))
{
var console = configuration.Settings.Console.GetConsole();
console.WriteLine(ResolveApplicationVersion(configuration));
return 0;
}
var console = configuration.Settings.Console.GetConsole();
console.WriteLine(ResolveApplicationVersion(configuration));
return 0;
}
}
// Parse and map the model against the arguments.
var parser = new CommandTreeParser(model, configuration.Settings);
var parsedResult = parser.Parse(args);
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
// Currently the root?
if (parsedResult.Tree == null)
{
// Display help.
configuration.Settings.Console.SafeRender(HelpWriter.Write(model));
return 0;
}
// Get the command to execute.
var leaf = parsedResult.Tree.GetLeafCommand();
if (leaf.Command.IsBranch || leaf.ShowHelp)
{
// Branches can't be executed. Show help.
configuration.Settings.Console.SafeRender(HelpWriter.WriteCommand(model, leaf.Command));
return leaf.ShowHelp ? 0 : 1;
}
// Register the arguments with the container.
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
// Create the resolver and the context.
using (var resolver = new TypeResolverAdapter(_registrar.Build()))
{
var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data);
// Execute the command tree.
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
}
}
private static string ResolveApplicationVersion(IConfiguration configuration)
// Parse and map the model against the arguments.
var parser = new CommandTreeParser(model, configuration.Settings);
var parsedResult = parser.Parse(args);
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
// Currently the root?
if (parsedResult.Tree == null)
{
return
configuration.Settings.ApplicationVersion ?? // potential override
VersionHelper.GetVersion(Assembly.GetEntryAssembly());
// Display help.
configuration.Settings.Console.SafeRender(HelpWriter.Write(model));
return 0;
}
private static Task<int> Execute(
CommandTree leaf,
CommandTree tree,
CommandContext context,
ITypeResolver resolver,
IConfiguration configuration)
// Get the command to execute.
var leaf = parsedResult.Tree.GetLeafCommand();
if (leaf.Command.IsBranch || leaf.ShowHelp)
{
// Bind the command tree against the settings.
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
configuration.Settings.Interceptor?.Intercept(context, settings);
// Branches can't be executed. Show help.
configuration.Settings.Console.SafeRender(HelpWriter.WriteCommand(model, leaf.Command));
return leaf.ShowHelp ? 0 : 1;
}
// Create and validate the command.
var command = leaf.CreateCommand(resolver);
var validationResult = command.Validate(context, settings);
if (!validationResult.Successful)
{
throw CommandRuntimeException.ValidationFailed(validationResult);
}
// Register the arguments with the container.
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
// Execute the command.
return command.Execute(context, settings);
// Create the resolver and the context.
using (var resolver = new TypeResolverAdapter(_registrar.Build()))
{
var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data);
// Execute the command tree.
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
}
}
}
private static string ResolveApplicationVersion(IConfiguration configuration)
{
return
configuration.Settings.ApplicationVersion ?? // potential override
VersionHelper.GetVersion(Assembly.GetEntryAssembly());
}
private static Task<int> Execute(
CommandTree leaf,
CommandTree tree,
CommandContext context,
ITypeResolver resolver,
IConfiguration configuration)
{
// Bind the command tree against the settings.
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
configuration.Settings.Interceptor?.Intercept(context, settings);
// Create and validate the command.
var command = leaf.CreateCommand(resolver);
var validationResult = command.Validate(context, settings);
if (!validationResult.Successful)
{
throw CommandRuntimeException.ValidationFailed(validationResult);
}
// Execute the command.
return command.Execute(context, settings);
}
}

View File

@ -1,8 +1,7 @@
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal enum CommandPart
{
internal enum CommandPart
{
CommandName,
LongOption,
}
}
CommandName,
LongOption,
}

View File

@ -1,77 +1,76 @@
using System;
using System.Linq;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal static class CommandSuggestor
{
internal static class CommandSuggestor
private const float SmallestDistance = 2f;
public static CommandInfo? Suggest(CommandModel model, CommandInfo? command, string name)
{
private const float SmallestDistance = 2f;
var result = (CommandInfo?)null;
public static CommandInfo? Suggest(CommandModel model, CommandInfo? command, string name)
var container = command ?? (ICommandContainer)model;
if (command?.IsDefaultCommand ?? false)
{
var result = (CommandInfo?)null;
var container = command ?? (ICommandContainer)model;
if (command?.IsDefaultCommand ?? false)
{
// Default commands have no children,
// so use the root commands here.
container = model;
}
var score = float.MaxValue;
foreach (var child in container.Commands.Where(x => !x.IsHidden))
{
var temp = Score(child.Name, name);
if (temp < score)
{
score = temp;
result = child;
}
}
if (score <= SmallestDistance)
{
return result;
}
return null;
// Default commands have no children,
// so use the root commands here.
container = model;
}
private static float Score(string source, string target)
var score = float.MaxValue;
foreach (var child in container.Commands.Where(x => !x.IsHidden))
{
source = source.ToUpperInvariant();
target = target.ToUpperInvariant();
var n = source.Length;
var m = target.Length;
if (n == 0)
var temp = Score(child.Name, name);
if (temp < score)
{
return m;
score = temp;
result = child;
}
if (m == 0)
{
return n;
}
var d = new int[n + 1, m + 1];
Enumerable.Range(0, n + 1).ToList().ForEach(i => d[i, 0] = i);
Enumerable.Range(0, m + 1).ToList().ForEach(i => d[0, i] = i);
for (var sourceIndex = 1; sourceIndex <= n; sourceIndex++)
{
for (var targetIndex = 1; targetIndex <= m; targetIndex++)
{
var cost = (target[targetIndex - 1] == source[sourceIndex - 1]) ? 0 : 1;
d[sourceIndex, targetIndex] = Math.Min(
Math.Min(d[sourceIndex - 1, targetIndex] + 1, d[sourceIndex, targetIndex - 1] + 1),
d[sourceIndex - 1, targetIndex - 1] + cost);
}
}
return d[n, m];
}
if (score <= SmallestDistance)
{
return result;
}
return null;
}
}
private static float Score(string source, string target)
{
source = source.ToUpperInvariant();
target = target.ToUpperInvariant();
var n = source.Length;
var m = target.Length;
if (n == 0)
{
return m;
}
if (m == 0)
{
return n;
}
var d = new int[n + 1, m + 1];
Enumerable.Range(0, n + 1).ToList().ForEach(i => d[i, 0] = i);
Enumerable.Range(0, m + 1).ToList().ForEach(i => d[0, i] = i);
for (var sourceIndex = 1; sourceIndex <= n; sourceIndex++)
{
for (var targetIndex = 1; targetIndex <= m; targetIndex++)
{
var cost = (target[targetIndex - 1] == source[sourceIndex - 1]) ? 0 : 1;
d[sourceIndex, targetIndex] = Math.Min(
Math.Min(d[sourceIndex - 1, targetIndex] + 1, d[sourceIndex, targetIndex - 1] + 1),
d[sourceIndex - 1, targetIndex - 1] + cost);
}
}
return d[n, m];
}
}

View File

@ -1,46 +1,45 @@
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
internal static class CommandValidator
{
internal static class CommandValidator
public static void ValidateRequiredParameters(CommandTree? tree)
{
public static void ValidateRequiredParameters(CommandTree? tree)
var node = tree?.GetRootCommand();
while (node != null)
{
var node = tree?.GetRootCommand();
while (node != null)
foreach (var parameter in node.Unmapped)
{
foreach (var parameter in node.Unmapped)
if (parameter.Required)
{
if (parameter.Required)
switch (parameter)
{
switch (parameter)
{
case CommandArgument argument:
throw CommandRuntimeException.MissingRequiredArgument(node, argument);
}
case CommandArgument argument:
throw CommandRuntimeException.MissingRequiredArgument(node, argument);
}
}
node = node.Next;
}
node = node.Next;
}
}
public static void ValidateParameter(CommandParameter parameter, CommandValueLookup settings, ITypeResolver resolver)
public static void ValidateParameter(CommandParameter parameter, CommandValueLookup settings, ITypeResolver resolver)
{
var assignedValue = settings.GetValue(parameter);
foreach (var validator in parameter.Validators)
{
var assignedValue = settings.GetValue(parameter);
foreach (var validator in parameter.Validators)
var context = new CommandParameterContext(parameter, resolver, assignedValue);
var validationResult = validator.Validate(context);
if (!validationResult.Successful)
{
var context = new CommandParameterContext(parameter, resolver, assignedValue);
var validationResult = validator.Validate(context);
if (!validationResult.Successful)
{
// If there is an error message specified in the parameter validator attribute,
// then use that one, otherwise use the validation result.
var result = string.IsNullOrWhiteSpace(validator.ErrorMessage)
? validationResult
: ValidationResult.Error(validator.ErrorMessage);
// If there is an error message specified in the parameter validator attribute,
// then use that one, otherwise use the validation result.
var result = string.IsNullOrWhiteSpace(validator.ErrorMessage)
? validationResult
: ValidationResult.Error(validator.ErrorMessage);
throw CommandRuntimeException.ValidationFailed(result);
}
throw CommandRuntimeException.ValidationFailed(result);
}
}
}
}
}

View File

@ -1,272 +1,271 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
[Description("Displays diagnostics about CLI configurations")]
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class ExplainCommand : Command<ExplainCommand.Settings>
{
[Description("Displays diagnostics about CLI configurations")]
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class ExplainCommand : Command<ExplainCommand.Settings>
private readonly CommandModel _commandModel;
private readonly IAnsiConsole _writer;
public ExplainCommand(IConfiguration configuration, CommandModel commandModel)
{
private readonly CommandModel _commandModel;
private readonly IAnsiConsole _writer;
_commandModel = commandModel ?? throw new ArgumentNullException(nameof(commandModel));
_writer = configuration.Settings.Console.GetConsole();
}
public ExplainCommand(IConfiguration configuration, CommandModel commandModel)
public sealed class Settings : CommandSettings
{
public Settings(string[]? commands, bool? detailed, bool includeHidden)
{
_commandModel = commandModel ?? throw new ArgumentNullException(nameof(commandModel));
_writer = configuration.Settings.Console.GetConsole();
Commands = commands;
Detailed = detailed;
IncludeHidden = includeHidden;
}
public sealed class Settings : CommandSettings
[CommandArgument(0, "[command]")]
public string[]? Commands { get; }
[Description("Include detailed information about the commands.")]
[CommandOption("-d|--detailed")]
public bool? Detailed { get; }
[Description("Include hidden commands and options.")]
[CommandOption("--hidden")]
public bool IncludeHidden { get; }
}
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
{
var tree = new Tree("CLI Configuration");
tree.AddNode(ValueMarkup("Application Name", _commandModel.ApplicationName, "no application name"));
tree.AddNode(ValueMarkup("Parsing Mode", _commandModel.ParsingMode.ToString()));
if (settings.Commands == null || settings.Commands.Length == 0)
{
public Settings(string[]? commands, bool? detailed, bool includeHidden)
{
Commands = commands;
Detailed = detailed;
IncludeHidden = includeHidden;
}
// If there is a default command we'll want to include it in the list too.
var commands = _commandModel.DefaultCommand != null
? new[] { _commandModel.DefaultCommand }.Concat(_commandModel.Commands)
: _commandModel.Commands;
[CommandArgument(0, "[command]")]
public string[]? Commands { get; }
[Description("Include detailed information about the commands.")]
[CommandOption("-d|--detailed")]
public bool? Detailed { get; }
[Description("Include hidden commands and options.")]
[CommandOption("--hidden")]
public bool IncludeHidden { get; }
AddCommands(
tree.AddNode(ParentMarkup("Commands")),
commands,
settings.Detailed ?? false,
settings.IncludeHidden);
}
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
else
{
var tree = new Tree("CLI Configuration");
tree.AddNode(ValueMarkup("Application Name", _commandModel.ApplicationName, "no application name"));
tree.AddNode(ValueMarkup("Parsing Mode", _commandModel.ParsingMode.ToString()));
if (settings.Commands == null || settings.Commands.Length == 0)
var currentCommandTier = _commandModel.Commands;
CommandInfo? currentCommand = null;
foreach (var command in settings.Commands)
{
// If there is a default command we'll want to include it in the list too.
var commands = _commandModel.DefaultCommand != null
? new[] { _commandModel.DefaultCommand }.Concat(_commandModel.Commands)
: _commandModel.Commands;
AddCommands(
tree.AddNode(ParentMarkup("Commands")),
commands,
settings.Detailed ?? false,
settings.IncludeHidden);
}
else
{
var currentCommandTier = _commandModel.Commands;
CommandInfo? currentCommand = null;
foreach (var command in settings.Commands)
{
currentCommand = currentCommandTier
.SingleOrDefault(i =>
i.Name.Equals(command, StringComparison.CurrentCultureIgnoreCase) ||
i.Aliases
.Any(alias => alias.Equals(command, StringComparison.CurrentCultureIgnoreCase)));
if (currentCommand == null)
{
break;
}
currentCommandTier = currentCommand.Children;
}
currentCommand = currentCommandTier
.SingleOrDefault(i =>
i.Name.Equals(command, StringComparison.CurrentCultureIgnoreCase) ||
i.Aliases
.Any(alias => alias.Equals(command, StringComparison.CurrentCultureIgnoreCase)));
if (currentCommand == null)
{
throw new Exception($"Command {string.Join(" ", settings.Commands)} not found");
break;
}
AddCommands(
tree.AddNode(ParentMarkup("Commands")),
new[] { currentCommand },
settings.Detailed ?? true,
settings.IncludeHidden);
currentCommandTier = currentCommand.Children;
}
_writer.Write(tree);
if (currentCommand == null)
{
throw new Exception($"Command {string.Join(" ", settings.Commands)} not found");
}
return 0;
AddCommands(
tree.AddNode(ParentMarkup("Commands")),
new[] { currentCommand },
settings.Detailed ?? true,
settings.IncludeHidden);
}
private IRenderable ValueMarkup(string key, string value)
_writer.Write(tree);
return 0;
}
private IRenderable ValueMarkup(string key, string value)
{
return new Markup($"{key}: [blue]{value.EscapeMarkup()}[/]");
}
private IRenderable ValueMarkup(string key, string? value, string noValueText)
{
if (string.IsNullOrWhiteSpace(value))
{
return new Markup($"{key}: [blue]{value.EscapeMarkup()}[/]");
return new Markup($"{key}: [grey]({noValueText.EscapeMarkup()})[/]");
}
private IRenderable ValueMarkup(string key, string? value, string noValueText)
{
if (string.IsNullOrWhiteSpace(value))
{
return new Markup($"{key}: [grey]({noValueText.EscapeMarkup()})[/]");
}
var table = new Table().NoBorder().HideHeaders().AddColumns("key", "value");
table.AddRow($"{key}", $"[blue]{value.EscapeMarkup()}[/]");
return table;
}
var table = new Table().NoBorder().HideHeaders().AddColumns("key", "value");
table.AddRow($"{key}", $"[blue]{value.EscapeMarkup()}[/]");
return table;
private string ParentMarkup(string description)
{
return $"[yellow]{description.EscapeMarkup()}[/]";
}
private void AddStringList(TreeNode node, string description, IList<string>? strings)
{
if (strings == null || strings.Count == 0)
{
return;
}
private string ParentMarkup(string description)
var parentNode = node.AddNode(ParentMarkup(description));
foreach (var s in strings)
{
return $"[yellow]{description.EscapeMarkup()}[/]";
}
private void AddStringList(TreeNode node, string description, IList<string>? strings)
{
if (strings == null || strings.Count == 0)
{
return;
}
var parentNode = node.AddNode(ParentMarkup(description));
foreach (var s in strings)
{
parentNode.AddNode(s);
}
}
private void AddCommands(TreeNode node, IEnumerable<CommandInfo> commands, bool detailed, bool includeHidden)
{
foreach (var command in commands)
{
if (!includeHidden && command.IsHidden)
{
continue;
}
var commandName = $"[green]{command.Name}[/]";
if (command.IsDefaultCommand)
{
commandName += " (default)";
}
var commandNode = node.AddNode(commandName);
commandNode.AddNode(ValueMarkup("Description", command.Description, "no description"));
if (command.IsHidden)
{
commandNode.AddNode(ValueMarkup("IsHidden", command.IsHidden.ToString()));
}
if (!command.IsBranch)
{
commandNode.AddNode(ValueMarkup("Type", command.CommandType?.ToString(), "no command type"));
commandNode.AddNode(ValueMarkup("Settings Type", command.SettingsType.ToString()));
}
if (command.Parameters.Count > 0)
{
var parametersNode = commandNode.AddNode(ParentMarkup("Parameters"));
foreach (var parameter in command.Parameters)
{
AddParameter(parametersNode, parameter, detailed, includeHidden);
}
}
AddStringList(commandNode, "Aliases", command.Aliases.ToList());
AddStringList(commandNode, "Examples", command.Examples.Select(i => string.Join(" ", i)).ToList());
if (command.Children.Count > 0)
{
var childNode = commandNode.AddNode(ParentMarkup("Child Commands"));
AddCommands(childNode, command.Children, detailed, includeHidden);
}
}
}
private void AddParameter(TreeNode parametersNode, CommandParameter parameter, bool detailed, bool includeHidden)
{
if (!includeHidden && parameter.IsHidden)
{
return;
}
if (!detailed)
{
parametersNode.AddNode(
$"{parameter.PropertyName} [purple]{GetShortOptions(parameter)}[/] [grey]{parameter.Property.PropertyType.ToString().EscapeMarkup()}[/]");
return;
}
var parameterNode = parametersNode.AddNode(
$"{parameter.PropertyName} [grey]{parameter.Property.PropertyType.ToString().EscapeMarkup()}[/]");
parameterNode.AddNode(ValueMarkup("Description", parameter.Description, "no description"));
parameterNode.AddNode(ValueMarkup("Parameter Kind", parameter.ParameterKind.ToString()));
if (parameter is CommandOption commandOptionParameter)
{
if (commandOptionParameter.IsShadowed)
{
parameterNode.AddNode(ValueMarkup("IsShadowed", commandOptionParameter.IsShadowed.ToString()));
}
if (commandOptionParameter.LongNames.Count > 0)
{
parameterNode.AddNode(ValueMarkup(
"Long Names",
string.Join("|", commandOptionParameter.LongNames.Select(i => $"--{i}"))));
parameterNode.AddNode(ValueMarkup(
"Short Names",
string.Join("|", commandOptionParameter.ShortNames.Select(i => $"-{i}"))));
}
}
else if (parameter is CommandArgument commandArgumentParameter)
{
parameterNode.AddNode(ValueMarkup("Position", commandArgumentParameter.Position.ToString()));
parameterNode.AddNode(ValueMarkup("Value", commandArgumentParameter.Value));
}
parameterNode.AddNode(ValueMarkup("Required", parameter.Required.ToString()));
if (parameter.Converter != null)
{
parameterNode.AddNode(ValueMarkup(
"Converter", $"\"{parameter.Converter.ConverterTypeName}\""));
}
if (parameter.DefaultValue != null)
{
parameterNode.AddNode(ValueMarkup(
"Default Value", $"\"{parameter.DefaultValue.Value}\""));
}
if (parameter.PairDeconstructor != null)
{
parameterNode.AddNode(ValueMarkup("Pair Deconstructor", parameter.PairDeconstructor.Type.ToString()));
}
AddStringList(
parameterNode,
"Validators",
parameter.Validators.Select(i => i.GetType().ToString()).ToList());
}
private static string GetShortOptions(CommandParameter parameter)
{
if (parameter is CommandOption commandOptionParameter)
{
return string.Join(
" | ",
commandOptionParameter.LongNames.Select(i => $"--{i}")
.Concat(commandOptionParameter.ShortNames.Select(i => $"-{i}")));
}
if (parameter is CommandArgument commandArgumentParameter)
{
return $"{commandArgumentParameter.Value} position {commandArgumentParameter.Position}";
}
return string.Empty;
parentNode.AddNode(s);
}
}
private void AddCommands(TreeNode node, IEnumerable<CommandInfo> commands, bool detailed, bool includeHidden)
{
foreach (var command in commands)
{
if (!includeHidden && command.IsHidden)
{
continue;
}
var commandName = $"[green]{command.Name}[/]";
if (command.IsDefaultCommand)
{
commandName += " (default)";
}
var commandNode = node.AddNode(commandName);
commandNode.AddNode(ValueMarkup("Description", command.Description, "no description"));
if (command.IsHidden)
{
commandNode.AddNode(ValueMarkup("IsHidden", command.IsHidden.ToString()));
}
if (!command.IsBranch)
{
commandNode.AddNode(ValueMarkup("Type", command.CommandType?.ToString(), "no command type"));
commandNode.AddNode(ValueMarkup("Settings Type", command.SettingsType.ToString()));
}
if (command.Parameters.Count > 0)
{
var parametersNode = commandNode.AddNode(ParentMarkup("Parameters"));
foreach (var parameter in command.Parameters)
{
AddParameter(parametersNode, parameter, detailed, includeHidden);
}
}
AddStringList(commandNode, "Aliases", command.Aliases.ToList());
AddStringList(commandNode, "Examples", command.Examples.Select(i => string.Join(" ", i)).ToList());
if (command.Children.Count > 0)
{
var childNode = commandNode.AddNode(ParentMarkup("Child Commands"));
AddCommands(childNode, command.Children, detailed, includeHidden);
}
}
}
private void AddParameter(TreeNode parametersNode, CommandParameter parameter, bool detailed, bool includeHidden)
{
if (!includeHidden && parameter.IsHidden)
{
return;
}
if (!detailed)
{
parametersNode.AddNode(
$"{parameter.PropertyName} [purple]{GetShortOptions(parameter)}[/] [grey]{parameter.Property.PropertyType.ToString().EscapeMarkup()}[/]");
return;
}
var parameterNode = parametersNode.AddNode(
$"{parameter.PropertyName} [grey]{parameter.Property.PropertyType.ToString().EscapeMarkup()}[/]");
parameterNode.AddNode(ValueMarkup("Description", parameter.Description, "no description"));
parameterNode.AddNode(ValueMarkup("Parameter Kind", parameter.ParameterKind.ToString()));
if (parameter is CommandOption commandOptionParameter)
{
if (commandOptionParameter.IsShadowed)
{
parameterNode.AddNode(ValueMarkup("IsShadowed", commandOptionParameter.IsShadowed.ToString()));
}
if (commandOptionParameter.LongNames.Count > 0)
{
parameterNode.AddNode(ValueMarkup(
"Long Names",
string.Join("|", commandOptionParameter.LongNames.Select(i => $"--{i}"))));
parameterNode.AddNode(ValueMarkup(
"Short Names",
string.Join("|", commandOptionParameter.ShortNames.Select(i => $"-{i}"))));
}
}
else if (parameter is CommandArgument commandArgumentParameter)
{
parameterNode.AddNode(ValueMarkup("Position", commandArgumentParameter.Position.ToString()));
parameterNode.AddNode(ValueMarkup("Value", commandArgumentParameter.Value));
}
parameterNode.AddNode(ValueMarkup("Required", parameter.Required.ToString()));
if (parameter.Converter != null)
{
parameterNode.AddNode(ValueMarkup(
"Converter", $"\"{parameter.Converter.ConverterTypeName}\""));
}
if (parameter.DefaultValue != null)
{
parameterNode.AddNode(ValueMarkup(
"Default Value", $"\"{parameter.DefaultValue.Value}\""));
}
if (parameter.PairDeconstructor != null)
{
parameterNode.AddNode(ValueMarkup("Pair Deconstructor", parameter.PairDeconstructor.Type.ToString()));
}
AddStringList(
parameterNode,
"Validators",
parameter.Validators.Select(i => i.GetType().ToString()).ToList());
}
private static string GetShortOptions(CommandParameter parameter)
{
if (parameter is CommandOption commandOptionParameter)
{
return string.Join(
" | ",
commandOptionParameter.LongNames.Select(i => $"--{i}")
.Concat(commandOptionParameter.ShortNames.Select(i => $"-{i}")));
}
if (parameter is CommandArgument commandArgumentParameter)
{
return $"{commandArgumentParameter.Value} position {commandArgumentParameter.Position}";
}
return string.Empty;
}
}

View File

@ -1,34 +1,33 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
[Description("Displays the CLI library version")]
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class VersionCommand : Command<VersionCommand.Settings>
{
[Description("Displays the CLI library version")]
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class VersionCommand : Command<VersionCommand.Settings>
private readonly IAnsiConsole _writer;
public VersionCommand(IConfiguration configuration)
{
private readonly IAnsiConsole _writer;
public VersionCommand(IConfiguration configuration)
{
_writer = configuration.Settings.Console.GetConsole();
}
public sealed class Settings : CommandSettings
{
}
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
{
_writer.MarkupLine(
"[yellow]Spectre.Cli[/] version [aqua]{0}[/]",
VersionHelper.GetVersion(typeof(VersionCommand)?.Assembly));
_writer.MarkupLine(
"[yellow]Spectre.Console[/] version [aqua]{0}[/]",
VersionHelper.GetVersion(typeof(IAnsiConsole)?.Assembly));
return 0;
}
_writer = configuration.Settings.Console.GetConsole();
}
}
public sealed class Settings : CommandSettings
{
}
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
{
_writer.MarkupLine(
"[yellow]Spectre.Cli[/] version [aqua]{0}[/]",
VersionHelper.GetVersion(typeof(VersionCommand)?.Assembly));
_writer.MarkupLine(
"[yellow]Spectre.Console[/] version [aqua]{0}[/]",
VersionHelper.GetVersion(typeof(IAnsiConsole)?.Assembly));
return 0;
}
}

View File

@ -7,179 +7,178 @@ using System.Linq;
using System.Text;
using System.Xml;
namespace Spectre.Console.Cli
namespace Spectre.Console.Cli;
[Description("Generates an XML representation of the CLI configuration.")]
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
{
[Description("Generates an XML representation of the CLI configuration.")]
[SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")]
internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
private readonly CommandModel _model;
private readonly IAnsiConsole _writer;
public XmlDocCommand(IConfiguration configuration, CommandModel model)
{
private readonly CommandModel _model;
private readonly IAnsiConsole _writer;
_model = model ?? throw new ArgumentNullException(nameof(model));
_writer = configuration.Settings.Console.GetConsole();
}
public XmlDocCommand(IConfiguration configuration, CommandModel model)
public sealed class Settings : CommandSettings
{
}
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
{
_writer.Write(Serialize(_model), Style.Plain);
return 0;
}
public static string Serialize(CommandModel model)
{
var settings = new XmlWriterSettings
{
_model = model ?? throw new ArgumentNullException(nameof(model));
_writer = configuration.Settings.Console.GetConsole();
}
Indent = true,
IndentChars = " ",
NewLineChars = "\n",
OmitXmlDeclaration = false,
Encoding = Encoding.UTF8,
};
public sealed class Settings : CommandSettings
using (var buffer = new StringWriterWithEncoding(Encoding.UTF8))
using (var xmlWriter = XmlWriter.Create(buffer, settings))
{
}
public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings)
{
_writer.Write(Serialize(_model), Style.Plain);
return 0;
}
public static string Serialize(CommandModel model)
{
var settings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
NewLineChars = "\n",
OmitXmlDeclaration = false,
Encoding = Encoding.UTF8,
};
using (var buffer = new StringWriterWithEncoding(Encoding.UTF8))
using (var xmlWriter = XmlWriter.Create(buffer, settings))
{
SerializeModel(model).WriteTo(xmlWriter);
xmlWriter.Flush();
return buffer.GetStringBuilder().ToString();
}
}
private static XmlDocument SerializeModel(CommandModel model)
{
var document = new XmlDocument();
var root = document.CreateElement("Model");
if (model.DefaultCommand != null)
{
root.AppendChild(document.CreateComment("DEFAULT COMMAND"));
root.AppendChild(CreateCommandNode(document, model.DefaultCommand, isDefaultCommand: true));
}
foreach (var command in model.Commands.Where(x => !x.IsHidden))
{
root.AppendChild(document.CreateComment(command.Name.ToUpperInvariant()));
root.AppendChild(CreateCommandNode(document, command));
}
document.AppendChild(root);
return document;
}
private static XmlNode CreateCommandNode(XmlDocument doc, CommandInfo command, bool isDefaultCommand = false)
{
var node = doc.CreateElement("Command");
// Attributes
node.SetNullableAttribute("Name", command.Name);
node.SetBooleanAttribute("IsBranch", command.IsBranch);
if (isDefaultCommand)
{
node.SetBooleanAttribute("IsDefault", true);
}
if (command.CommandType != null)
{
node.SetNullableAttribute("ClrType", command.CommandType?.FullName);
}
node.SetNullableAttribute("Settings", command.SettingsType?.FullName);
// Parameters
if (command.Parameters.Count > 0)
{
var parameterRootNode = doc.CreateElement("Parameters");
foreach (var parameter in CreateParameterNodes(doc, command))
{
parameterRootNode.AppendChild(parameter);
}
node.AppendChild(parameterRootNode);
}
// Commands
foreach (var childCommand in command.Children)
{
node.AppendChild(doc.CreateComment(childCommand.Name.ToUpperInvariant()));
node.AppendChild(CreateCommandNode(doc, childCommand));
}
return node;
}
private static IEnumerable<XmlNode> CreateParameterNodes(XmlDocument document, CommandInfo command)
{
// Arguments
foreach (var argument in command.Parameters.OfType<CommandArgument>().OrderBy(x => x.Position))
{
var node = document.CreateElement("Argument");
node.SetNullableAttribute("Name", argument.Value);
node.SetAttribute("Position", argument.Position.ToString(CultureInfo.InvariantCulture));
node.SetBooleanAttribute("Required", argument.Required);
node.SetEnumAttribute("Kind", argument.ParameterKind);
node.SetNullableAttribute("ClrType", argument.ParameterType?.FullName);
if (!string.IsNullOrWhiteSpace(argument.Description))
{
var descriptionNode = document.CreateElement("Description");
descriptionNode.InnerText = argument.Description;
node.AppendChild(descriptionNode);
}
if (argument.Validators.Count > 0)
{
var validatorRootNode = document.CreateElement("Validators");
foreach (var validator in argument.Validators.OrderBy(x => x.GetType().FullName))
{
var validatorNode = document.CreateElement("Validator");
validatorNode.SetNullableAttribute("ClrType", validator.GetType().FullName);
validatorNode.SetNullableAttribute("Message", validator.ErrorMessage);
validatorRootNode.AppendChild(validatorNode);
}
node.AppendChild(validatorRootNode);
}
yield return node;
}
// Options
foreach (var option in command.Parameters.OfType<CommandOption>()
.Where(x => !x.IsHidden)
.OrderBy(x => string.Join(",", x.LongNames))
.ThenBy(x => string.Join(",", x.ShortNames)))
{
var node = document.CreateElement("Option");
if (option.IsShadowed)
{
node.SetBooleanAttribute("Shadowed", true);
}
node.SetNullableAttribute("Short", option.ShortNames);
node.SetNullableAttribute("Long", option.LongNames);
node.SetNullableAttribute("Value", option.ValueName);
node.SetBooleanAttribute("Required", option.Required);
node.SetEnumAttribute("Kind", option.ParameterKind);
node.SetNullableAttribute("ClrType", option.ParameterType?.FullName);
if (!string.IsNullOrWhiteSpace(option.Description))
{
var descriptionNode = document.CreateElement("Description");
descriptionNode.InnerText = option.Description;
node.AppendChild(descriptionNode);
}
yield return node;
}
SerializeModel(model).WriteTo(xmlWriter);
xmlWriter.Flush();
return buffer.GetStringBuilder().ToString();
}
}
}
private static XmlDocument SerializeModel(CommandModel model)
{
var document = new XmlDocument();
var root = document.CreateElement("Model");
if (model.DefaultCommand != null)
{
root.AppendChild(document.CreateComment("DEFAULT COMMAND"));
root.AppendChild(CreateCommandNode(document, model.DefaultCommand, isDefaultCommand: true));
}
foreach (var command in model.Commands.Where(x => !x.IsHidden))
{
root.AppendChild(document.CreateComment(command.Name.ToUpperInvariant()));
root.AppendChild(CreateCommandNode(document, command));
}
document.AppendChild(root);
return document;
}
private static XmlNode CreateCommandNode(XmlDocument doc, CommandInfo command, bool isDefaultCommand = false)
{
var node = doc.CreateElement("Command");
// Attributes
node.SetNullableAttribute("Name", command.Name);
node.SetBooleanAttribute("IsBranch", command.IsBranch);
if (isDefaultCommand)
{
node.SetBooleanAttribute("IsDefault", true);
}
if (command.CommandType != null)
{
node.SetNullableAttribute("ClrType", command.CommandType?.FullName);
}
node.SetNullableAttribute("Settings", command.SettingsType?.FullName);
// Parameters
if (command.Parameters.Count > 0)
{
var parameterRootNode = doc.CreateElement("Parameters");
foreach (var parameter in CreateParameterNodes(doc, command))
{
parameterRootNode.AppendChild(parameter);
}
node.AppendChild(parameterRootNode);
}
// Commands
foreach (var childCommand in command.Children)
{
node.AppendChild(doc.CreateComment(childCommand.Name.ToUpperInvariant()));
node.AppendChild(CreateCommandNode(doc, childCommand));
}
return node;
}
private static IEnumerable<XmlNode> CreateParameterNodes(XmlDocument document, CommandInfo command)
{
// Arguments
foreach (var argument in command.Parameters.OfType<CommandArgument>().OrderBy(x => x.Position))
{
var node = document.CreateElement("Argument");
node.SetNullableAttribute("Name", argument.Value);
node.SetAttribute("Position", argument.Position.ToString(CultureInfo.InvariantCulture));
node.SetBooleanAttribute("Required", argument.Required);
node.SetEnumAttribute("Kind", argument.ParameterKind);
node.SetNullableAttribute("ClrType", argument.ParameterType?.FullName);
if (!string.IsNullOrWhiteSpace(argument.Description))
{
var descriptionNode = document.CreateElement("Description");
descriptionNode.InnerText = argument.Description;
node.AppendChild(descriptionNode);
}
if (argument.Validators.Count > 0)
{
var validatorRootNode = document.CreateElement("Validators");
foreach (var validator in argument.Validators.OrderBy(x => x.GetType().FullName))
{
var validatorNode = document.CreateElement("Validator");
validatorNode.SetNullableAttribute("ClrType", validator.GetType().FullName);
validatorNode.SetNullableAttribute("Message", validator.ErrorMessage);
validatorRootNode.AppendChild(validatorNode);
}
node.AppendChild(validatorRootNode);
}
yield return node;
}
// Options
foreach (var option in command.Parameters.OfType<CommandOption>()
.Where(x => !x.IsHidden)
.OrderBy(x => string.Join(",", x.LongNames))
.ThenBy(x => string.Join(",", x.ShortNames)))
{
var node = document.CreateElement("Option");
if (option.IsShadowed)
{
node.SetBooleanAttribute("Shadowed", true);
}
node.SetNullableAttribute("Short", option.ShortNames);
node.SetNullableAttribute("Long", option.LongNames);
node.SetNullableAttribute("Value", option.ValueName);
node.SetBooleanAttribute("Required", option.Required);
node.SetEnumAttribute("Kind", option.ParameterKind);
node.SetNullableAttribute("ClrType", option.ParameterType?.FullName);
if (!string.IsNullOrWhiteSpace(option.Description))
{
var descriptionNode = document.CreateElement("Description");
descriptionNode.InnerText = option.Description;
node.AppendChild(descriptionNode);
}
yield return node;
}
}
}

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