Full C++23 framework tree: runtime, observability, security, data, app, realtime, supervisor, clients, CLI, example plus enterprise tenancy, audit, operability, governance, supplychain. Postgres via docker compose for tests.
247 lines
8.4 KiB
C++
247 lines
8.4 KiB
C++
#include "Templates.hpp"
|
|
|
|
#include <stdexcept>
|
|
|
|
#include "Caps.hpp"
|
|
|
|
namespace molo::example::fw {
|
|
|
|
std::string html_escape(std::string_view in) {
|
|
std::string out;
|
|
out.reserve(in.size());
|
|
for (char c : in) {
|
|
switch (c) {
|
|
case '&': out += "&"; break;
|
|
case '<': out += "<"; break;
|
|
case '>': out += ">"; break;
|
|
case '"': out += """; break;
|
|
default: out.push_back(c); break;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
namespace {
|
|
|
|
std::string trim(std::string s) {
|
|
while (!s.empty() && (s.front() == ' ' || s.front() == '\t' ||
|
|
s.front() == '\n' || s.front() == '\r'))
|
|
s.erase(s.begin());
|
|
while (!s.empty() && (s.back() == ' ' || s.back() == '\t' ||
|
|
s.back() == '\n' || s.back() == '\r'))
|
|
s.pop_back();
|
|
return s;
|
|
}
|
|
|
|
std::string apply_filter(std::string value, const std::string& filter) {
|
|
if (filter == "upper") {
|
|
for (char& c : value) c = (char)std::toupper((unsigned char)c);
|
|
} else if (filter == "lower") {
|
|
for (char& c : value) c = (char)std::tolower((unsigned char)c);
|
|
} else if (filter == "escape") {
|
|
value = html_escape(value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
std::string ctx_string(const TemplateCtx& ctx, const std::string& key) {
|
|
auto it = ctx.find(key);
|
|
if (it == ctx.end()) return "";
|
|
if (auto* s = std::get_if<std::string>(&it->second)) return *s;
|
|
return "";
|
|
}
|
|
|
|
bool ctx_truthy(const TemplateCtx& ctx, const std::string& key) {
|
|
auto it = ctx.find(key);
|
|
if (it == ctx.end()) return false;
|
|
if (auto* s = std::get_if<std::string>(&it->second))
|
|
return !s->empty() && *s != "false" && *s != "0";
|
|
if (auto* l = std::get_if<TemplateList>(&it->second)) return !l->empty();
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void Environment::add_template(std::string name, std::string source) {
|
|
templates_[std::move(name)] = std::move(source);
|
|
}
|
|
|
|
void Environment::add_global(std::string key, std::string value) {
|
|
globals_[std::move(key)] = std::move(value);
|
|
}
|
|
|
|
bool Environment::has_template(const std::string& name) const {
|
|
return templates_.count(name) > 0;
|
|
}
|
|
|
|
std::string Environment::render(const std::string& name, TemplateCtx ctx) const {
|
|
auto it = templates_.find(name);
|
|
if (it == templates_.end()) throw std::runtime_error("template not found");
|
|
for (const auto& [k, v] : globals_) {
|
|
if (!ctx.count(k)) ctx[k] = v;
|
|
}
|
|
return render_source(name, it->second, ctx, 0);
|
|
}
|
|
|
|
std::string Environment::render_source(const std::string& name,
|
|
const std::string& src, TemplateCtx& ctx,
|
|
int depth) const {
|
|
(void)name;
|
|
if (depth > caps::kMaxTemplateDepth)
|
|
throw std::runtime_error("template depth exceeded");
|
|
std::string extends;
|
|
std::size_t pos = src.find("{% extends");
|
|
if (pos != std::string::npos) {
|
|
std::size_t q1 = src.find('"', pos);
|
|
std::size_t q2 = q1 == std::string::npos
|
|
? std::string::npos
|
|
: src.find('"', q1 + 1);
|
|
if (q1 != std::string::npos && q2 != std::string::npos)
|
|
extends = src.substr(q1 + 1, q2 - q1 - 1);
|
|
}
|
|
if (!extends.empty()) {
|
|
auto it = templates_.find(extends);
|
|
if (it == templates_.end()) throw std::runtime_error("base not found");
|
|
std::map<std::string, std::string> blocks;
|
|
std::size_t b = 0;
|
|
while (true) {
|
|
std::size_t bs = src.find("{% block", b);
|
|
if (bs == std::string::npos) break;
|
|
std::size_t be = src.find("%}", bs);
|
|
std::size_t cs = src.find("{% endblock %}", be);
|
|
if (be == std::string::npos || cs == std::string::npos) break;
|
|
std::string head = trim(src.substr(bs + 9, be - bs - 9));
|
|
blocks[head] = src.substr(be + 2, cs - be - 2);
|
|
b = cs + 14;
|
|
}
|
|
std::string base = it->second;
|
|
for (const auto& [k, v] : blocks) {
|
|
std::string open = "{% block " + k + " %}";
|
|
std::size_t s = base.find(open);
|
|
std::size_t e = s == std::string::npos
|
|
? std::string::npos
|
|
: base.find("{% endblock %}", s);
|
|
if (s != std::string::npos && e != std::string::npos)
|
|
base = base.substr(0, s) + v + base.substr(e + 14);
|
|
}
|
|
return render_blocks(base, ctx, depth);
|
|
}
|
|
return render_blocks(src, ctx, depth);
|
|
}
|
|
|
|
std::string Environment::render_blocks(const std::string& src, TemplateCtx& ctx,
|
|
int depth) const {
|
|
std::string out;
|
|
std::size_t i = 0;
|
|
while (i < src.size()) {
|
|
std::size_t v = src.find("{{", i);
|
|
std::size_t t = src.find("{%", i);
|
|
if (v == std::string::npos && t == std::string::npos) {
|
|
out += src.substr(i);
|
|
break;
|
|
}
|
|
if (v != std::string::npos && (t == std::string::npos || v < t)) {
|
|
out += src.substr(i, v - i);
|
|
std::size_t e = src.find("}}", v);
|
|
if (e == std::string::npos) break;
|
|
std::string expr = trim(src.substr(v + 2, e - v - 2));
|
|
bool safe = false;
|
|
std::string var = expr;
|
|
std::vector<std::string> filters;
|
|
std::size_t p = expr.find('|');
|
|
if (p != std::string::npos) {
|
|
var = trim(expr.substr(0, p));
|
|
std::string rest = expr.substr(p + 1);
|
|
std::size_t s = 0;
|
|
while (s < rest.size()) {
|
|
std::size_t q = rest.find('|', s);
|
|
std::string f = trim(rest.substr(s, q == std::string::npos
|
|
? q
|
|
: q - s));
|
|
if (f == "safe") {
|
|
safe = true;
|
|
} else if (!f.empty()) {
|
|
filters.push_back(f);
|
|
}
|
|
if (q == std::string::npos) break;
|
|
s = q + 1;
|
|
}
|
|
}
|
|
std::string value;
|
|
std::size_t dot = var.find('.');
|
|
if (dot != std::string::npos) {
|
|
value = ctx_string(ctx, var);
|
|
} else {
|
|
value = ctx_string(ctx, var);
|
|
}
|
|
for (const auto& f : filters) value = apply_filter(value, f);
|
|
if (!safe) value = html_escape(value);
|
|
out += value;
|
|
i = e + 2;
|
|
} else {
|
|
out += src.substr(i, t - i);
|
|
std::size_t e = src.find("%}", t);
|
|
if (e == std::string::npos) break;
|
|
std::string tag = trim(src.substr(t + 2, e - t - 2));
|
|
if (tag.rfind("if ", 0) == 0) {
|
|
std::string cond = trim(tag.substr(3));
|
|
std::size_t end = src.find("{% endif %}", e);
|
|
if (end == std::string::npos) break;
|
|
std::string inner = src.substr(e + 2, end - e - 2);
|
|
std::size_t els = inner.find("{% else %}");
|
|
std::string yes = els == std::string::npos ? inner
|
|
: inner.substr(0, els);
|
|
std::string no =
|
|
els == std::string::npos ? "" : inner.substr(els + 10);
|
|
out += render_blocks(ctx_truthy(ctx, cond) ? yes : no, ctx, depth + 1);
|
|
i = end + 11;
|
|
} else if (tag.rfind("for ", 0) == 0) {
|
|
std::string rest = trim(tag.substr(4));
|
|
std::size_t in = rest.find(" in ");
|
|
std::string item = in == std::string::npos
|
|
? ""
|
|
: trim(rest.substr(0, in));
|
|
std::string list = in == std::string::npos
|
|
? ""
|
|
: trim(rest.substr(in + 4));
|
|
std::size_t end = src.find("{% endfor %}", e);
|
|
if (end == std::string::npos) break;
|
|
std::string inner = src.substr(e + 2, end - e - 2);
|
|
auto it = ctx.find(list);
|
|
if (it != ctx.end()) {
|
|
if (auto* l = std::get_if<TemplateList>(&it->second)) {
|
|
for (const auto& row : *l) {
|
|
TemplateCtx sub = ctx;
|
|
for (const auto& [k, val] : row) {
|
|
sub[item + "." + k] = val;
|
|
sub[k] = val;
|
|
}
|
|
out += render_blocks(inner, sub, depth + 1);
|
|
}
|
|
}
|
|
}
|
|
i = end + 12;
|
|
} else if (tag.rfind("include ", 0) == 0) {
|
|
std::string rest = trim(tag.substr(8));
|
|
if (rest.size() >= 2 && rest.front() == '"') {
|
|
std::string inc = rest.substr(1, rest.size() - 2);
|
|
auto it = templates_.find(inc);
|
|
if (it != templates_.end())
|
|
out += render_blocks(it->second, ctx, depth + 1);
|
|
}
|
|
i = e + 2;
|
|
} else if (tag.rfind("block ", 0) == 0) {
|
|
std::size_t end = src.find("{% endblock %}", e);
|
|
if (end == std::string::npos) break;
|
|
out += render_blocks(src.substr(e + 2, end - e - 2), ctx, depth + 1);
|
|
i = end + 14;
|
|
} else {
|
|
i = e + 2;
|
|
}
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
} // namespace molo::example::fw
|