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