mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-24 04:02:50 +08:00
(#606) added ExceptionHandler to ICommandAppSettings
So exceptions can be handled in Spectre.Console.Cli. Also, some documentation was added.
This commit is contained in:
parent
045d0922c8
commit
c5c1852fc3
116
docs/input/cli/exceptions.md
Normal file
116
docs/input/cli/exceptions.md
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
Title: Exceptions
|
||||||
|
Order: 12
|
||||||
|
Description: "Handling exceptions in *Spectre.Console.Cli*"
|
||||||
|
---
|
||||||
|
|
||||||
|
Exceptions happen.
|
||||||
|
|
||||||
|
`Spectre.Console.Cli` handles exceptions, writes a user friendly message to the console and sets the exitCode
|
||||||
|
of the application to `-1`.
|
||||||
|
While this might be enough for the needs of most applications, there are some options to customize this behavior.
|
||||||
|
|
||||||
|
## Propagating exceptions
|
||||||
|
|
||||||
|
The most basic way is to set `PropagateExceptions()` during configuration and handle everything.
|
||||||
|
While this option grants the most freedom, it also requires the most work: Setting `PropagateExceptions`
|
||||||
|
means that `Spectre.Console.Cli` effectively re-throws exceptions.
|
||||||
|
This means that `app.Run()` should be wrapped in a `try`-`catch`-block which has to handle the exception
|
||||||
|
(i.e. outputting something useful) and also provide the exitCode (or `return` value) for the application.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace MyApp
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static int Main(string[] args)
|
||||||
|
{
|
||||||
|
var app = new CommandApp<FileSizeCommand>();
|
||||||
|
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return app.Run(args);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
|
||||||
|
return -99;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using a custom ExceptionHandler
|
||||||
|
|
||||||
|
Using the `SetErrorHandler()` during configuration it is possible to handle exceptions in a defined way.
|
||||||
|
This method comes in two flavours: One that uses the default exitCode (or `return` value) of `-1` and one
|
||||||
|
where the exitCode needs to be supplied.
|
||||||
|
|
||||||
|
### Using `SetErrorHandler(Func<Exception, int> handler)`
|
||||||
|
|
||||||
|
Using this method exceptions can be handled in a custom way. The return value of the handler is used as
|
||||||
|
the exitCode for the application.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace MyApp
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static int Main(string[] args)
|
||||||
|
{
|
||||||
|
var app = new CommandApp<FileSizeCommand>();
|
||||||
|
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.SetExceptionHandler(ex =>
|
||||||
|
{
|
||||||
|
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
|
||||||
|
return -99;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return app.Run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using `SetErrorHandler(Action<Exception> handler)`
|
||||||
|
|
||||||
|
Using this method exceptions can be handled in a custom way, much the same as with the `SetErrorHandler(Func<Exception, int> handler)`.
|
||||||
|
Using the `Action` as the handler however, it is not possible (or required) to supply a return value.
|
||||||
|
The exitCode for the application will be `-1`.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace MyApp
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static int Main(string[] args)
|
||||||
|
{
|
||||||
|
var app = new CommandApp<FileSizeCommand>();
|
||||||
|
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.SetExceptionHandler(ex =>
|
||||||
|
{
|
||||||
|
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return app.Run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -103,6 +103,11 @@ namespace Spectre.Console.Cli
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_configurator.Settings.ExceptionHandler != null)
|
||||||
|
{
|
||||||
|
return _configurator.Settings.ExceptionHandler(ex);
|
||||||
|
}
|
||||||
|
|
||||||
// Render the exception.
|
// Render the exception.
|
||||||
var pretty = GetRenderableErrorMessage(ex);
|
var pretty = GetRenderableErrorMessage(ex);
|
||||||
if (pretty != null)
|
if (pretty != null)
|
||||||
|
@ -224,5 +224,39 @@ namespace Spectre.Console.Cli
|
|||||||
|
|
||||||
return configurator.AddDelegate<TSettings>(name, (c, _) => func(c));
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
namespace Spectre.Console.Cli
|
namespace Spectre.Console.Cli
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -43,6 +45,8 @@ namespace Spectre.Console.Cli
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not exceptions should be propagated.
|
/// 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>
|
/// </summary>
|
||||||
bool PropagateExceptions { get; set; }
|
bool PropagateExceptions { get; set; }
|
||||||
|
|
||||||
@ -50,5 +54,11 @@ namespace Spectre.Console.Cli
|
|||||||
/// Gets or sets a value indicating whether or not examples should be validated.
|
/// Gets or sets a value indicating whether or not examples should be validated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool ValidateExamples { get; set; }
|
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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,6 +17,8 @@ namespace Spectre.Console.Cli
|
|||||||
public ParsingMode ParsingMode =>
|
public ParsingMode ParsingMode =>
|
||||||
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
|
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
|
||||||
|
|
||||||
|
public Func<Exception, int>? ExceptionHandler { get; set; }
|
||||||
|
|
||||||
public CommandAppSettings(ITypeRegistrar registrar)
|
public CommandAppSettings(ITypeRegistrar registrar)
|
||||||
{
|
{
|
||||||
Registrar = new TypeRegistrar(registrar);
|
Registrar = new TypeRegistrar(registrar);
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
using System;
|
||||||
|
using Shouldly;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
using Spectre.Console.Tests.Data;
|
||||||
|
using Spectre.Console.Testing;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Tests.Unit.Cli
|
||||||
|
{
|
||||||
|
public sealed partial class CommandAppTests
|
||||||
|
{
|
||||||
|
public sealed class Exception_Handling
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Not_Propagate_Runtime_Exceptions_If_Not_Explicitly_Told_To_Do_So()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
animal.AddCommand<HorseCommand>("horse");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[] { "animal", "4", "dog", "101", "--name", "Rufus" });
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Not_Propagate_Exceptions_If_Not_Explicitly_Told_To_Do_So()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.AddCommand<ThrowingCommand>("throw");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[] { "throw" });
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Handle_Exceptions_If_ExceptionHandler_Is_Set_Using_Action()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var exceptionHandled = false;
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.AddCommand<ThrowingCommand>("throw");
|
||||||
|
config.SetExceptionHandler(_ =>
|
||||||
|
{
|
||||||
|
exceptionHandled = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[] { "throw" });
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(-1);
|
||||||
|
exceptionHandled.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Handle_Exceptions_If_ExceptionHandler_Is_Set_Using_Function()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var exceptionHandled = false;
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.AddCommand<ThrowingCommand>("throw");
|
||||||
|
config.SetExceptionHandler(_ =>
|
||||||
|
{
|
||||||
|
exceptionHandled = true;
|
||||||
|
return -99;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[] { "throw" });
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(-99);
|
||||||
|
exceptionHandled.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -816,46 +816,5 @@ namespace Spectre.Console.Tests.Unit.Cli
|
|||||||
result.Context.Remaining.Raw[4].ShouldBe("qux");
|
result.Context.Remaining.Raw[4].ShouldBe("qux");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Exception_Handling
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void Should_Not_Propagate_Runtime_Exceptions_If_Not_Explicitly_Told_To_Do_So()
|
|
||||||
{
|
|
||||||
// Given
|
|
||||||
var app = new CommandAppTester();
|
|
||||||
app.Configure(config =>
|
|
||||||
{
|
|
||||||
config.AddBranch<AnimalSettings>("animal", animal =>
|
|
||||||
{
|
|
||||||
animal.AddCommand<DogCommand>("dog");
|
|
||||||
animal.AddCommand<HorseCommand>("horse");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// When
|
|
||||||
var result = app.Run(new[] { "animal", "4", "dog", "101", "--name", "Rufus" });
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.ExitCode.ShouldBe(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Should_Not_Propagate_Exceptions_If_Not_Explicitly_Told_To_Do_So()
|
|
||||||
{
|
|
||||||
// Given
|
|
||||||
var app = new CommandAppTester();
|
|
||||||
app.Configure(config =>
|
|
||||||
{
|
|
||||||
config.AddCommand<ThrowingCommand>("throw");
|
|
||||||
});
|
|
||||||
|
|
||||||
// When
|
|
||||||
var result = app.Run(new[] { "throw" });
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.ExitCode.ShouldBe(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user