1 /**
2 * Functor-powered lazy @nogc text formatting.
3 *
4 * License:
5 * This Source Code Form is subject to the terms of
6 * the Mozilla Public License, v. 2.0. If a copy of
7 * the MPL was not distributed with this file, You
8 * can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * Authors:
11 * Vladimir Panteleev <ae@cy.md>
12 */13 14 moduleae.utils.text.functor;
15 16 importstd.format : formattedWrite, formatValue, FormatSpec;
17 importstd.functional : forward;
18 importstd.range.primitives : isOutputRange;
19 20 importae.utils.functor.composition : isFunctor, select, seq;
21 importae.utils.functor.primitives : functor;
22 importae.utils.meta : tupleMap, I;
23 24 /// Given zero or more values, returns a functor which retains a copy of these values;25 /// the functor can later be called with a sink, which will make it write the values out.26 /// The returned functor's signature varies depending on whether a27 /// format string is specified, but either way compatible with28 /// `toString` signatures accepted by `formattedWrite`29 /// If a format string is specified, that will be used to format the values;30 /// otherwise, a format string will be accepted at call time.31 /// For details, see accepted `toString` signatures in the32 /// "Structs, Unions, Classes, and Interfaces" section of33 /// https://dlang.org/phobos/std_format_write.html.34 templateformattingFunctor(
35 stringfmt = null,
36 intline = __LINE__, // https://issues.dlang.org/show_bug.cgi?id=2390437 T...)
38 {
39 staticif (fmt)
40 aliasfun =
41 (Tvalues, refw)
42 {
43 w.formattedWrite!fmt(values);
44 };
45 else46 aliasfun =
47 (Tvalues, refw, constreffmt)
48 {
49 foreach (refvalue; values)
50 w.formatValue(value, fmt);
51 };
52 53 autoformattingFunctor(autorefTvalues)
54 {
55 returnfunctor!fun(forward!values);
56 }
57 }
58 59 ///60 debug(ae_unittest) unittest61 {
62 importstd.array : appender;
63 importstd.format : singleSpec;
64 65 autoa = appender!string;
66 autospec = "%03d".singleSpec;
67 formattingFunctor(5)(a, spec);
68 assert(a.data == "005");
69 }
70 71 ///72 debug(ae_unittest) unittest73 {
74 importstd.array : appender;
75 autoa = appender!string;
76 formattingFunctor!"%03d"(5)(a); // or `&a.put!(const(char)[])`77 assert(a.data == "005");
78 }
79 80 /// Constructs a stringifiable object from a functor.81 autostringifiable(F)(Ffunctor)
82 if (isFunctor!F)
83 {
84 // std.format uses speculative compilation to detect compatibility.85 // As such, any error in the function will just cause the86 // object to be silently stringified as "Stringifiable(Functor(...))".87 // To avoid that, try an explicit instantiation here to88 // get detailed information about any errors in the function.89 debugif (false)
90 {
91 // Because std.format accepts any one of several signatures,92 // try all valid combinations to first check that at least one93 // is accepted.94 FormatSpec!charfc;
95 FormatSpec!wcharfw;
96 FormatSpec!dcharfd;
97 structDummyWriter(Char) { voidput(Charc) {} }
98 DummyWriter!charwc;
99 DummyWriter!wcharww;
100 DummyWriter!dcharwd;
101 voiddummySink(const(char)[]) {}
102 staticif(
103 !is(typeof(functor(wc, fc))) &&
104 !is(typeof(functor(ww, fw))) &&
105 !is(typeof(functor(wd, fd))) &&
106 !is(typeof(functor(wc))) &&
107 !is(typeof(functor(ww))) &&
108 !is(typeof(functor(wd))) &&
109 !is(typeof(functor(&dummySink))))
110 {
111 // None were valid; try non-speculatively with the simplest one:112 pragma(msg, "Functor ", F.stringof, " does not successfully instantiate with any toString signatures.");
113 pragma(msg, "Attempting to non-speculatively instantiate with delegate sink:");
114 functor(&dummySink);
115 }
116 }
117 118 staticstructStringifiable119 {
120 Ffunctor;
121 122 voidtoString(thisThis, Writer, Char)(refWriterwriter, constrefFormatSpec!Charfmt)
123 if (isOutputRange!(Writer, Char))
124 {
125 functor(writer, fmt);
126 }
127 128 voidtoString(thisThis, Writer)(refWriterwriter)
129 {
130 functor(writer);
131 }
132 133 voidtoString(thisThis)(scopevoiddelegate(const(char)[]) sink)
134 {
135 functor(sink);
136 }
137 }
138 returnStringifiable(functor);
139 }
140 141 ///142 debug(ae_unittest) unittest143 {
144 importstd.conv : text;
145 autof = (voiddelegate(const(char)[]) sink) => sink("Hello");
146 assert(stringifiable(f).text == "Hello", stringifiable(f).text);
147 }
148 149 /// Constructs a stringifiable object from a value150 /// (i.e., a lazily formatted object).151 /// Combines `formattingFunctor` and `stringifiable`.152 autoformatted(stringfmt = null, T...)(autorefTvalues)
153 {
154 returnvalues155 .formattingFunctor!fmt()
156 .stringifiable;
157 }
158 159 ///160 debug(ae_unittest) unittest161 {
162 importstd.conv : text;
163 importstd.format : format;
164 assert(formatted(5).text == "5");
165 assert(formatted!"%03d"(5).text == "005");
166 assert(format!"%s%s%s"("<", formatted!"%x"(64), ">") == "<40>");
167 assert(format!"<%03d>"(formatted(5)) == "<005>");
168 }
169 170 /// Constructs a functor type from a function alias, and wraps it into171 /// a stringifiable object. Can be used to create stringifiable172 /// widgets which need a sink for more complex behavior.173 templatestringifiable(aliasfun, T...)
174 {
175 autostringifiable()(autorefTvalues)
176 {
177 returnvalues178 .functor!fun()
179 .I!(.stringifiable);
180 }
181 }
182 183 ///184 debug(ae_unittest) unittest185 {
186 aliashumanSize = stringifiable!(
187 (size, sink)
188 {
189 importstd.format : formattedWrite;
190 if (!size)
191 // You would otherwise need to wrap everything in fmtIf:192 returnsink("0");
193 staticimmutableprefixChars = " KMGTPEZY";
194 size_tpower = 0;
195 while (size > 1000 && power + 1 < prefixChars.length)
196 size /= 1024, power++;
197 sink.formattedWrite!"%s %sB"(size, prefixChars[power]);
198 }, real);
199 200 importstd.conv : text;
201 assert(humanSize(0).text == "0");
202 assert(humanSize(8192).text == "8 KB");
203 }
204 205 /// Returns an object which, depending on a condition, is stringified206 /// as one of two objects.207 /// The two branches should themselves be passed as nullary functors,208 /// to enable lazy evaluation.209 /// Combines `formattingFunctor`, `stringifiable`, and `select`.210 autofmtIf(stringfmt = null, Cond, T, F)(Condcond, Tt, Ff) @nogc211 if (isFunctor!T && isFunctor!F)
212 {
213 // Store the value-returning functor into a new functor, which will accept a sink.214 // When the new functor is called, evaluate the value-returning functor,215 // put the value into a formatting functor, and immediately call it with the sink.216 217 // Must be explicitly static due to218 // https://issues.dlang.org/show_bug.cgi?id=23896 :219 staticvoidfun(X, Sink...)(Xx, autorefSinksink)
220 {
221 x().formattingFunctor!fmt()(forward!sink);
222 }
223 returnselect(
224 cond,
225 functor!fun(t),
226 functor!fun(f),
227 ).stringifiable;
228 }
229 230 ///231 debug(ae_unittest) unittest232 {
233 importstd.conv : text;
234 assert(fmtIf(true , () => 5, () => "apple").text == "5");
235 assert(fmtIf(false, () => 5, () => "apple").text == "apple");
236 237 // Scope lazy when? https://issues.dlang.org/show_bug.cgi?id=12647238 autodivision(inta, intb) { returnfmtIf(b != 0, () => a / b, () => "NaN"); }
239 assert(division(4, 2).text == "2");
240 assert(division(4, 0).text == "NaN");
241 }
242 243 /// Returns an object which is stringified as all of the given objects244 /// in sequence. In essence, a lazy `std.conv.text`.245 /// Combines `formattingFunctor`, `stringifiable`, and `seq`.246 autofmtSeq(stringfmt = "%s", Values...)(Valuesvalues) @nogc247 {
248 return249 values250 .tupleMap!((refvalue) => formattingFunctor!fmt(value)).expand251 .seq252 .stringifiable;
253 }
254 255 debug(ae_unittest) unittest256 {
257 importstd.conv : text;
258 assert(fmtSeq(5, " ", "apple").text == "5 apple");
259 }
260