mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-31 17:15:28 +08:00 
			
		
		
		
	Simplify exception formatting
Now that exceptions are not _parsed_ anymore (#513 and #637), keeping the `ExceptionInfo` and `StackFrameInfo` internal classes doesn't make much sense anymore. The `Exception` and `StackFrame` types can be used by the `ExceptionFormatter` class directly, without conversion.
This commit is contained in:
		| @@ -1,57 +0,0 @@ | ||||
| namespace Spectre.Console; | ||||
|  | ||||
| internal static class ExceptionConverter | ||||
| { | ||||
|     public static ExceptionInfo Convert(Exception exception) | ||||
|     { | ||||
|         if (exception is null) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(exception)); | ||||
|         } | ||||
|  | ||||
|         var exceptionType = exception.GetType(); | ||||
|         var stackTrace = new StackTrace(exception, true); | ||||
|         var frames = stackTrace.GetFrames().Where(f => f != null).Cast<StackFrame>().Select(Convert).ToList(); | ||||
|         var inner = exception.InnerException is null ? null : Convert(exception.InnerException); | ||||
|         return new ExceptionInfo(exceptionType.FullName ?? exceptionType.Name, exception.Message, frames, inner); | ||||
|     } | ||||
|  | ||||
|     private static StackFrameInfo Convert(StackFrame frame) | ||||
|     { | ||||
|         var method = frame.GetMethod(); | ||||
|         if (method is null) | ||||
|         { | ||||
|             return new StackFrameInfo("<unknown method>", new List<(string Type, string Name)>(), null, null); | ||||
|         } | ||||
|  | ||||
|         var methodName = GetMethodName(method); | ||||
|         var parameters = method.GetParameters().Select(e => (e.ParameterType.Name, e.Name ?? string.Empty)).ToList(); | ||||
|         var path = frame.GetFileName(); | ||||
|         var lineNumber = frame.GetFileLineNumber(); | ||||
|         return new StackFrameInfo(methodName, parameters, path, lineNumber == 0 ? null : lineNumber); | ||||
|     } | ||||
|  | ||||
|     private static string GetMethodName(MethodBase method) | ||||
|     { | ||||
|         var builder = new StringBuilder(256); | ||||
|  | ||||
|         var fullName = method.DeclaringType?.FullName; | ||||
|         if (fullName != null) | ||||
|         { | ||||
|             // See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs#L247-L253 | ||||
|             builder.Append(fullName.Replace('+', '.')); | ||||
|             builder.Append('.'); | ||||
|         } | ||||
|  | ||||
|         builder.Append(method.Name); | ||||
|  | ||||
|         if (method.IsGenericMethod) | ||||
|         { | ||||
|             builder.Append('['); | ||||
|             builder.Append(string.Join(",", method.GetGenericArguments().Select(t => t.Name))); | ||||
|             builder.Append(']'); | ||||
|         } | ||||
|  | ||||
|         return builder.ToString(); | ||||
|     } | ||||
| } | ||||
| @@ -9,35 +9,35 @@ internal static class ExceptionFormatter | ||||
|             throw new ArgumentNullException(nameof(exception)); | ||||
|         } | ||||
|  | ||||
|         var info = ExceptionConverter.Convert(exception); | ||||
|  | ||||
|         return GetException(info, settings); | ||||
|         return GetException(exception, settings); | ||||
|     } | ||||
|  | ||||
|     private static IRenderable GetException(ExceptionInfo info, ExceptionSettings settings) | ||||
|     private static IRenderable GetException(Exception exception, ExceptionSettings settings) | ||||
|     { | ||||
|         if (info is null) | ||||
|         if (exception is null) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(info)); | ||||
|             throw new ArgumentNullException(nameof(exception)); | ||||
|         } | ||||
|  | ||||
|         return new Rows(new IRenderable[] | ||||
|         { | ||||
|                 GetMessage(info, settings), | ||||
|                 GetStackFrames(info, settings), | ||||
|                 GetMessage(exception, settings), | ||||
|                 GetStackFrames(exception, settings), | ||||
|         }).Expand(); | ||||
|     } | ||||
|  | ||||
|     private static Markup GetMessage(ExceptionInfo ex, ExceptionSettings settings) | ||||
|     private static Markup GetMessage(Exception ex, ExceptionSettings settings) | ||||
|     { | ||||
|         var shortenTypes = (settings.Format & ExceptionFormats.ShortenTypes) != 0; | ||||
|         var type = Emphasize(ex.Type, new[] { '.' }, settings.Style.Exception, shortenTypes, settings); | ||||
|         var exceptionType = ex.GetType(); | ||||
|         var exceptionTypeFullName = exceptionType.FullName ?? exceptionType.Name; | ||||
|         var type = Emphasize(exceptionTypeFullName, new[] { '.' }, settings.Style.Exception, shortenTypes, settings); | ||||
|  | ||||
|         var message = $"[{settings.Style.Message.ToMarkup()}]{ex.Message.EscapeMarkup()}[/]"; | ||||
|         return new Markup(string.Concat(type, ": ", message)); | ||||
|     } | ||||
|  | ||||
|     private static Grid GetStackFrames(ExceptionInfo ex, ExceptionSettings settings) | ||||
|     private static Grid GetStackFrames(Exception ex, ExceptionSettings settings) | ||||
|     { | ||||
|         var styles = settings.Style; | ||||
|  | ||||
| @@ -46,39 +46,44 @@ internal static class ExceptionFormatter | ||||
|         grid.AddColumn(new GridColumn().PadLeft(1).PadRight(0)); | ||||
|  | ||||
|         // Inner | ||||
|         if (ex.Inner != null) | ||||
|         if (ex.InnerException != null) | ||||
|         { | ||||
|             grid.AddRow( | ||||
|                 Text.Empty, | ||||
|                 GetException(ex.Inner, settings)); | ||||
|                 GetException(ex.InnerException, settings)); | ||||
|         } | ||||
|  | ||||
|         // Stack frames | ||||
|         foreach (var frame in ex.Frames) | ||||
|         var stackTrace = new StackTrace(ex, fNeedFileInfo: true); | ||||
|         foreach (var frame in stackTrace.GetFrames().Where(f => f != null).Cast<StackFrame>()) | ||||
|         { | ||||
|             var builder = new StringBuilder(); | ||||
|  | ||||
|             // Method | ||||
|             var shortenMethods = (settings.Format & ExceptionFormats.ShortenMethods) != 0; | ||||
|             builder.Append(Emphasize(frame.Method, new[] { '.' }, styles.Method, shortenMethods, settings)); | ||||
|             var method = frame.GetMethod(); | ||||
|             var methodName = method.GetName(); | ||||
|             builder.Append(Emphasize(methodName, new[] { '.' }, styles.Method, shortenMethods, settings)); | ||||
|             builder.AppendWithStyle(styles.Parenthesis, "("); | ||||
|             AppendParameters(builder, frame, settings); | ||||
|             AppendParameters(builder, method, settings); | ||||
|             builder.AppendWithStyle(styles.Parenthesis, ")"); | ||||
|  | ||||
|             if (frame.Path != null) | ||||
|             var path = frame.GetFileName(); | ||||
|             if (path != null) | ||||
|             { | ||||
|                 builder.Append(' '); | ||||
|                 builder.AppendWithStyle(styles.Dimmed, "in"); | ||||
|                 builder.Append(' '); | ||||
|  | ||||
|                 // Path | ||||
|                 AppendPath(builder, frame, settings); | ||||
|                 AppendPath(builder, path, settings); | ||||
|  | ||||
|                 // Line number | ||||
|                 if (frame.LineNumber != null) | ||||
|                 var lineNumber = frame.GetFileLineNumber(); | ||||
|                 if (lineNumber != 0) | ||||
|                 { | ||||
|                     builder.AppendWithStyle(styles.Dimmed, ":"); | ||||
|                     builder.AppendWithStyle(styles.LineNumber, frame.LineNumber); | ||||
|                     builder.AppendWithStyle(styles.LineNumber, lineNumber); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @@ -90,30 +95,28 @@ internal static class ExceptionFormatter | ||||
|         return grid; | ||||
|     } | ||||
|  | ||||
|     private static void AppendParameters(StringBuilder builder, StackFrameInfo frame, ExceptionSettings settings) | ||||
|     private static void AppendParameters(StringBuilder builder, MethodBase? method, ExceptionSettings settings) | ||||
|     { | ||||
|         var typeColor = settings.Style.ParameterType.ToMarkup(); | ||||
|         var nameColor = settings.Style.ParameterName.ToMarkup(); | ||||
|         var parameters = frame.Parameters.Select(x => $"[{typeColor}]{x.Type.EscapeMarkup()}[/] [{nameColor}]{x.Name.EscapeMarkup()}[/]"); | ||||
|         builder.Append(string.Join(", ", parameters)); | ||||
|         var parameters = method?.GetParameters().Select(x => $"[{typeColor}]{x.ParameterType.Name.EscapeMarkup()}[/] [{nameColor}]{x.Name?.EscapeMarkup()}[/]"); | ||||
|         if (parameters != null) | ||||
|         { | ||||
|             builder.Append(string.Join(", ", parameters)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void AppendPath(StringBuilder builder, StackFrameInfo frame, ExceptionSettings settings) | ||||
|     private static void AppendPath(StringBuilder builder, string path, ExceptionSettings settings) | ||||
|     { | ||||
|         if (frame?.Path is null) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         void AppendPath() | ||||
|         { | ||||
|             var shortenPaths = (settings.Format & ExceptionFormats.ShortenPaths) != 0; | ||||
|             builder.Append(Emphasize(frame.Path, new[] { '/', '\\' }, settings.Style.Path, shortenPaths, settings)); | ||||
|             builder.Append(Emphasize(path, new[] { '/', '\\' }, settings.Style.Path, shortenPaths, settings)); | ||||
|         } | ||||
|  | ||||
|         if ((settings.Format & ExceptionFormats.ShowLinks) != 0) | ||||
|         { | ||||
|             var hasLink = frame.TryGetUri(out var uri); | ||||
|             var hasLink = path.TryGetUri(out var uri); | ||||
|             if (hasLink && uri != null) | ||||
|             { | ||||
|                 builder.Append("[link=").Append(uri.AbsoluteUri).Append(']'); | ||||
|   | ||||
| @@ -1,20 +0,0 @@ | ||||
| namespace Spectre.Console; | ||||
|  | ||||
| internal sealed class ExceptionInfo | ||||
| { | ||||
|     public string Type { get; } | ||||
|     public string Message { get; } | ||||
|     public List<StackFrameInfo> Frames { get; } | ||||
|     public ExceptionInfo? Inner { get; } | ||||
|  | ||||
|     public ExceptionInfo( | ||||
|         string type, string message, | ||||
|         List<StackFrameInfo> frames, | ||||
|         ExceptionInfo? inner) | ||||
|     { | ||||
|         Type = type ?? string.Empty; | ||||
|         Message = message ?? string.Empty; | ||||
|         Frames = frames ?? new List<StackFrameInfo>(); | ||||
|         Inner = inner; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/Spectre.Console/Widgets/Exceptions/MethodExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/Spectre.Console/Widgets/Exceptions/MethodExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| namespace Spectre.Console; | ||||
|  | ||||
| internal static class MethodExtensions | ||||
| { | ||||
|     public static string GetName(this MethodBase? method) | ||||
|     { | ||||
|         if (method is null) | ||||
|         { | ||||
|             return "<unknown method>"; | ||||
|         } | ||||
|  | ||||
|         var builder = new StringBuilder(256); | ||||
|  | ||||
|         var fullName = method.DeclaringType?.FullName; | ||||
|         if (fullName != null) | ||||
|         { | ||||
|             // See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs#L247-L253 | ||||
|             builder.Append(fullName.Replace('+', '.')); | ||||
|             builder.Append('.'); | ||||
|         } | ||||
|  | ||||
|         builder.Append(method.Name); | ||||
|  | ||||
|         if (method.IsGenericMethod) | ||||
|         { | ||||
|             builder.Append('['); | ||||
|             builder.Append(string.Join(",", method.GetGenericArguments().Select(t => t.Name))); | ||||
|             builder.Append(']'); | ||||
|         } | ||||
|  | ||||
|         return builder.ToString(); | ||||
|     } | ||||
| } | ||||
| @@ -1,58 +0,0 @@ | ||||
| namespace Spectre.Console; | ||||
|  | ||||
| internal sealed class StackFrameInfo | ||||
| { | ||||
|     public string Method { get; } | ||||
|     public List<(string Type, string Name)> Parameters { get; } | ||||
|     public string? Path { get; } | ||||
|     public int? LineNumber { get; } | ||||
|  | ||||
|     public StackFrameInfo( | ||||
|         string method, List<(string Type, string Name)> parameters, | ||||
|         string? path, int? lineNumber) | ||||
|     { | ||||
|         Method = method ?? throw new ArgumentNullException(nameof(method)); | ||||
|         Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); | ||||
|         Path = path; | ||||
|         LineNumber = lineNumber; | ||||
|     } | ||||
|  | ||||
|     public bool TryGetUri([NotNullWhen(true)] out Uri? result) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (Path == null) | ||||
|             { | ||||
|                 result = null; | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if (!Uri.TryCreate(Path, UriKind.Absolute, out var uri)) | ||||
|             { | ||||
|                 result = null; | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if (uri.Scheme == "file") | ||||
|             { | ||||
|                 // For local files, we need to append | ||||
|                 // the host name. Otherwise the terminal | ||||
|                 // will most probably not allow it. | ||||
|                 var builder = new UriBuilder(uri) | ||||
|                 { | ||||
|                     Host = Dns.GetHostName(), | ||||
|                 }; | ||||
|  | ||||
|                 uri = builder.Uri; | ||||
|             } | ||||
|  | ||||
|             result = uri; | ||||
|             return true; | ||||
|         } | ||||
|         catch | ||||
|         { | ||||
|             result = null; | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,37 @@ | ||||
| namespace Spectre.Console; | ||||
|  | ||||
| internal static class StringUriExtensions | ||||
| { | ||||
|     public static bool TryGetUri(this string path, [NotNullWhen(true)] out Uri? result) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (!Uri.TryCreate(path, UriKind.Absolute, out var uri)) | ||||
|             { | ||||
|                 result = null; | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             if (uri.Scheme == "file") | ||||
|             { | ||||
|                 // For local files, we need to append | ||||
|                 // the host name. Otherwise the terminal | ||||
|                 // will most probably not allow it. | ||||
|                 var builder = new UriBuilder(uri) | ||||
|                 { | ||||
|                     Host = Dns.GetHostName(), | ||||
|                 }; | ||||
|  | ||||
|                 uri = builder.Uri; | ||||
|             } | ||||
|  | ||||
|             result = uri; | ||||
|             return true; | ||||
|         } | ||||
|         catch | ||||
|         { | ||||
|             result = null; | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Cédric Luthi
					Cédric Luthi