Skip to content

Commit 7e5250e

Browse files
committed
feat: whitespace control specs
1 parent d8bbf98 commit 7e5250e

File tree

6 files changed

+865
-71
lines changed

6 files changed

+865
-71
lines changed

include/mrdox/Support/Handlebars.hpp

+15-8
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ struct HandlebarsOptions
5454
remove the whitespace on that line
5555
*/
5656
bool ignoreStandalone = false;
57+
58+
/** Custom private data object
59+
60+
This variable can be used to pass in an object to define custom
61+
private variables.
62+
*/
63+
dom::Value data = {nullptr};
5764
};
5865

5966
namespace detail {
@@ -835,8 +842,8 @@ class Handlebars {
835842
std::string
836843
render(
837844
std::string_view templateText,
838-
dom::Value const & context = {},
839-
HandlebarsOptions options = {}) const;
845+
dom::Value const& context = {},
846+
HandlebarsOptions const& options = {}) const;
840847

841848
/** Render a handlebars template
842849
@@ -855,8 +862,8 @@ class Handlebars {
855862
render_to(
856863
OutputRef& out,
857864
std::string_view templateText,
858-
dom::Value const & context,
859-
HandlebarsOptions options = {}) const;
865+
dom::Value const& context,
866+
HandlebarsOptions const& options = {}) const;
860867

861868
/** Register a partial
862869
@@ -1096,15 +1103,15 @@ class Handlebars {
10961103
render_to(
10971104
OutputRef& out,
10981105
dom::Value const &context,
1099-
HandlebarsOptions opt,
1106+
HandlebarsOptions const& opt,
11001107
detail::RenderState& state) const;
11011108

11021109
void
11031110
renderTag(
11041111
Tag const& tag,
11051112
OutputRef& out,
11061113
dom::Value const &context,
1107-
HandlebarsOptions opt,
1114+
HandlebarsOptions const& opt,
11081115
detail::RenderState& state) const;
11091116

11101117
void
@@ -1121,7 +1128,7 @@ class Handlebars {
11211128
Handlebars::Tag const& tag,
11221129
OutputRef &out,
11231130
dom::Value const& context,
1124-
HandlebarsOptions &opt,
1131+
HandlebarsOptions const& opt,
11251132
detail::RenderState& state) const;
11261133

11271134
void
@@ -1145,7 +1152,7 @@ class Handlebars {
11451152
dom::Value const& context,
11461153
detail::RenderState& state,
11471154
dom::Array &args,
1148-
HandlebarsCallback &options) const;
1155+
HandlebarsCallback& opt) const;
11491156

11501157
std::pair<dom::Value, bool>
11511158
evalExpr(

src/lib/Dom/Value.cpp

+48-6
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,44 @@ swap(
282282

283283
//------------------------------------------------
284284

285+
void
286+
JSON_escape(
287+
std::string& dest,
288+
std::string_view value)
289+
{
290+
dest.reserve(dest.size() + value.size());
291+
for(auto c : value)
292+
{
293+
switch (c)
294+
{
295+
case '"':
296+
dest.append("\\\"");
297+
break;
298+
case '\\':
299+
dest.append("\\\\");
300+
break;
301+
case '\b':
302+
dest.append("\\b");
303+
break;
304+
case '\f':
305+
dest.append("\\f");
306+
break;
307+
case '\n':
308+
dest.append("\\n");
309+
break;
310+
case '\r':
311+
dest.append("\\r");
312+
break;
313+
case '\t':
314+
dest.append("\\t");
315+
break;
316+
default:
317+
dest.push_back(c);
318+
break;
319+
}
320+
}
321+
}
322+
285323
static
286324
void
287325
JSON_stringify(
@@ -306,13 +344,12 @@ JSON_stringify(
306344
break;
307345
case Kind::String:
308346
{
309-
// VFALCO need escapes
310347
std::string_view const s =
311348
value.getString().get();
312349
dest.reserve(dest.size() +
313350
1 + s.size() + 1);
314351
dest.push_back('"');
315-
dest.append(s.data(), s.size());
352+
JSON_escape(dest, s);
316353
dest.push_back('"');
317354
break;
318355
}
@@ -337,10 +374,11 @@ JSON_stringify(
337374
JSON_stringify(dest, arr.get(i), indent, visited);
338375
if(i != arr.size() - 1)
339376
dest.push_back(',');
340-
else
341-
dest.append(" ]\n");
377+
dest.push_back('\n');
342378
}
343379
indent.resize(indent.size() - 4);
380+
dest.append(indent);
381+
dest.append("]");
344382
break;
345383
}
346384
case Kind::Object:
@@ -363,15 +401,19 @@ JSON_stringify(
363401
auto it0 = it++;
364402
dest.append(indent);
365403
dest.push_back('"');
404+
JSON_escape(dest, it0->key);
366405
dest.append("\" : ");
367406
JSON_stringify(dest,
368407
it0->value, indent, visited);
369408
if(it != obj.end())
409+
{
370410
dest.push_back(',');
371-
else
372-
dest.append(" }\n");
411+
}
412+
dest.push_back('\n');
373413
}
374414
indent.resize(indent.size() - 4);
415+
dest.append(indent);
416+
dest.append("}");
375417
break;
376418
}
377419
case Kind::Function:

src/lib/Support/Handlebars.cpp

+77-19
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,13 @@ format_to(
219219

220220
constexpr
221221
std::string_view
222-
trim_spaces(std::string_view expression)
222+
trim_delimiters(std::string_view expression, std::string_view delimiters)
223223
{
224-
auto pos = expression.find_first_not_of(" \t\r\n");
224+
auto pos = expression.find_first_not_of(delimiters);
225225
if (pos == std::string_view::npos)
226226
return "";
227227
expression.remove_prefix(pos);
228-
pos = expression.find_last_not_of(" \t\r\n");
228+
pos = expression.find_last_not_of(delimiters);
229229
if (pos == std::string_view::npos)
230230
return "";
231231
expression.remove_suffix(expression.size() - pos - 1);
@@ -234,9 +234,9 @@ trim_spaces(std::string_view expression)
234234

235235
constexpr
236236
std::string_view
237-
trim_lspaces(std::string_view expression)
237+
trim_ldelimiters(std::string_view expression, std::string_view delimiters)
238238
{
239-
auto pos = expression.find_first_not_of(" \t\r\n");
239+
auto pos = expression.find_first_not_of(delimiters);
240240
if (pos == std::string_view::npos)
241241
return "";
242242
expression.remove_prefix(pos);
@@ -245,15 +245,36 @@ trim_lspaces(std::string_view expression)
245245

246246
constexpr
247247
std::string_view
248-
trim_rspaces(std::string_view expression)
248+
trim_rdelimiters(std::string_view expression, std::string_view delimiters)
249249
{
250-
auto pos = expression.find_last_not_of(" \t\r\n");
250+
auto pos = expression.find_last_not_of(delimiters);
251251
if (pos == std::string_view::npos)
252252
return "";
253253
expression.remove_suffix(expression.size() - pos - 1);
254254
return expression;
255255
}
256256

257+
constexpr
258+
std::string_view
259+
trim_spaces(std::string_view expression)
260+
{
261+
return trim_delimiters(expression, " \t\r\n");
262+
}
263+
264+
constexpr
265+
std::string_view
266+
trim_lspaces(std::string_view expression)
267+
{
268+
return trim_ldelimiters(expression, " \t\r\n");
269+
}
270+
271+
constexpr
272+
std::string_view
273+
trim_rspaces(std::string_view expression)
274+
{
275+
return trim_rdelimiters(expression, " \t\r\n");
276+
}
277+
257278
// ==============================================================
258279
// Helper Callback
259280
// ==============================================================
@@ -658,7 +679,7 @@ Handlebars::
658679
render(
659680
std::string_view templateText,
660681
dom::Value const & context,
661-
HandlebarsOptions options) const
682+
HandlebarsOptions const& options) const
662683
{
663684
std::string out;
664685
OutputRef os(out);
@@ -889,6 +910,18 @@ parseTag(std::string_view tagStr)
889910
tagStr = trim_spaces(tagStr);
890911
}
891912

913+
// Force no HTML escape after whitespace removal
914+
if (!tagStr.empty() && tagStr.front() == '{' && tagStr.back() == '}')
915+
{
916+
t.forceNoHTMLEscape = true;
917+
tagStr = tagStr.substr(1, tagStr.size() - 2);
918+
if (!tagStr.empty() && tagStr.front() == '{' && tagStr.back() == '}')
919+
{
920+
t.rawBlock = true;
921+
tagStr = tagStr.substr(1, tagStr.size() - 2);
922+
}
923+
}
924+
892925
// Empty tag
893926
if (tagStr.empty())
894927
{
@@ -980,14 +1013,21 @@ Handlebars::
9801013
render_to(
9811014
OutputRef& out,
9821015
std::string_view templateText,
983-
dom::Value const & context,
984-
HandlebarsOptions options) const
1016+
dom::Value const& context,
1017+
HandlebarsOptions const& options) const
9851018
{
9861019
detail::RenderState state;
9871020
state.templateText0 = templateText;
9881021
state.templateText = templateText;
989-
state.data.set("root", context);
990-
state.data.set("level", "warn");
1022+
if (options.data.isNull())
1023+
{
1024+
state.data.set("root", context);
1025+
state.data.set("level", "warn");
1026+
}
1027+
else if (options.data.isObject())
1028+
{
1029+
state.data = options.data.getObject();
1030+
}
9911031
render_to(out, context, options, state);
9921032
}
9931033

@@ -996,7 +1036,7 @@ Handlebars::
9961036
render_to(
9971037
OutputRef& out,
9981038
dom::Value const& context,
999-
HandlebarsOptions opt,
1039+
HandlebarsOptions const& opt,
10001040
detail::RenderState& state) const
10011041
{
10021042
while (!state.templateText.empty()) {
@@ -1485,9 +1525,9 @@ renderTag(
14851525
Tag const& tag,
14861526
OutputRef& out,
14871527
dom::Value const& context,
1488-
HandlebarsOptions opt,
1528+
HandlebarsOptions const& opt,
14891529
detail::RenderState& state) const {
1490-
if (tag.type == '#')
1530+
if (tag.type == '#' || tag.type == '^')
14911531
{
14921532
renderBlock(tag.helper, tag, out, context, opt, state);
14931533
}
@@ -1588,7 +1628,7 @@ setupArgs(
15881628
dom::Value const& context,
15891629
detail::RenderState & state,
15901630
dom::Array &args,
1591-
HandlebarsCallback &cb) const
1631+
HandlebarsCallback& cb) const
15921632
{
15931633
std::string_view expr;
15941634
while (findExpr(expr, expression))
@@ -1652,7 +1692,7 @@ renderPartial(
16521692
Handlebars::Tag const &tag,
16531693
OutputRef &out,
16541694
dom::Value const &context,
1655-
HandlebarsOptions &opt,
1695+
HandlebarsOptions const& opt,
16561696
detail::RenderState& state) const {
16571697
// Evaluate dynamic partial
16581698
std::string helper(tag.helper);
@@ -1689,8 +1729,7 @@ renderPartial(
16891729
if (tag.type2 == '#') {
16901730
partial_content = fnBlock;
16911731
} else {
1692-
out << "[undefined partial in \"" << tag.buffer << "\"]";
1693-
return;
1732+
throw HandlebarsError(fmt::format("The partial {} could not be found", helper));
16941733
}
16951734
} else {
16961735
partial_content = it->second;
@@ -1755,6 +1794,23 @@ renderPartial(
17551794
if (tag.removeRWhitespace) {
17561795
state.templateText = trim_lspaces(state.templateText);
17571796
}
1797+
else if (!opt.ignoreStandalone)
1798+
{
1799+
auto beforePartial = state.templateText0.substr(
1800+
0, tag.buffer.data() - state.templateText0.data());
1801+
std::string_view lastLine = beforePartial;
1802+
auto pos = beforePartial.find_last_of("\r\n");
1803+
if (pos != std::string_view::npos)
1804+
{
1805+
lastLine = beforePartial.substr(pos + 1);
1806+
}
1807+
bool const isStandalone = std::ranges::all_of(
1808+
lastLine, [](char c) { return c == ' '; });
1809+
if (isStandalone)
1810+
{
1811+
state.templateText = trim_ldelimiters(state.templateText, " ");
1812+
}
1813+
}
17581814
}
17591815

17601816
void
@@ -2120,6 +2176,8 @@ isLt(dom::Value const& a, dom::Value const& b) {
21202176
return isLt(a.getArray(), b.getArray());
21212177
case dom::Kind::Object:
21222178
return isLt(a.getObject(), b.getObject());
2179+
case dom::Kind::Function:
2180+
return true;
21232181
}
21242182
return false;
21252183
}

0 commit comments

Comments
 (0)