mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 17:02:51 +08:00
Making tuples pretty too
This commit is contained in:
parent
e8eb5b85b9
commit
74a2e10ff0
@ -276,16 +276,85 @@ internal static class ExceptionFormatter
|
|||||||
var prefix = GetPrefix(parameter);
|
var prefix = GetPrefix(parameter);
|
||||||
var parameterType = parameter.ParameterType;
|
var parameterType = parameter.ParameterType;
|
||||||
|
|
||||||
|
string typeName;
|
||||||
|
if (parameterType.IsGenericType && TryGetTupleName(parameter, parameterType, out var s))
|
||||||
|
{
|
||||||
|
typeName = s;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (parameterType.IsByRef && parameterType.GetElementType() is { } elementType)
|
if (parameterType.IsByRef && parameterType.GetElementType() is { } elementType)
|
||||||
{
|
{
|
||||||
parameterType = elementType;
|
parameterType = elementType;
|
||||||
}
|
}
|
||||||
|
|
||||||
var typeName = TypeNameHelper.GetTypeDisplayName(parameterType, false, true);
|
typeName = TypeNameHelper.GetTypeDisplayName(parameterType);
|
||||||
|
}
|
||||||
|
|
||||||
return string.IsNullOrWhiteSpace(prefix) ? typeName : $"{prefix} {typeName}";
|
return string.IsNullOrWhiteSpace(prefix) ? typeName : $"{prefix} {typeName}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryGetTupleName(ParameterInfo parameter, Type parameterType, [NotNullWhen(true)] out string? tupleName)
|
||||||
|
{
|
||||||
|
var customAttribs = parameter.GetCustomAttributes(inherit: false);
|
||||||
|
|
||||||
|
var tupleNameAttribute = customAttribs
|
||||||
|
.OfType<Attribute>()
|
||||||
|
.FirstOrDefault(a =>
|
||||||
|
{
|
||||||
|
var attributeType = a.GetType();
|
||||||
|
return attributeType.Namespace == "System.Runtime.CompilerServices" &&
|
||||||
|
attributeType.Name == "TupleElementNamesAttribute";
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tupleNameAttribute != null)
|
||||||
|
{
|
||||||
|
var propertyInfo = tupleNameAttribute.GetType()
|
||||||
|
.GetProperty("TransformNames", BindingFlags.Instance | BindingFlags.Public)!;
|
||||||
|
var tupleNames = propertyInfo.GetValue(tupleNameAttribute) as IList<string>;
|
||||||
|
if (tupleNames?.Count > 0)
|
||||||
|
{
|
||||||
|
var args = parameterType.GetGenericArguments();
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.Append('(');
|
||||||
|
for (var i = 0; i < args.Length; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
sb.Append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(TypeNameHelper.GetTypeDisplayName(args[i]));
|
||||||
|
|
||||||
|
if (i >= tupleNames.Count)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var argName = tupleNames[i];
|
||||||
|
|
||||||
|
sb.Append(' ');
|
||||||
|
sb.Append(argName);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(')');
|
||||||
|
|
||||||
|
tupleName = sb.ToString();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (parameterType.Namespace == "System" && parameterType.Name.Contains("ValueTuple`"))
|
||||||
|
{
|
||||||
|
var args = parameterType.GetGenericArguments().Select(i => TypeNameHelper.GetTypeDisplayName(i));
|
||||||
|
tupleName = $"({string.Join(", ", args)})";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
tupleName = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetMethodName(ref MethodBase method, out bool isAsync)
|
private static string GetMethodName(ref MethodBase method, out bool isAsync)
|
||||||
{
|
{
|
||||||
var declaringType = method.DeclaringType;
|
var declaringType = method.DeclaringType;
|
||||||
|
@ -40,35 +40,13 @@ internal static class TypeNameHelper
|
|||||||
/// <param name="fullName"><c>true</c> to print a fully qualified name.</param>
|
/// <param name="fullName"><c>true</c> to print a fully qualified name.</param>
|
||||||
/// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param>
|
/// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param>
|
||||||
/// <returns>The pretty printed type name.</returns>
|
/// <returns>The pretty printed type name.</returns>
|
||||||
public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false)
|
public static string GetTypeDisplayName(Type type, bool fullName = false, bool includeGenericParameterNames = true)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
|
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StringBuilder AppendTypeDisplayName(this StringBuilder builder, Type type, bool fullName = true,
|
|
||||||
bool includeGenericParameterNames = false)
|
|
||||||
{
|
|
||||||
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a name of given generic type without '`'.
|
|
||||||
/// </summary>
|
|
||||||
public static string GetTypeNameForGenericType(Type type)
|
|
||||||
{
|
|
||||||
if (!type.IsGenericType)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("The given type should be generic", nameof(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
var genericPartIndex = type.Name.IndexOf('`');
|
|
||||||
|
|
||||||
return (genericPartIndex >= 0) ? type.Name.Substring(0, genericPartIndex) : type.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options)
|
private static void ProcessType(StringBuilder builder, Type type, DisplayNameOptions options)
|
||||||
{
|
{
|
||||||
if (type.IsGenericType)
|
if (type.IsGenericType)
|
||||||
|
@ -35,4 +35,10 @@ public static class TestExceptions
|
|||||||
firstFewItems = new List<T>();
|
firstFewItems = new List<T>();
|
||||||
throw new InvalidOperationException("Throwing!");
|
throw new InvalidOperationException("Throwing!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static (string Key, List<T> Values) GetTuplesWithInnerException<T>((int First, string Second) myValue)
|
||||||
|
{
|
||||||
|
MethodThatThrows(0);
|
||||||
|
return ("key", new List<T>());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
InvalidOperationException: Throwing!
|
||||||
|
at bool Spectre.Console.Tests.Data.TestExceptions.MethodThatThrows(int? number) in /xyz/Exceptions.cs:nn
|
||||||
|
at (string Key, List<T> Values) Spectre.Console.Tests.Data.TestExceptions.GetTuplesWithInnerException<T>((int First, string Second) myValue) in /xyz/Exceptions.cs:nn
|
||||||
|
at void Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_Exception_With_Tuple_Return>b__6_0() in /xyz/ExceptionTests.cs:nn
|
||||||
|
at Exception Spectre.Console.Tests.Unit.ExceptionTests.GetException(Action action) in /xyz/ExceptionTests.cs:nn
|
@ -94,6 +94,21 @@ public sealed class ExceptionTests
|
|||||||
return Verifier.Verify(result);
|
return Verifier.Verify(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Expectation("Tuple")]
|
||||||
|
public Task Should_Write_Exception_With_Tuple_Return()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new TestConsole().Width(1024);
|
||||||
|
var dex = GetException(() => TestExceptions.GetTuplesWithInnerException<int>((0, "value")));
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = console.WriteNormalizedException(dex, ExceptionFormats.ShortenTypes);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
return Verifier.Verify(result);
|
||||||
|
}
|
||||||
|
|
||||||
public static Exception GetException(Action action)
|
public static Exception GetException(Action action)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
Loading…
x
Reference in New Issue
Block a user