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 }