Skip to content

Commit

Permalink
feat: strict specs
Browse files Browse the repository at this point in the history
  • Loading branch information
alandefreitas committed Sep 5, 2023
1 parent 53273a0 commit deaa47c
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 64 deletions.
12 changes: 12 additions & 0 deletions include/mrdox/Support/Handlebars.hpp
Expand Up @@ -38,9 +38,20 @@ struct HandlebarsOptions
bool noEscape = false;

/** Templates will throw rather than ignore missing fields
Run in strict mode. In this mode, templates will throw rather than
silently ignore missing fields.
*/
bool strict = false;

/** Removes object existence checks when traversing paths
This is a subset of strict mode that generates optimized
templates when the data inputs are known to be safe.
*/
bool assumeObjects = false;

/** Disable the auto-indent feature
By default, an indented partial-call causes the output of the
Expand Down Expand Up @@ -333,6 +344,7 @@ class MRDOX_DECL HandlebarsCallback
std::vector<std::string_view> blockParamIds_;
std::function<void(dom::Value, dom::Array const&)> const* logger_;
detail::RenderState* renderState_;
HandlebarsOptions const* opt_;
friend class Handlebars;

friend
Expand Down
23 changes: 8 additions & 15 deletions src/lib/Dom/Array.cpp
Expand Up @@ -54,24 +54,17 @@ toString(
Array const& arr)
{
if(arr.empty())
return "[]";
std::string s = "[";
{
auto const n = arr.size();
auto insert = std::back_inserter(s);
for(std::size_t i = 0; i < n; ++i)
return "";
std::string s;
auto const n = arr.size();
if (n != 0) {
s += toString(arr.at(0));
for(std::size_t i = 1; i < n; ++i)
{
if(i != 0)
fmt::format_to(insert,
", {}",
toString(arr.at(i)));
else
fmt::format_to(insert,
" {}",
toStringChild(arr.at(i)));
s += ',';
s += toString(arr.at(i));
}
}
s += " ]";
return s;
}

Expand Down
23 changes: 3 additions & 20 deletions src/lib/Dom/Object.cpp
Expand Up @@ -89,26 +89,9 @@ exists(std::string_view key) const
}

std::string
toString(
Object const& obj)
{
if(obj.empty())
return "{}";
std::string s = "{";
{
auto insert = std::back_inserter(s);
for(auto const& kv : RangeFor(obj))
{
if(! kv.first)
s.push_back(',');
fmt::format_to(insert,
" {} : {}",
kv.value.key,
toStringChild(kv.value.value));
}
}
s += " }";
return s;
toString(Object const&)
{
return "[object Object]";
}

//------------------------------------------------
Expand Down
6 changes: 1 addition & 5 deletions src/lib/Dom/Value.cpp
Expand Up @@ -471,11 +471,7 @@ toStringChild(
return "[]";
}
case Value::Kind::Object:
{
if(! value.obj_.empty())
return "{...}";
return "{}";
}
return "[object Object]";
case Value::Kind::Function:
return "[function]";
default:
Expand Down
124 changes: 103 additions & 21 deletions src/lib/Support/Handlebars.cpp
Expand Up @@ -606,7 +606,7 @@ find_position_in_text(
if (res.line == 1)
res.column = res.pos;
else
res.column = res.pos - text.rfind('\n', res.pos);
res.column = res.pos - text.rfind('\n', res.pos) - 1;
}
return res;
}
Expand Down Expand Up @@ -648,7 +648,8 @@ std::pair<dom::Value, bool>
lookupPropertyImpl(
dom::Object const& context,
std::string_view path,
detail::RenderState const& state)
detail::RenderState const& state,
HandlebarsOptions const& opt)
{
// Get first value from Object
std::string_view segment = popFirstSegment(path);
Expand All @@ -661,7 +662,21 @@ lookupPropertyImpl(
}
else if (!context.exists(literalSegment))
{
return {nullptr, false};
if (opt.strict || (opt.assumeObjects && !path.empty()))
{
std::string msg = fmt::format(
"\"{}\" not defined in {}", literalSegment, toString(context));
auto res = find_position_in_text(state.templateText0, literalSegment);
if (res)
{
throw HandlebarsError(msg, res.line, res.column, res.pos);
}
throw HandlebarsError(msg);
}
else
{
return {nullptr, false};
}
}
else
{
Expand All @@ -679,9 +694,27 @@ lookupPropertyImpl(
{
auto obj = cur.getObject();
if (obj.exists(literalSegment))
{
cur = obj.find(literalSegment);
}
else
return {nullptr, false};
{
if (opt.strict)
{
std::string msg = fmt::format(
"\"{}\" not defined in {}", literalSegment, toString(cur));
auto res = find_position_in_text(state.templateText0, literalSegment);
if (res)
{
throw HandlebarsError(msg, res.line, res.column, res.pos);
}
throw HandlebarsError(msg);
}
else
{
return {nullptr, false};
}
}
}
// If current value is an Array, get the next value the stripped index
else if (cur.isArray())
Expand Down Expand Up @@ -717,7 +750,9 @@ std::pair<dom::Value, bool>
lookupPropertyImpl(
dom::Value const& context,
std::string_view path,
detail::RenderState const& state) {
detail::RenderState const& state,
HandlebarsOptions const& opt)
{
checkPath(path, state);
// ==============================================================
// "." / "this"
Expand All @@ -728,40 +763,52 @@ lookupPropertyImpl(
// Non-object key
// ==============================================================
if (context.kind() != dom::Kind::Object) {
if (opt.strict || opt.assumeObjects)
{
std::string msg = fmt::format("\"{}\" not defined in {}", path, context);
auto res = find_position_in_text(state.templateText0, path);
if (res)
{
throw HandlebarsError(msg, res.line, res.column, res.pos);
}
throw HandlebarsError(msg);
}
return {nullptr, false};
}
// ==============================================================
// Object path
// ==============================================================
return lookupPropertyImpl(context.getObject(), path, state);
return lookupPropertyImpl(context.getObject(), path, state, opt);
}

template <std::convertible_to<std::string_view> S>
std::pair<dom::Value, bool>
lookupPropertyImpl(
dom::Value const& data,
S const& path,
detail::RenderState const& state)
detail::RenderState const& state,
HandlebarsOptions const& opt)
{
return lookupPropertyImpl(data, std::string_view(path), state);
return lookupPropertyImpl(data, std::string_view(path), state, opt);
}

std::pair<dom::Value, bool>
lookupPropertyImpl(
dom::Value const& context,
dom::Value const& path,
detail::RenderState const& state)
detail::RenderState const& state,
HandlebarsOptions const& opt)
{
if (path.isString())
return lookupPropertyImpl(context, path.getString(), state);
return lookupPropertyImpl(context, path.getString(), state, opt);
if (path.isInteger()) {
if (context.isArray()) {
auto& arr = context.getArray();
if (path.getInteger() >= static_cast<std::int64_t>(arr.size()))
return {nullptr, false};
return {arr.at(path.getInteger()), true};
}
return lookupPropertyImpl(context, std::to_string(path.getInteger()), state);
return lookupPropertyImpl(context, std::to_string(path.getInteger()), state, opt);
}
return {nullptr, false};
}
Expand All @@ -772,7 +819,7 @@ lookupProperty(
dom::Value const& context,
dom::Value const& path) const
{
return lookupPropertyImpl(context, path, *renderState_);
return lookupPropertyImpl(context, path, *renderState_, *opt_);
}


Expand Down Expand Up @@ -1679,12 +1726,15 @@ evalExpr(
break;
}
}
auto [res, found] = lookupPropertyImpl(data, expression, state);
auto [res, found] = lookupPropertyImpl(data, expression, state, opt);
return {res, found, false};
}
// ==============================================================
// Dotdot context path
// ==============================================================
HandlebarsOptions noStrict = opt;
noStrict.strict = false;
noStrict.assumeObjects = false;
if (expression.starts_with("..")) {
// Get value from parent helper contexts
std::size_t dotdots = 1;
Expand All @@ -1704,7 +1754,7 @@ evalExpr(
}
dom::Value parentCtx =
state.parentContext[state.parentContext.size() - dotdots];
auto [res, found] = lookupPropertyImpl(parentCtx, expression, state);
auto [res, found] = lookupPropertyImpl(parentCtx, expression, state, noStrict);
return {res, found, false};
}
// ==============================================================
Expand All @@ -1730,7 +1780,7 @@ evalExpr(
bool defined;
if (isPathedValue)
{
std::tie(r, defined) = lookupPropertyImpl(context, expression, state);
std::tie(r, defined) = lookupPropertyImpl(context, expression, state, noStrict);
if (defined) {
return {r, defined, false};
}
Expand All @@ -1739,7 +1789,7 @@ evalExpr(
// ==============================================================
// Block values
// ==============================================================
std::tie(r, defined) = lookupPropertyImpl(state.blockValues, expression, state);
std::tie(r, defined) = lookupPropertyImpl(state.blockValues, expression, state, noStrict);
if (defined)
{
return {r, defined, false, false, true};
Expand All @@ -1759,7 +1809,10 @@ evalExpr(
// ==============================================================
// Context values
// ==============================================================
std::tie(r, defined) = lookupPropertyImpl(context, expression, state);
HandlebarsOptions strictOpt = opt;
strictOpt.strict = opt.strict && !opt.compat;
strictOpt.assumeObjects = opt.assumeObjects && !opt.compat;
std::tie(r, defined) = lookupPropertyImpl(context, expression, state, strictOpt);
if (defined) {
return {r, defined, false};
}
Expand All @@ -1772,13 +1825,19 @@ evalExpr(
auto parentContexts = std::ranges::views::reverse(state.parentContext);
for (auto parentContext: parentContexts)
{
std::tie(r, defined) = lookupPropertyImpl(parentContext, expression, state);
std::tie(r, defined) = lookupPropertyImpl(parentContext, expression, state, noStrict);
if (defined)
{
return {r, defined, false};
}
}
}

if (opt.strict)
{
std::string msg = fmt::format("\"{}\" not defined", expression);
throw HandlebarsError(msg);
}
return {nullptr, false, false};
}

Expand Down Expand Up @@ -2093,7 +2152,9 @@ renderExpression(
cb.context_ = &context;
cb.data_ = &state.data;
cb.logger_ = &logger_;
setupArgs(tag.arguments, context, state, args, cb, opt);
HandlebarsOptions noStrict = opt;
noStrict.strict = false;
setupArgs(tag.arguments, context, state, args, cb, noStrict);
auto [res, render] = fn(args, cb);
if (render == HelperBehavior::RENDER_RESULT) {
format_to(out, res, opt2);
Expand Down Expand Up @@ -2125,7 +2186,9 @@ renderExpression(
dom::Array args = dom::newArray<dom::DefaultArrayImpl>();
HandlebarsCallback cb;
cb.name_ = helper_expr;
setupArgs(tag.arguments, context, state, args, cb, opt);
HandlebarsOptions noStrict = opt;
noStrict.strict = false;
setupArgs(tag.arguments, context, state, args, cb, noStrict);
auto v2 = resV.value.getFunction().call(args).value();
format_to(out, v2, opt2);
}
Expand All @@ -2135,6 +2198,11 @@ renderExpression(
}
return;
}
else if (opt.strict)
{
std::string msg = fmt::format("\"{}\" not defined in {}", helper_expr, toString(context));
throw HandlebarsError(msg);
}

// ==============================================================
// helperMissing hook
Expand Down Expand Up @@ -2252,6 +2320,7 @@ setupArgs(
}
}
cb.renderState_ = &state;
cb.opt_ = &opt;
}

void
Expand Down Expand Up @@ -2604,7 +2673,20 @@ renderBlock(
arguments = tag.helper;
}
}
setupArgs(arguments, context, state, args, cb, opt);
else if (opt.strict && !found)
{
std::string msg = fmt::format(
"\"{}\" not defined in {}", tag.helper, toString(context));
auto res = find_position_in_text(state.templateText0, tag.helper);
if (res)
{
throw HandlebarsError(msg, res.line, res.column, res.pos);
}
throw HandlebarsError(msg);
}
HandlebarsOptions noStrict = opt;
noStrict.strict = opt.strict && emulateMustache;
setupArgs(arguments, context, state, args, cb, noStrict);

// ==============================================================
// Setup block parameters
Expand Down

0 comments on commit deaa47c

Please sign in to comment.