From 50685f837fd276f96dc3f1a308db3076dcb264ba Mon Sep 17 00:00:00 2001 From: ochafik Date: Thu, 26 Sep 2024 19:03:59 +0100 Subject: [PATCH] `minja`: add str.title() --- common/minja.hpp | 33 ++++++++++++++++++++++++--------- tests/test-minja.cpp | 1 + 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/common/minja.hpp b/common/minja.hpp index 646b054b7..91a9f669e 100644 --- a/common/minja.hpp +++ b/common/minja.hpp @@ -100,7 +100,7 @@ private: } out << string_quote; } - void dump(std::ostringstream & out, int indent = -1, int level = 0, char string_quote = '\'') const { + void dump(std::ostringstream & out, int indent = -1, int level = 0, bool to_json = false) const { auto print_indent = [&](int level) { if (indent > 0) { out << "\n"; @@ -113,13 +113,15 @@ private: else print_indent(level + 1); }; + auto string_quote = to_json ? '"' : '\''; + if (is_null()) out << "null"; else if (array_) { out << "["; print_indent(level + 1); for (size_t i = 0; i < array_->size(); ++i) { if (i) print_sub_sep(); - (*array_)[i].dump(out, indent, level + 1, string_quote); + (*array_)[i].dump(out, indent, level + 1, to_json); } print_indent(level); out << "]"; @@ -134,15 +136,15 @@ private: out << string_quote << it->first.dump() << string_quote; } out << ": "; - it->second.dump(out, indent, level + 1, string_quote); + it->second.dump(out, indent, level + 1, to_json); } print_indent(level); out << "}"; } else if (callable_) { throw std::runtime_error("Cannot dump callable to JSON"); - } else if (is_boolean()) { + } else if (is_boolean() && !to_json) { out << (this->to_bool() ? "True" : "False"); - } else if (is_string()) { + } else if (is_string() && !to_json) { dump_string(primitive_, out, string_quote); } else { out << primitive_.dump(); @@ -378,7 +380,7 @@ public: std::string dump(int indent=-1, bool to_json=false) const { std::ostringstream out; - dump(out, indent, 0, to_json ? '"' : '\''); + dump(out, indent, 0, to_json); return out.str(); } @@ -1231,14 +1233,22 @@ public: return callable.call(context, vargs); } } else if (obj.is_string()) { + auto str = obj.get(); if (method->get_name() == "strip") { args.expectArgs("strip method", {0, 0}, {0, 0}); - return Value(strip(obj.get())); + return Value(strip(str)); } else if (method->get_name() == "endswith") { args.expectArgs("endswith method", {1, 1}, {0, 0}); - auto str = obj.get(); auto suffix = args.args[0]->evaluate(context).get(); return suffix.length() <= str.length() && std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()); + } else if (method->get_name() == "title") { + args.expectArgs("title method", {0, 0}, {0, 0}); + auto res = str; + for (size_t i = 0, n = res.size(); i < n; ++i) { + if (i == 0 || std::isspace(res[i - 1])) res[i] = std::toupper(res[i]); + else res[i] = std::tolower(res[i]); + } + return res; } } throw std::runtime_error("Unknown method: " + method->get_name()); @@ -2240,7 +2250,12 @@ inline std::shared_ptr Context::builtins() { auto items = Value::array(); if (args.contains("object")) { auto & obj = args.at("object"); - if (!obj.is_null()) { + if (obj.is_string()) { + auto json_obj = json::parse(obj.get()); + for (const auto & kv : json_obj.items()) { + items.push_back(Value::array({kv.key(), kv.value()})); + } + } else if (!obj.is_null()) { for (auto & key : obj.keys()) { items.push_back(Value::array({key, obj.at(key)})); } diff --git a/tests/test-minja.cpp b/tests/test-minja.cpp index 8b702cbb0..6018845f2 100644 --- a/tests/test-minja.cpp +++ b/tests/test-minja.cpp @@ -149,6 +149,7 @@ static void test_error_contains(const std::string & template_str, const json & b } static void test_template_features() { + test_render(R"({{ 'foo bar'.title() }})", {}, {}, "Foo Bar"); test_render(R"({{ 1 | safe }})", {}, {}, "1"); test_render(R"({{ 'abc'.endswith('bc') }},{{ ''.endswith('a') }})", {}, {}, "True,False"); test_render(R"({{ none | selectattr("foo", "equalto", "bar") | list }})", {}, {}, "[]");