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 }