aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mustache.h83
-rw-r--r--template_test/Makefile2
-rw-r--r--template_test/mustachetest.cc13
-rwxr-xr-xtemplate_test/test.py10
-rw-r--r--unittest.cpp12
5 files changed, 107 insertions, 13 deletions
diff --git a/mustache.h b/mustache.h
index 3fa002f..7f878e3 100644
--- a/mustache.h
+++ b/mustache.h
@@ -1,6 +1,9 @@
#pragma once
#include <string>
#include <vector>
+#include <fstream>
+#include <iterator>
+#include <functional>
#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<context*>& stack, std::string& out)
+ void render_internal(int actionBegin, int actionEnd, std::vector<context*>& 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<int, int> 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<char>(inf), std::istreambuf_iterator<char>()};
+ }
+
+ namespace detail
+ {
+ std::function<std::string (std::string)> 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<std::string(std::string)> 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 f03e8fb..ad0d49b 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 3b80b5d..c4ac2c9 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<char>(is), istreambuf_iterator<char>(), back_inserter(ret));
- return ret;
+ return {istreambuf_iterator<char>(is), istreambuf_iterator<char>()};
}
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 f99ca23..22fcca0 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 07bf2bf..eec119e 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;