1 module aggregateprinter;
2 
3 import std.conv : to;
4 import std.traits : FieldNameTuple, BaseClassesTuple;
5 import std.typecons : Nullable, nullable;
6 import std.meta : staticMap;
7 import std.format;
8 
9 AggregatePrinter!T aggPrinter(T)(auto ref T t) {
10 	return AggregatePrinter!T(&t);
11 }
12 
13 struct AggregatePrinter(T) {
14 	T* thing;
15 
16 	this(T* thing) {
17 		this.thing = thing;
18 	}
19 
20 	void toString(void delegate(const(char)[]) @safe output ) {
21 		printerImpl(output, *(this.thing));
22 	}
23 }
24 
25 private template ClassFieldsImpl(T) {
26 	enum ClassFieldsImpl = FieldNameTuple!(T);
27 }
28 
29 private template ClassFields(T) {
30 	enum ClassFields = [staticMap!(ClassFieldsImpl, BaseClassesTuple!T)]
31 		~ [ClassFieldsImpl!T];
32 }
33 
34 private template AllFieldNames(T) {
35 	static if(is(T == class)) {
36 		enum AllFieldNames = ClassFields!T;
37 	} else {
38 		enum AllFieldNames = FieldNameTuple!T;
39 	}
40 }
41 
42 private void printerImpl(Out,T)(ref Out o, T t) {
43 	import std.traits : isArray, isSomeString, Unqual;
44 	import graphql : GQLDCustomLeaf;
45 	import nullablestore : NullableStore;
46 	import std.datetime : SysTime, Date, DateTime, TimeOfDay;
47 	import core.time : Duration;
48 
49 	alias UT = Unqual!T;
50 
51 	static if(is(UT == Nullable!Fs, Fs...)) {
52 		if(t.isNull()) {
53 			o("null");
54 		} else {
55 			printerImpl(o, t.get());
56 		}
57 	} else static if(is(UT : GQLDCustomLeaf!K, K...)) {
58 		printerImpl(o, t.value);
59 	} else static if(is(UT : NullableStore!G, G)) {
60 		o(T.stringof);
61  		// NullableStore stores no data
62 	} else static if(isSomeString!T) {
63 		o("\"");
64 		o(to!string(t));
65 		o("\"");
66 	} else static if(isArray!T) {
67 		o("[");
68 		foreach(i; 0 .. t.length) {
69 			o(i > 0 ? ", " : " ");
70 			printerImpl(o, t[i]);
71 		}
72 		o("]");
73 	
74 	} else static if(is(UT : DateTime) || is(UT : TimeOfDay) || is(UT : Date)
75 			|| is(UT : SysTime))
76 	{
77 		o(t.toISOExtString());
78 	} else static if(is(UT == Duration)) {
79 		o(t.toString());
80 	} else static if(is(T == struct) || is(T == class)) {
81 		enum mems = AllFieldNames!T;
82 		o(T.stringof);
83 		o("(");
84 		static if(mems.length > 0) {
85 			o(mems[0]);
86 			o(": ");
87 			enum mOne = mems[0];
88 			printerImpl(o, __traits(getMember, t, mOne));
89 			static foreach(mem; mems[1 .. $]) {
90 				o(", ");
91 				o(mem);
92 				o(": ");
93 				printerImpl(o, __traits(getMember, t, mem));
94 			}
95 		}
96 		o(")");
97 	} else {
98 		o(to!string(t));
99 	}
100 }
101 
102 unittest {
103 	import std.typecons : Nullable;
104 
105 	struct Foo {
106 		int a;
107 		string b;
108 		bool c;
109 		Nullable!string d;
110 	}
111 
112 	auto f = Foo(13, "Hello World", true);
113 	string s = format("%s", aggPrinter(f));
114 	assert(s == `Foo(a: 13, b: "Hello World", c: true, d: null)`, s);
115 }
116 
117 unittest {
118 	import std.typecons : Nullable;
119 	import std.format;
120 
121 	class Bar {
122 		int a;
123 		string b;
124 		bool c;
125 		Nullable!string d;
126 
127 		this(int a, string b, bool c) {
128 			this.a = a;
129 			this.b = b;
130 			this.c = c;
131 		}
132 	}
133 
134 	auto f = new Bar(13, "Hello World", true);
135 	string s = format("%s", aggPrinter(f));
136 	assert(s == `Bar(a: 13, b: "Hello World", c: true, d: null)`, s);
137 }
138 
139 unittest {
140 	import std.typecons : Nullable;
141 	import std.format;
142 
143 	class Cls {
144 		int a;
145 		string b;
146 		bool c;
147 		Nullable!string d;
148 
149 		this(int a, string b, bool c) {
150 			this.a = a;
151 			this.b = b;
152 			this.c = c;
153 		}
154 	}
155 
156 	class SubClass : Cls {
157 		long e;
158 		this(int a, string b, bool c, long e) {
159 			super(a, b, c);
160 			this.e = e;
161 		}
162 	}
163 
164 	auto f = new SubClass(13, "Hello World", true, 1337);
165 	string s = format("%s", aggPrinter(f));
166 	string exp = `SubClass(a: 13, b: "Hello World", c: true, d: null, e: 1337)`;
167 	string as = format("\next: %s\ngot: %s", exp, s);
168 	assert(s == exp, as);
169 }
170 
171 unittest {
172 	import std.typecons : Nullable;
173 
174 	struct Foo {
175 		Nullable!int a;
176 	}
177 
178 	Foo f;
179 	f.a = 1337;
180 	string s = format("%s", aggPrinter(f));
181 	assert(s == "Foo(a: 1337)", s);
182 }
183 unittest {
184 	import std.typecons : Nullable;
185 	import graphql.uda : GQLDCustomLeaf;
186 	import nullablestore;
187 
188 	int toInt(string s) {
189 		return to!int(s);
190 	}
191 
192 	string fromInt(int i) {
193 		return to!string(i);
194 	}
195 
196 	alias QInt = GQLDCustomLeaf!(int, fromInt, toInt);
197 	alias QNInt = GQLDCustomLeaf!(Nullable!int, fromInt, toInt);
198 
199 	struct Foo {
200 		Nullable!int a;
201 		QNInt b;
202 		QInt c;
203 		NullableStore!(int[]) d;
204 	}
205 
206 	Foo f;
207 	f.a = 1337;
208 	string s = format("%s", aggPrinter(f));
209 	string exp = "Foo(a: 1337, b: null, c: 0, d: NullableStore!(int[]))";
210 	assert(s == exp, format("\next: %s\ngot: %s", exp, s));
211 
212 	Foo f2;
213 	f2.a = 1338;
214 	f2.b.value = nullable(37);
215 	s = format("%s", aggPrinter(f2));
216 	exp = "Foo(a: 1338, b: 37, c: 0, d: NullableStore!(int[]))";
217 	assert(s == exp, format("\next: %s\ngot: %s", exp, s));
218 }
219 
220 unittest {
221 	import std.stdio;
222 	import std.datetime;
223 	static struct Foo {
224 		int a;
225 		float b;
226 		DateTime dt;
227 		Date d;
228 		TimeOfDay tod;
229 	}
230 
231 	static struct Bar {
232 		Foo foo;
233 		Nullable!Foo foo2;
234 		string c;
235 		Duration dur;
236 		TickDuration tdur;
237 		Foo[] foos;
238 	}
239 
240 	Bar b;
241 	string s = format("%s", aggPrinter(b));
242 	string exp = 
243 	`Bar(foo: Foo(a: 0, b: nan, dt: 0001-01-01T00:00:00, d: 0001-01-01, tod: 00:00:00), foo2: null, c: "", dur: 0 hnsecs, tdur: TickDuration(length: 0), foos: [])`
244 	;
245 	assert(s == exp, s);
246 
247 	const(Bar) c;
248 	s = format("%s", aggPrinter(c));
249 	exp = 
250 	`const(Bar)(foo: const(Foo)(a: 0, b: nan, dt: 0001-01-01T00:00:00, d: 0001-01-01, tod: 00:00:00), foo2: null, c: "", dur: 0 hnsecs, tdur: const(TickDuration)(length: 0), foos: [])`
251 	;
252 	assert(s == exp, s);
253 }