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;
44 	import graphql : GQLDCustomLeaf;
45 	import nullablestore : NullableStore;
46 	import std.datetime : SysTime, Date, DateTime, TimeOfDay;
47 	import core.time : Duration;
48 
49 	static if(is(T == Nullable!Fs, Fs...)) {
50 		if(t.isNull()) {
51 			o("null");
52 		} else {
53 			printerImpl(o, t.get());
54 		}
55 	} else static if(is(T : GQLDCustomLeaf!K, K...)) {
56 		printerImpl(o, t.value);
57 	} else static if(is(T : NullableStore!G, G)) {
58 		o(T.stringof);
59  		// NullableStore stores no data
60 	} else static if(isSomeString!T) {
61 		o("\"");
62 		o(to!string(t));
63 		o("\"");
64 	} else static if(isArray!T) {
65 		o("[");
66 		foreach(i; 0 .. t.length) {
67 			o(i > 0 ? ", " : " ");
68 			printerImpl(o, t[i]);
69 		}
70 		o("]");
71 	
72 	} else static if(is(T == DateTime) || is(T == TimeOfDay) || is(T == Date)
73 			|| is(T == SysTime))
74 	{
75 		o(t.toISOExtString());
76 	} else static if(is(T == Duration)) {
77 		o(t.toString());
78 	} else static if(is(T == struct) || is(T == class)) {
79 		enum mems = AllFieldNames!T;
80 		o(T.stringof);
81 		o("(");
82 		static if(mems.length > 0) {
83 			o(mems[0]);
84 			o(": ");
85 			enum mOne = mems[0];
86 			printerImpl(o, __traits(getMember, t, mOne));
87 			static foreach(mem; mems[1 .. $]) {
88 				o(", ");
89 				o(mem);
90 				o(": ");
91 				printerImpl(o, __traits(getMember, t, mem));
92 			}
93 		}
94 		o(")");
95 	} else {
96 		o(to!string(t));
97 	}
98 }
99 
100 unittest {
101 	import std.typecons : Nullable;
102 
103 	struct Foo {
104 		int a;
105 		string b;
106 		bool c;
107 		Nullable!string d;
108 	}
109 
110 	auto f = Foo(13, "Hello World", true);
111 	string s = format("%s", aggPrinter(f));
112 	assert(s == `Foo(a: 13, b: "Hello World", c: true, d: null)`, s);
113 }
114 
115 unittest {
116 	import std.typecons : Nullable;
117 	import std.format;
118 
119 	class Bar {
120 		int a;
121 		string b;
122 		bool c;
123 		Nullable!string d;
124 
125 		this(int a, string b, bool c) {
126 			this.a = a;
127 			this.b = b;
128 			this.c = c;
129 		}
130 	}
131 
132 	auto f = new Bar(13, "Hello World", true);
133 	string s = format("%s", aggPrinter(f));
134 	assert(s == `Bar(a: 13, b: "Hello World", c: true, d: null)`, s);
135 }
136 
137 unittest {
138 	import std.typecons : Nullable;
139 	import std.format;
140 
141 	class Cls {
142 		int a;
143 		string b;
144 		bool c;
145 		Nullable!string d;
146 
147 		this(int a, string b, bool c) {
148 			this.a = a;
149 			this.b = b;
150 			this.c = c;
151 		}
152 	}
153 
154 	class SubClass : Cls {
155 		long e;
156 		this(int a, string b, bool c, long e) {
157 			super(a, b, c);
158 			this.e = e;
159 		}
160 	}
161 
162 	auto f = new SubClass(13, "Hello World", true, 1337);
163 	string s = format("%s", aggPrinter(f));
164 	string exp = `SubClass(a: 13, b: "Hello World", c: true, d: null, e: 1337)`;
165 	string as = format("\next: %s\ngot: %s", exp, s);
166 	assert(s == exp, as);
167 }
168 
169 unittest {
170 	import std.typecons : Nullable;
171 
172 	struct Foo {
173 		Nullable!int a;
174 	}
175 
176 	Foo f;
177 	f.a = 1337;
178 	string s = format("%s", aggPrinter(f));
179 	assert(s == "Foo(a: 1337)", s);
180 }
181 unittest {
182 	import std.typecons : Nullable;
183 	import graphql.uda : GQLDCustomLeaf;
184 	import nullablestore;
185 
186 	int toInt(string s) {
187 		return to!int(s);
188 	}
189 
190 	string fromInt(int i) {
191 		return to!string(i);
192 	}
193 
194 	alias QInt = GQLDCustomLeaf!(int, fromInt, toInt);
195 	alias QNInt = GQLDCustomLeaf!(Nullable!int, fromInt, toInt);
196 
197 	struct Foo {
198 		Nullable!int a;
199 		QNInt b;
200 		QInt c;
201 		NullableStore!(int[]) d;
202 	}
203 
204 	Foo f;
205 	f.a = 1337;
206 	string s = format("%s", aggPrinter(f));
207 	string exp = "Foo(a: 1337, b: null, c: 0, d: NullableStore!(int[]))";
208 	assert(s == exp, format("\next: %s\ngot: %s", exp, s));
209 
210 	Foo f2;
211 	f2.a = 1338;
212 	f2.b.value = nullable(37);
213 	s = format("%s", aggPrinter(f2));
214 	exp = "Foo(a: 1338, b: 37, c: 0, d: NullableStore!(int[]))";
215 	assert(s == exp, format("\next: %s\ngot: %s", exp, s));
216 }
217 
218 unittest {
219 	import std.stdio;
220 	import std.datetime;
221 	static struct Foo {
222 		int a;
223 		float b;
224 		DateTime dt;
225 		Date d;
226 		TimeOfDay tod;
227 	}
228 
229 	static struct Bar {
230 		Foo foo;
231 		Nullable!Foo foo2;
232 		string c;
233 		Duration dur;
234 		TickDuration tdur;
235 		Foo[] foos;
236 	}
237 
238 	Bar b;
239 	string s = format("%s", aggPrinter(b));
240 	string exp = 
241 	`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: [])`
242 	;
243 	assert(s == exp, s);
244 }