mustache partial implementation

This commit is contained in:
ipknHama 2014-08-02 10:46:00 +09:00
parent 9d1d65b08c
commit 615e648260
5 changed files with 107 additions and 13 deletions

View File

@ -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));
}
}
}

View File

@ -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

View File

@ -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;

View File

@ -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')

View File

@ -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;