mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-31 17:15:28 +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:
		 Nils Andresen
					Nils Andresen
				
			
				
					committed by
					
						 Patrik Svensson
						Patrik Svensson
					
				
			
			
				
	
			
			
			 Patrik Svensson
						Patrik Svensson
					
				
			
						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); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user