1 /** User-friendly dump. 2 * 3 * See_Also: https://github.com/dlang/phobos/pull/4318 4 */ 5 module nxt.dump; 6 7 import std.exception : enforce; 8 import std.traits : isCallable, ReturnType; 9 import std.range.primitives : isOutputRange, empty, put; 10 import std.format : formatValue, FormatSpec, FormatException; 11 12 private alias enforceFmt = enforce!FormatException; 13 14 /** 15 Pretty-print variables with name and values. 16 It can print will print all compile-time and runtime arguments. 17 The first runtime argument can be a custom OutputRange. The following argument can 18 be a custom format can be given in the $(LREF format) Syntax, where the first 19 value is the name of the variable (or number for runtime arguments), the second 20 it's value and the third it's type. Positional specifier can be used too. 21 If no OutputRange is given, a `string` will be returned. 22 Callable objects will be evaluated. 23 Params: 24 T = names of the variables to print 25 w = The $(D OutputRange) to write to. 26 fmt = The format of the data to read. 27 xs = runtime arguments to print 28 Returns: 29 `void` if an output range was provided as first runtime argument and a `string` 30 otherwise. 31 */ 32 template dump(T...) 33 { 34 // formats compile-time parameters 35 private void dumpCT(Writer, Char)(Writer w, in Char[] fmt, bool printLastSeparator) 36 if (isOutputRange!(Writer, string)) 37 { 38 alias FPfmt = void delegate(Writer wr) @safe; 39 enum maxLength = 3; 40 41 foreach (k, el; T) 42 { 43 auto spec = FormatSpec!Char(fmt); 44 45 // we expose three variables: name, value and typo 46 FPfmt[maxLength] funs; 47 48 // special case for lambdas/alias functions 49 static if (isCallable!el) 50 { 51 funs[0] = (Writer wr) { formatValue(wr, "()", spec); }; 52 funs[1] = (Writer wr) { formatValue(wr, el(), spec); }; 53 funs[2] = (Writer wr) { 54 formatValue(wr, ReturnType!(typeof(el)).stringof, spec); 55 }; 56 } 57 else 58 { 59 funs[0] = (Writer wr) { formatValue(wr, T[k].stringof, spec); }; 60 funs[1] = (Writer wr) { formatValue(wr, el, spec); }; 61 funs[2] = (Writer wr) { 62 formatValue(wr, typeof(el).stringof, spec); 63 }; 64 } 65 66 applySpecToFunctions(w, spec, funs); 67 68 // don't print the trailing bit for the last function except we have 69 // runtime arguments too 70 if (k < T.length - 1 || printLastSeparator) 71 w.put(spec.trailing); 72 } 73 } 74 75 void dump(Writer, Char, Xs...)(Writer w, in Char[] fmt, lazy Xs xs) 76 if (isOutputRange!(Writer, string)) 77 { 78 static if (T.length > 0) 79 dumpCT(w, fmt, Xs.length > 0); 80 81 static if (Xs.length > 0) 82 dumpRuntime(w, fmt, xs); 83 } 84 85 // if no writer passed, return string 86 string dump(Char, Xs...)(in Char[] fmt, lazy Xs xs) 87 { 88 import std.array : appender; 89 90 auto w = appender!string; 91 dump(w, fmt, xs); 92 return w.data; 93 } 94 } 95 96 // formats runtime parameters 97 private void dumpRuntime(Writer, Char, Xs...)(Writer w, in Char[] fmt, lazy Xs xs) 98 if (isOutputRange!(Writer, string)) 99 { 100 import std.format : formatValue, FormatSpec; 101 102 alias FPfmt = void delegate(Writer wr) @safe; 103 enum maxLength = 3; 104 105 foreach (k, el; xs) 106 { 107 auto spec = FormatSpec!Char(fmt); 108 109 // we expose three variables: name, value and typo 110 FPfmt[maxLength] funs; 111 112 static if (isCallable!el) 113 { 114 funs[0] = (Writer wr) { formatValue(wr, "()", spec); }; 115 funs[1] = (Writer wr) { formatValue(wr, el(), spec); }; 116 funs[2] = (Writer wr) { 117 formatValue(wr, ReturnType!(typeof(el)).stringof, spec); 118 }; 119 } 120 else 121 { 122 funs[0] = (Writer wr) { formatValue(wr, k, spec); }; 123 funs[1] = (Writer wr) { formatValue(wr, el, spec); }; 124 funs[2] = (Writer wr) { formatValue(wr, typeof(el).stringof, spec); }; 125 } 126 127 applySpecToFunctions(w, spec, funs); 128 129 if (k < Xs.length - 1) 130 w.put(spec.trailing); 131 } 132 } 133 134 // apply given spec with given printing functions 135 private void applySpecToFunctions(Writer, Char, Fun)(Writer w, ref FormatSpec!Char spec, Fun funs) 136 { 137 enum maxLength = 3; 138 139 uint currentArg = 0; 140 while (spec.writeUpToNextSpecWithoutEnd(w)) 141 { 142 if (currentArg == funs.length && !spec.indexStart) 143 { 144 import std.conv : text; 145 146 // Something went wrong here? 147 enforceFmt(text("Orphan format specifier: %", spec.spec)); 148 break; 149 } 150 if (spec.indexStart > 0) 151 { 152 // positional parameters 153 static if (maxLength > 0) 154 { 155 foreach (i; spec.indexStart - 1 .. spec.indexEnd) 156 { 157 if (funs.length <= i) 158 break; 159 funs[i](w); 160 } 161 } 162 if (currentArg < spec.indexEnd) 163 currentArg = spec.indexEnd; 164 } 165 else 166 { 167 // parameters in given order 168 funs[currentArg++](w); 169 } 170 } 171 } 172 173 // writes the non-spec parts of a format string except for the trailing end 174 private bool writeUpToNextSpecWithoutEnd(Char, OutputRange)( 175 ref FormatSpec!Char spec, OutputRange writer) 176 { 177 with (spec) 178 { 179 if (trailing.empty) 180 return false; 181 for (size_t i = 0; i < trailing.length; ++i) 182 { 183 if (trailing[i] != '%') 184 continue; 185 put(writer, trailing[0 .. i]); 186 trailing = trailing[i .. $]; 187 enforceFmt(trailing.length >= 2, `Unterminated format specifier: "%"`); 188 trailing = trailing[1 .. $]; 189 190 if (trailing[0] != '%') 191 { 192 // Spec found. Fill up the spec, and bailout 193 // TODO: fillUp(); 194 return true; 195 } 196 // Doubled! Reset and Keep going 197 i = 0; 198 } 199 } 200 return false; 201 } 202 203 /// 204 unittest 205 { 206 int x = 5, y = 3; 207 import std.stdio; 208 209 assert(dump!(x, y)("%s = %s, ") == "x = 5, y = 3"); 210 211 // with order 212 assert(dump!(x, y)("%2$s = %1$s, ") == "5 = x, 3 = y"); 213 214 // with runtime args 215 assert(dump!(x, y)("%s = %s, ", () => 42) == "x = 5, y = 3, () = 42"); 216 217 // with runtime args & position-specifier 218 assert(dump!(x, y)("%1$s = %2$s; ", "var1") == "x = 5; y = 3; 0 = var1"); 219 220 // with types 221 assert(dump!(x, y)("(%s: %3$s) = %2$s, ") == "(x: int) = 5, (y: int) = 3"); 222 assert(dump!(x, y)("(%s!%3$s) = %2$s, ") == "(x!int) = 5, (y!int) = 3"); 223 224 // custom separator 225 assert(dump!(x, y)("%s = %s; ") == "x = 5; y = 3"); 226 227 // all printf formatting works 228 assert(dump!(x, y)("%-4s = %4s, ") == "x = 5, y = 3"); 229 230 // special formatting (if applicable for all types) 231 auto z1 = 2.0, z2 = 4.0; 232 assert(dump!(z1, z2)("%s = %.3f & ") == "z1 = 2.000 & z2 = 4.000"); 233 234 // functions 235 assert(dump!(x, y, () => x + y)("%s = %s; ") == "x = 5; y = 3; () = 8"); 236 237 // runtime paramters 238 auto b = (int a) => ++a; 239 assert(dump!(x, y)("%s = %s, ", b(x), x - y) == "x = 5, y = 3, 0 = 6, 1 = 2"); 240 241 // validate laziness 242 auto c = (ref int a) => ++a; 243 assert(dump!(x, y, () => x + y)("%s = %s, ", c(x), x - y) == "x = 5, y = 3, () = 8, 0 = 6, 1 = 3"); 244 assert(dump!(x, y, () => x + y)("%s = %s, ", c(x), x - y) == "x = 6, y = 3, () = 9, 0 = 7, 1 = 4"); 245 } 246 247 // test with output range 248 unittest 249 { 250 import std.array : appender; 251 252 auto x = 2; 253 long y = 4; 254 auto w = appender!string; 255 dump!(x, y)(w, "%s = %s, "); 256 assert(w.data == "x = 2, y = 4"); 257 258 import std.stdio : stdout; 259 260 if (false) 261 dump!(x, y)(stdout.lockingTextWriter(), "%s = %s, "); 262 }