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