1 module exceeds_expectations.pretty_print; 2 3 import colorize; 4 import exceeds_expectations; 5 import std.algorithm; 6 import std.conv; 7 import std.range; 8 import std.string; 9 import std.traits; 10 11 12 /// Prints the given value in a nice, human readable format. If receiving a 13 /// string, it will output the string with bold double quotes indicating the 14 /// start and end of the string. if the returned string had line breaks, it is 15 /// guaranteed to also start and end with a line break. 16 package string prettyPrint(T)(T value) 17 out(result; !(result.canFind('\n')) || (result.endsWith("\n") && result.startsWith("\n"))) 18 { 19 string rawStringified; 20 21 static if (is(T == class) && !__traits(isOverrideFunction, T.toString)) 22 { 23 if (TypeInfo typeInfo = cast(TypeInfo) value) 24 { 25 rawStringified = prettyPrintTypeInfo(typeInfo); 26 } 27 28 rawStringified = prettyPrintClassObject(value); 29 } 30 else static if (isFloatingPoint!T) 31 { 32 string asString = "%.14f".format(value); 33 rawStringified = asString.canFind('.') ? asString.stripRight("0").stripRight(".") : asString; // Strip trailing zeroes and decimal points 34 } 35 else static if (isSomeString!T) 36 { 37 rawStringified = ( 38 `"`.color(fg.init, bg.init, mode.bold) ~ 39 value ~ 40 `"`.color(fg.init, bg.init, mode.bold) 41 ); 42 } 43 else 44 { 45 rawStringified = value.to!string; 46 } 47 48 immutable bool isMultiline = rawStringified.canFind('\n'); 49 50 return isMultiline ? ("\n" ~ rawStringified.stripLeft("\n").stripRight("\n") ~ "\n") : rawStringified; 51 } 52 53 54 55 private string prettyPrintClassObject(T)(const T object) 56 if (is(T == class)) 57 { 58 import std.range : Appender; 59 60 Appender!string output; 61 62 output.put(T.stringof); 63 output.put(" {\n"); 64 65 static foreach (tup; zip(fieldTypeStrings!T, cast(string[])[ FieldNameTuple!T ])) 66 { 67 output.put(" "); 68 output.put(tup[0]); 69 output.put(" "); 70 output.put(tup[1]); 71 output.put(" = "); 72 output.put(__traits(getMember, object, tup[1]).to!string); 73 output.put(";\n"); 74 } 75 76 output.put("}"); 77 78 return output.data; 79 } 80 81 private string[] fieldTypeStrings(T)() 82 { 83 string[] types; 84 85 foreach (type; Fields!T) 86 { 87 types ~= type.stringof; 88 } 89 90 91 return types; 92 } 93 94 private string prettyPrintTypeInfo(TypeInfo typeInfo) 95 { 96 import std.regex : ctRegex, replaceAll; 97 98 string typeName; 99 100 if (TypeInfo_Tuple tiu = cast(TypeInfo_Tuple) typeInfo) 101 { 102 // The default toString() does not separate the elements with spaces 103 typeName = "(" ~ 104 ( 105 tiu.elements 106 .map!prettyPrintTypeInfo 107 .join(", ") 108 ) ~ ")"; 109 } 110 else 111 { 112 typeName = typeInfo.toString(); 113 } 114 115 return typeName.replaceAll(ctRegex!`immutable\(char\)\[\]`, "string"); 116 } 117 118 private class TestClass {} 119 private interface TestInterface {} 120 private struct TestStruct {} 121 private enum TestEnum { TestEnumValue } 122 private enum int testEnumConst = 4; 123 124 @("prettyPrintTypeInfo — class") 125 unittest 126 { 127 expect(prettyPrintTypeInfo(typeid(TestClass))).toEqual("exceeds_expectations.pretty_print.TestClass"); 128 expect(prettyPrintTypeInfo(TestClass.classinfo)).toEqual("exceeds_expectations.pretty_print.TestClass"); 129 expect(prettyPrintTypeInfo(typeid(new TestClass()))).toEqual("exceeds_expectations.pretty_print.TestClass"); 130 expect(prettyPrintTypeInfo((new TestClass()).classinfo)).toEqual("exceeds_expectations.pretty_print.TestClass"); 131 } 132 133 @("prettyPrintTypeInfo — interface") 134 unittest 135 { 136 expect(prettyPrintTypeInfo(TestInterface.classinfo)).toEqual("exceeds_expectations.pretty_print.TestInterface"); 137 } 138 139 @("prettyPrintTypeInfo — struct") 140 unittest 141 { 142 expect(prettyPrintTypeInfo(typeid(TestStruct))).toEqual("exceeds_expectations.pretty_print.TestStruct"); 143 expect(prettyPrintTypeInfo(typeid(TestStruct()))).toEqual("exceeds_expectations.pretty_print.TestStruct"); 144 } 145 146 @("prettyPrintTypeInfo — int") 147 unittest 148 { 149 expect(prettyPrintTypeInfo(typeid(int))).toEqual("int"); 150 } 151 152 @("prettyPrintTypeInfo — string") 153 unittest 154 { 155 expect(prettyPrintTypeInfo(typeid("Hello World"))).toEqual("string"); 156 } 157 158 @("prettyPrintTypeInfo — static array") 159 unittest 160 { 161 expect(prettyPrintTypeInfo(typeid(int[3]))).toEqual("int[3]"); 162 expect(prettyPrintTypeInfo(typeid(TestClass[3]))).toEqual("exceeds_expectations.pretty_print.TestClass[3]"); 163 expect(prettyPrintTypeInfo(typeid(TestInterface[3]))).toEqual("exceeds_expectations.pretty_print.TestInterface[3]"); 164 } 165 166 @("prettyPrintTypeInfo — dynamic array") 167 unittest 168 { 169 expect(prettyPrintTypeInfo(typeid(int[]))).toEqual("int[]"); 170 expect(prettyPrintTypeInfo(typeid(TestClass[]))).toEqual("exceeds_expectations.pretty_print.TestClass[]"); 171 expect(prettyPrintTypeInfo(typeid(TestInterface[]))).toEqual("exceeds_expectations.pretty_print.TestInterface[]"); 172 } 173 174 @("prettyPrintTypeInfo — enum") 175 unittest 176 { 177 expect(prettyPrintTypeInfo(typeid(TestEnum))).toEqual("exceeds_expectations.pretty_print.TestEnum"); 178 expect(prettyPrintTypeInfo(typeid(TestEnum.TestEnumValue))).toEqual("exceeds_expectations.pretty_print.TestEnum"); 179 expect(prettyPrintTypeInfo(typeid(testEnumConst))).toEqual("int"); 180 } 181 182 @("prettyPrintTypeInfo — associative array") 183 unittest 184 { 185 expect(prettyPrintTypeInfo(typeid(int[string]))).toEqual("int[string]"); 186 expect(prettyPrintTypeInfo(typeid(TestClass[TestInterface]))).toEqual( 187 "exceeds_expectations.pretty_print.TestClass[exceeds_expectations.pretty_print.TestInterface]" 188 ); 189 expect(prettyPrintTypeInfo(typeid(TestInterface[TestClass]))).toEqual( 190 "exceeds_expectations.pretty_print.TestInterface[exceeds_expectations.pretty_print.TestClass]" 191 ); 192 } 193 194 @("prettyPrintTypeInfo — pointer") 195 unittest 196 { 197 expect(prettyPrintTypeInfo(typeid(TestStruct*))).toEqual("exceeds_expectations.pretty_print.TestStruct*"); 198 } 199 200 @("prettyPrintTypeInfo — function") 201 unittest 202 { 203 expect(prettyPrintTypeInfo(typeid(TestClass function(TestInterface ti)))).toEqual( 204 "exceeds_expectations.pretty_print.TestClass function(exceeds_expectations.pretty_print.TestInterface)*" 205 ); 206 207 static int testFn(float x) { return 0; } 208 expect(prettyPrintTypeInfo(typeid(&testFn))).toEqual( 209 "int function(float) pure nothrow @nogc @safe*" 210 ); 211 212 TestStruct* function(int[string]) testFnVar = (aa) => new TestStruct(); 213 expect(prettyPrintTypeInfo(typeid(testFnVar))).toEqual( 214 "exceeds_expectations.pretty_print.TestStruct* function(int[string])*" 215 ); 216 217 expect(prettyPrintTypeInfo(typeid((string s) => 3))).toEqual("int function(string) pure nothrow @nogc @safe*"); 218 } 219 220 @("prettyPrintTypeInfo — delegate") 221 unittest 222 { 223 expect(prettyPrintTypeInfo(typeid(TestClass delegate(TestInterface ti)))).toEqual( 224 "exceeds_expectations.pretty_print.TestClass delegate(exceeds_expectations.pretty_print.TestInterface)" 225 ); 226 227 int y = 4; 228 int testDg(float x) { return y; } 229 expect(prettyPrintTypeInfo(typeid(&testDg))).toEqual( 230 "int delegate(float) pure nothrow @nogc @safe" 231 ); 232 233 string[string] delegate(TestInterface[]) testDgVar = (arr) => ["hello": "world"]; 234 expect(prettyPrintTypeInfo(typeid(testDgVar))).toEqual( 235 "string[string] delegate(exceeds_expectations.pretty_print.TestInterface[])" 236 ); 237 238 immutable int z = 5; 239 expect(prettyPrintTypeInfo(typeid((string _) => cast(int)z))).toEqual( 240 "int delegate(string) pure nothrow @nogc @safe" 241 ); 242 } 243 244 @("prettyPrintTypeInfo — tuple (AliasSeq)") 245 unittest 246 { 247 // import std.typecons : tuple, Tuple; 248 import std.meta : AliasSeq; 249 expect(prettyPrintTypeInfo(typeid(AliasSeq!(string, int, TestStruct*)))).toEqual( 250 "(string, int, exceeds_expectations.pretty_print.TestStruct*)" 251 ); 252 } 253 254 255 package string prettyPrintInheritanceTree(TypeInfo typeInfo, int indentLevel = 0) 256 { 257 expect(typeInfo).toSatisfyAny( 258 (const TypeInfo it) => (cast(TypeInfo_Class) it) !is null, 259 (const TypeInfo it) => (cast(TypeInfo_Interface) it) !is null, 260 ); 261 262 if (TypeInfo_Class tic = cast(TypeInfo_Class) typeInfo) 263 { 264 string superClassesTrace; 265 266 string indentation = ' '.repeat(3 * indentLevel).array; 267 268 if (tic.base !is null && tic.base != typeid(Object)) 269 { 270 superClassesTrace ~= "\n" ~ indentation ~ "<: " ~ prettyPrintInheritanceTree(tic.base, indentLevel + 1); 271 } 272 273 foreach (Interface i; tic.interfaces) 274 { 275 superClassesTrace ~= "\n" ~ indentation ~ "<: " ~ prettyPrintInheritanceTree(i.classinfo, indentLevel + 1); 276 } 277 278 return prettyPrintTypeInfo(typeInfo) ~ superClassesTrace; 279 } 280 281 return prettyPrintTypeInfo(typeInfo); 282 } 283 284 private class Class1 {} 285 private interface Interface1 {} 286 287 @("prettyPrintInheritanceTree — Simple class") 288 unittest 289 { 290 expect(prettyPrintInheritanceTree(typeid(Class1))).toEqual( 291 "exceeds_expectations.pretty_print.Class1" 292 ); 293 } 294 295 @("prettyPrintInheritanceTree — Simple interface") 296 unittest 297 { 298 expect(prettyPrintInheritanceTree(typeid(Interface1))).toEqual( 299 "exceeds_expectations.pretty_print.Interface1" 300 ); 301 } 302 303 private class Class2 : Class1 {} 304 305 @("prettyPrintInheritanceTree — class extending class") 306 unittest 307 { 308 expect(prettyPrintInheritanceTree(typeid(Class2))).toEqual( 309 "exceeds_expectations.pretty_print.Class2\n" ~ 310 "<: exceeds_expectations.pretty_print.Class1" 311 ); 312 } 313 314 private class Class3 : Interface1 {} 315 316 @("prettyPrintInheritanceTree — class implementing interface") 317 unittest 318 { 319 expect(prettyPrintInheritanceTree(typeid(Class3))).toEqual( 320 "exceeds_expectations.pretty_print.Class3\n" ~ 321 "<: exceeds_expectations.pretty_print.Interface1" 322 ); 323 } 324 325 private interface Interface2 {} 326 private class Class4 : Interface1, Interface2 {} 327 328 @("prettyPrintInheritanceTree — class implementing 2 interfaces") 329 unittest 330 { 331 expect(prettyPrintInheritanceTree(typeid(Class4))).toEqual( 332 "exceeds_expectations.pretty_print.Class4\n" ~ 333 "<: exceeds_expectations.pretty_print.Interface1\n" ~ 334 "<: exceeds_expectations.pretty_print.Interface2" 335 ); 336 } 337 338 private class Class5 : Class1, Interface1 {} 339 340 @("prettyPrintInheritanceTree — class extending a class and implementing an interface") 341 unittest 342 { 343 expect(prettyPrintInheritanceTree(typeid(Class5))).toEqual( 344 "exceeds_expectations.pretty_print.Class5\n" ~ 345 "<: exceeds_expectations.pretty_print.Class1\n" ~ 346 "<: exceeds_expectations.pretty_print.Interface1" 347 ); 348 } 349 350 private class Class6 : Class2 {} 351 352 @("prettyPrintInheritanceTree — class extending class extending class") 353 unittest 354 { 355 expect(prettyPrintInheritanceTree(typeid(Class6))).toEqual( 356 "exceeds_expectations.pretty_print.Class6\n" ~ 357 "<: exceeds_expectations.pretty_print.Class2\n" ~ 358 " <: exceeds_expectations.pretty_print.Class1" 359 ); 360 } 361 362 private interface I1 {} 363 private interface I2 {} 364 private interface I3 {} 365 private interface IA {} 366 private interface IB {} 367 private interface IC {} 368 private interface ICC : IC {} 369 370 private class CA : IA {} 371 private class CB3 : IB, I3 {} 372 private class CBC3C : CB3, ICC {} 373 private class CAC2 : CA, IC, I2 {} 374 375 @("prettyPrintInheritanceTree — A complicated inheritance tree") 376 unittest 377 { 378 expect(prettyPrintInheritanceTree(typeid(CBC3C))).toEqual( 379 "exceeds_expectations.pretty_print.CBC3C\n" ~ 380 "<: exceeds_expectations.pretty_print.CB3\n" ~ 381 " <: exceeds_expectations.pretty_print.IB\n" ~ 382 " <: exceeds_expectations.pretty_print.I3\n" ~ 383 "<: exceeds_expectations.pretty_print.ICC\n" ~ 384 " <: exceeds_expectations.pretty_print.IC" 385 ); 386 }