From 615e648260a87eb2edabf4aefb456186800e67a1 Mon Sep 17 00:00:00 2001 From: ipknHama Date: Sat, 2 Aug 2014 10:46:00 +0900 Subject: [PATCH] mustache partial implementation --- mustache.h | 83 ++++++++++++++++++++++++++++++++--- template_test/Makefile | 2 +- template_test/mustachetest.cc | 13 ++++-- template_test/test.py | 10 +++-- unittest.cpp | 12 +++++ 5 files changed, 107 insertions(+), 13 deletions(-) diff --git a/mustache.h b/mustache.h index 3fa002f35..7f878e3fa 100644 --- a/mustache.h +++ b/mustache.h @@ -1,6 +1,9 @@ #pragma once #include #include +#include +#include +#include #include "json.h" namespace crow { @@ -8,6 +11,8 @@ namespace crow { using context = json::wvalue; + template_t load(const std::string& filename); + class invalid_template_exception : public std::exception { public: @@ -138,19 +143,31 @@ namespace crow } } - void render_internal(int actionBegin, int actionEnd, std::vector& stack, std::string& out) + void render_internal(int actionBegin, int actionEnd, std::vector& stack, std::string& out, int indent) { int current = actionBegin; + + if (indent) + out.insert(out.size(), indent, ' '); + while(current < actionEnd) { auto& fragment = fragments_[current]; auto& action = actions_[current]; - out.insert(out.size(), body_, fragment.first, fragment.second-fragment.first); + render_fragment(fragment, indent, out); switch(action.t) { case ActionType::Ignore: // do nothing break; + case ActionType::Partial: + { + std::string partial_name = tag_name(action); + auto partial_templ = load(partial_name); + int partial_indent = action.pos; + partial_templ.render_internal(0, partial_templ.fragments_.size()-1, stack, out, partial_indent?indent+partial_indent:0); + } + break; case ActionType::UnescapeTag: case ActionType::Tag: { @@ -218,7 +235,7 @@ namespace crow for(auto it = ctx.l->begin(); it != ctx.l->end(); ++it) { stack.push_back(&*it); - render_internal(current+1, action.pos, stack, out); + render_internal(current+1, action.pos, stack, out, indent); stack.pop_back(); } current = action.pos; @@ -248,7 +265,21 @@ namespace crow current++; } auto& fragment = fragments_[actionEnd]; - out.insert(out.size(), body_, fragment.first, fragment.second - fragment.first); + render_fragment(fragment, indent, out); + } + void render_fragment(const std::pair fragment, int indent, std::string& out) + { + if (indent) + { + for(int i = fragment.first; i < fragment.second; i ++) + { + out += body_[i]; + if (body_[i] == '\n' && i+1 != (int)body_.size()) + out.insert(out.size(), indent, ' '); + } + } + else + out.insert(out.size(), body_, fragment.first, fragment.second-fragment.first); } public: std::string render(context& ctx) @@ -257,7 +288,7 @@ namespace crow stack.emplace_back(&ctx); std::string ret; - render_internal(0, fragments_.size()-1, stack, ret); + render_internal(0, fragments_.size()-1, stack, ret, 0); return ret; } @@ -337,7 +368,6 @@ namespace crow while(body_[idx] == ' ') idx++; while(body_[endIdx-1] == ' ') endIdx--; actions_.emplace_back(ActionType::Partial, idx, endIdx); - throw invalid_template_exception("{{>: partial not implemented: " + body_.substr(idx+1, endIdx-idx-1)); break; case '{': if (tag_open != "{{" || tag_close != "}}") @@ -444,6 +474,10 @@ namespace crow k + 1 < (int)body_.size() && body_[k+1] == '\n'))) continue; + if (actions_[i].t == ActionType::Partial) + { + actions_[i].pos = fragment_before.second - j - 1; + } fragment_before.second = j+1; if (!all_space_after) { @@ -465,5 +499,42 @@ namespace crow { return template_t(body); } + namespace detail + { + std::string template_base_directory = "templates"; + } + + std::string default_loader(const std::string& filename) + { + std::ifstream inf(detail::template_base_directory + filename); + if (!inf) + return {}; + return {std::istreambuf_iterator(inf), std::istreambuf_iterator()}; + } + + namespace detail + { + std::function loader = default_loader; + } + + void set_base(const std::string& path) + { + detail::template_base_directory = path; + if (detail::template_base_directory.back() != '\\' && + detail::template_base_directory.back() != '/') + { + detail::template_base_directory += '/'; + } + } + + void set_loader(std::function loader) + { + detail::loader = std::move(loader); + } + + template_t load(const std::string& filename) + { + return compile(detail::loader(filename)); + } } } diff --git a/template_test/Makefile b/template_test/Makefile index f03e8fb2b..ad0d49b67 100644 --- a/template_test/Makefile +++ b/template_test/Makefile @@ -1,5 +1,5 @@ all: - $(CXX) -std=c++11 -g -o mustachetest mustachetest.cc + $(CXX) -Wall -std=c++11 -g -o mustachetest mustachetest.cc .PHONY: clean clean: rm -f mustachetest *.o diff --git a/template_test/mustachetest.cc b/template_test/mustachetest.cc index 3b80b5d74..c4ac2c97f 100644 --- a/template_test/mustachetest.cc +++ b/template_test/mustachetest.cc @@ -11,15 +11,22 @@ using namespace crow::mustache; string read_all(const string& filename) { ifstream is(filename); - string ret; - copy(istreambuf_iterator(is), istreambuf_iterator(), back_inserter(ret)); - return ret; + return {istreambuf_iterator(is), istreambuf_iterator()}; } int main() { auto data = json::load(read_all("data")); auto templ = compile(read_all("template")); + auto partials = json::load(read_all("partials")); + set_loader([&](std::string name)->std::string + { + if (partials.count(name)) + { + return partials[name].s(); + } + return ""; + }); context ctx(data); cout << templ.render(ctx); return 0; diff --git a/template_test/test.py b/template_test/test.py index f99ca2381..22fcca0cc 100755 --- a/template_test/test.py +++ b/template_test/test.py @@ -8,14 +8,17 @@ for testfile in glob.glob("*.json"): for test in testdoc["tests"]: if "lambda" in test["data"]: continue - if "partials" in test: - #print testfile, test["name"] - continue open('data', 'w').write(json.dumps(test["data"])) open('template', 'w').write(test["template"]) + if "partials" in test: + open('partials', 'w').write(json.dumps(test["partials"])) + else: + open('partials', 'w').write("{}") ret = subprocess.check_output("./mustachetest") print testfile, test["name"] if ret != test["expected"]: + if 'partials' in test: + print 'partials:', json.dumps(test["partials"]) print json.dumps(test["data"]) print test["template"] print 'Expected:',repr(test["expected"]) @@ -23,3 +26,4 @@ for testfile in glob.glob("*.json"): assert ret == test["expected"] os.unlink('data') os.unlink('template') + os.unlink('partials') diff --git a/unittest.cpp b/unittest.cpp index 07bf2bf07..eec119e63 100644 --- a/unittest.cpp +++ b/unittest.cpp @@ -414,6 +414,18 @@ TEST(template_basic) //crow::mustache::load("basic.mustache"); } +TEST(template_load) +{ + crow::mustache::set_base("."); + ofstream("test.mustache") << R"---(attack of {{name}})---"; + auto t = crow::mustache::load("test.mustache"); + crow::mustache::context ctx; + ctx["name"] = "killer tomatoes"; + auto result = t.render(ctx); + ASSERT_EQUAL("attack of killer tomatoes", result); + unlink("test.mustache"); +} + int testmain() { bool failed = false;