1 /** Pretty Printing.
2  *
3  * Test: dmd -version=show -preview=dip1000 -preview=in -vcolumns -I.. -i -unittest -version=show -main -run prettyio.d
4  *
5  * TODO: Print structures only matching a certain value passed as template
6  *       parameter. Used in print debugging.
7  */
8 module nxt.prettyio;
9 
10 /++ Format of pretty printing.
11  +/
12 struct Format {
13 	/++ Indentation character.
14      +/
15 	string indentation = "\t";
16 
17 	/++ New line terminator.
18 		See: https://en.wikipedia.org/wiki/Newline
19      +/
20 	string newline = "\n";
21 
22 	/++ Flags that (field) types should be shown.
23      +/
24 	bool showType = false;
25 
26 	/++ Flags that `.init` values should be hidden.
27      +/
28 	bool hideInit = false;
29 }
30 
31 /++ Pretty print `arg`.
32  +/
33 void cwritePretty(T)(T arg, in size_t depth = 0, in char[] name = [], in Format fmt = Format.init) {
34 	scope const(void)*[] ptrs;
35 	cwritePretty_!(T)(arg, depth, name, fmt, ptrs);
36 }
37 
38 private void cwritePretty_(T)(T arg, in size_t depth = 0, in char[] name = [], in Format fmt = Format.init, ref scope const(void)*[] ptrs) {
39 	import std.traits : isArray, isSomeString, isSomeChar, isPointer, hasMember;
40 	import std.stdio : wr = write;
41 
42 	void doIndent() {
43 		foreach (_; 0 .. depth) wr(fmt.indentation);
44 	}
45 
46 	void doFieldName() {
47 		if (name) wr(name, ": ");
48 	}
49 
50 	void doTypeName() {
51 		if (fmt.showType) wr(T.stringof, " ");
52 	}
53 
54 	void doAddress(in void* ptr) {
55 		wr('@', ptr);
56 	}
57 
58 	static if (is(T == struct) || is(T == class) || is(T == union)) {
59 		import std.traits : FieldNameTuple;
60 		void doMembers() {
61 			foreach (memberName; FieldNameTuple!T)
62 				cwritePretty_(__traits(getMember, arg, memberName), depth + 1,
63 								   memberName, fmt, ptrs);
64 		}
65 		void doAggregate() {
66 			/* TODO: Using class.toString requires special care here because
67 			   Object.toString defaults to its qualified type name: */
68 			static if ((is(T == struct) || is(T == union)) && hasMember!(T, "toString")) {
69 				const str = arg.toString;
70 				if (str !is null)
71 					wr('"', str, '"', fmt.newline);
72 				else
73 					wr("[]", fmt.newline); /+ TODO: use cwriteMembers(); instead +/
74 			} else {
75 				if (fmt.hideInit && arg is arg.init) {
76 					wr(".init", fmt.newline);
77 				} else {
78 					wr("{", fmt.newline);
79 					doMembers();
80 					doIndent();
81 					wr("}", fmt.newline);
82 				}
83 			}
84 		}
85 	}
86 
87 	doIndent();
88 	doFieldName();
89 	doTypeName();
90 
91 	static if (is(T == class)) {
92 		const(void)* ptr;
93 		() @trusted { ptr = cast(void*)arg; }();
94 		doAddress(ptr);
95 		if (arg is null) {
96 			wr(fmt.newline);
97 		} else {
98 			const ix = ptrs.indexOf(ptr);
99 			wr(' ');
100 			if (ix != -1) { // `ptr` already printed
101 				wr("#", ix, fmt.newline); // Emacs-Lisp-style back-reference
102 			} else {
103 				ptrs ~= ptr;
104 				wr("#", ptrs.length, ' '); // Emacs-Lisp-style back-reference
105 				doAggregate();
106 			}
107 		}
108 	} else static if (is(T == union) || is(T == struct)) {
109 		doAggregate();
110     } else static if (isPointer!T) {
111 		const ptr = cast(void*)arg;
112 		doAddress(ptr);
113 		if (arg is null) {
114 			wr(fmt.newline);
115 		} else {
116 			const ix = ptrs.indexOf(ptr);
117 			wr(' ');
118 			if (ix != -1) { // `ptr` already printed
119 				wr("#", ix, fmt.newline); // Emacs-Lisp-style back-reference
120 			} else {
121 				wr("#", ptrs.length, " -> "); // Emacs-Lisp-style back-reference
122 				ptrs ~= ptr;
123 				static if (is(immutable typeof(*arg))) { // `arg` is not `void*`
124 					cwritePretty_(*arg, depth, [], fmt, ptrs);
125 				}
126 			}
127 		}
128     } else static if (isSomeString!T) {
129 		if (arg !is null)
130 			wr('"', arg, '"', fmt.newline);
131 		else
132 			wr("[]", fmt.newline);
133     } else static if (isSomeChar!T) {
134         wr(`'`, arg, `'`, fmt.newline);
135     } else static if (isArray!T) {
136         wr("[");
137 		if (arg.length) { // non-empty
138 			import std.range.primitives : ElementType;
139 			alias E = ElementType!(T);
140 			enum scalarE = __traits(isScalar, E);
141 			static if (!scalarE)
142 				wr(fmt.newline);
143 			foreach (const i, ref element; arg) {
144 				static if (scalarE) {
145 					if (i != 0)
146 						wr(',');
147 					cwritePretty_(element, 0, [],
148 								  Format(indentation: [], newLine: [], showType: false, hideInit: false), ptrs);
149 				} else {
150 					cwritePretty_(element, depth + 1, [], fmt, ptrs);
151 				}
152 			}
153 			static if (!scalarE)
154 				doIndent();
155 		}
156 		wr("]", fmt.newline);
157     } else static if (__traits(isAssociativeArray, T)) {
158         wr(arg, fmt.newline);
159     } else {
160         wr(arg, fmt.newline);
161     }
162 }
163 
164 /++ Colorized version of `std.stdio.write`.
165  +/
166 void cwrite(T...)(T args) {
167 	import std.stdio : sw = write;
168 	version (none) import nxt.ansi_escape : putWithSGRs; /+ TODO: use below: +/
169 	// alias w = pathWrapperSymbol;
170 	static foreach (arg; args) {{
171 		static immutable S = typeof(arg).stringof;
172 		// pragma(msg, __FILE__, "(", __LINE__, ",1): Debug: ", S);
173 		// static if (S == "URL")
174 		// 	sw(w, arg, w); /+ TODO: SGR.yellowForegroundColor +/
175 		// else static if (S == "Path")
176 		// 	sw(w, arg, w); /+ TODO: SGR.whiteForegroundColor +/
177 		// else static if (S == "FilePath")
178 		// 	sw(w, arg, w); /+ TODO: SGR.whiteForegroundColor +/
179 		// else static if (S == "DirPath")
180 		// 	sw(w, arg, w); /+ TODO: SGR.cyanForegroundColor +/
181 		// else static if (S == "ExePath")
182 		// 	sw(w, arg, w); /+ TODO: SGR.lightRedForegroundColor +/
183 		// else static if (S == "FileName")
184 		// 	sw(w, arg, w); /+ TODO: SGR.whiteForegroundColor +/
185 		// else static if (S == "DirName")
186 		// 	sw(w, arg, w); /+ TODO: SGR.redForegroundColor +/
187 		// else static if (S == "ExitStatus")
188 		// 	sw(w, arg, w); /+ TODO: SGR.greenForegroundColor if arg == 0, otherwise SGR.redForegroundColor +/
189 		// else
190 		sw(arg);
191 	}}
192 }
193 
194 /++ Wrapper symbol when printing paths and URLs to standard out (`stdout`) and standard error (`stderr`).
195  +/
196 private static immutable string pathWrapperSymbol = `"`;
197 
198 /++ Colorized version of `std.stdio.writeln`.
199  +/
200 void cwriteln(T...)(T args) {
201 	import std.stdio : swln = writeln;
202 	cwrite!(T)(args);
203 	swln();
204 }
205 
206 version (show)
207 unittest {
208 	@safe struct P {
209 		double x;
210 		double y;
211 		double z;
212 	}
213 
214 	@safe union U {
215 		void* ptr;
216 		size_t word;
217 	}
218 
219 	@safe struct S {
220 		int x;
221 		double y;
222 		char[3] c3 = "abc";
223 		wchar[3] wc3 = "abc";
224 		dchar[3] dc3 = "abc";
225 		string w3 = "xyz";
226 		wstring ws3 = "xyz";
227 		dstring ds3 = "xyz";
228 		string ns = []; // null string
229 		string s = ""; // non-null empty string
230 		int[3] i3;
231 		float[3] f3;
232 		double[3] d3;
233 		real[3] r3;
234 		int[string] ais = ["a":1, "b":2];
235 		P[string] ps = ["a":P(1,2,3)];
236 		P p0;
237 		P* pp0 = null;
238 		P* pp1 = new P(1,2,3);
239 		U u;
240 	}
241 
242 	@safe class Cls {
243 		this(int x) {
244 			this.x = x;
245 			this.parent = this;
246 		}
247 		int x;
248 		Cls parent;
249 	}
250 
251 	@safe struct Top {
252 		S s;
253 		S[2] s2;
254 		Cls cls;
255 		Cls clsNull;
256 		string name;
257 		int[] numbers;
258 	}
259 
260 	S s = S(10, 20.5);
261     Top top = { s, [s,s], new Cls(1), null, "example", [1, 2, 3] };
262     top.cwritePretty(0, "top", Format(indentation: "\t", newline:  "\n", showType: false, hideInit: false));
263     top.cwritePretty(0, "top", Format(indentation: "\t", newline: "\n", showType: false, hideInit: true));
264     top.cwritePretty(0, "top", Format(indentation: "\t", newline: "\n", showType: true, hideInit: false));
265     top.cwritePretty(0, "top", Format(indentation: "\t", newline: "\n", showType: true, hideInit: true));
266 }
267 
268 /** Array-specialization of `indexOf` with default predicate.
269  *
270  * TODO: Add optimized implementation for needles with length >=
271  * `largeNeedleLength` with no repeat of elements.
272  */
273 ptrdiff_t indexOf(T)(scope inout(T)[] haystack,
274 					 scope const(T)[] needle) @trusted {
275 	// enum largeNeedleLength = 4;
276 	if (haystack.length < needle.length)
277 		return -1;
278 	foreach (const offset; 0 .. haystack.length - needle.length + 1)
279 		if (haystack.ptr[offset .. offset + needle.length] == needle)
280 			return offset;
281 	return -1;
282 }
283 /// ditto
284 ptrdiff_t indexOf(T)(scope inout(T)[] haystack,
285 					 scope const T needle) {
286 	static if (is(T == char))
287 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
288 	foreach (const offset, const ref element; haystack)
289 		if (element == needle)
290 			return offset;
291 	return -1;
292 }