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 }