Skip to content

Commit d15a74f

Browse files
authored
'extends' statement - support for 'super()' and 'self.blockName()' invocation (#28)
* Implement basic filesystem handlers * Fix build * Fix build (2) * Fix travis build * Base implementation of 'extends' functionality * Fix build * Fix build * Partial implementation of 'super'/'self.blockName()' * Fix 'SuperAndSelfBlocksExtends' test
1 parent 83cba28 commit d15a74f

File tree

9 files changed

+156
-11
lines changed

9 files changed

+156
-11
lines changed

src/expression_evaluator.cpp

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "expression_evaluator.h"
22
#include "filters.h"
33
#include "internal_value.h"
4+
#include "out_stream.h"
45
#include "testers.h"
56
#include "value_visitors.h"
67

@@ -12,6 +13,13 @@
1213
namespace jinja2
1314
{
1415

16+
void ExpressionEvaluatorBase::Render(OutStream& stream, RenderContext& values)
17+
{
18+
auto val = Evaluate(values);
19+
stream.WriteValue(val);
20+
}
21+
22+
1523
InternalValue FullExpressionEvaluator::Evaluate(RenderContext& values)
1624
{
1725
if (!m_expression)
@@ -27,6 +35,14 @@ InternalValue FullExpressionEvaluator::Evaluate(RenderContext& values)
2735
return origVal;
2836
}
2937

38+
void FullExpressionEvaluator::Render(OutStream& stream, RenderContext& values)
39+
{
40+
if (!m_filter && !m_tester)
41+
m_expression->Render(stream, values);
42+
else
43+
Expression::Render(stream, values);
44+
}
45+
3046
InternalValue ValueRefExpression::Evaluate(RenderContext& values)
3147
{
3248
bool found = false;
@@ -220,6 +236,31 @@ InternalValue CallExpression::Evaluate(RenderContext& values)
220236
return InternalValue();
221237
}
222238

239+
void CallExpression::Render(OutStream& stream, RenderContext& values)
240+
{
241+
auto fnVal = m_valueRef->Evaluate(values);
242+
Callable* callable = boost::get<Callable>(&fnVal);
243+
if (callable == nullptr)
244+
{
245+
fnVal = Subscript(fnVal, std::string("operator()"));
246+
callable = boost::get<Callable>(&fnVal);
247+
if (callable == nullptr)
248+
{
249+
Expression::Render(stream, values);
250+
return;
251+
}
252+
}
253+
254+
if (callable->GetType() == Callable::Type::Expression)
255+
{
256+
stream.WriteValue(callable->GetExpressionCallable()(m_params, values));
257+
}
258+
else
259+
{
260+
callable->GetStatementCallable()(m_params, stream, values);
261+
}
262+
}
263+
223264
InternalValue CallExpression::CallGlobalRange(RenderContext& values)
224265
{
225266
bool isArgsParsed = true;
@@ -450,5 +491,4 @@ ParsedArguments ParseCallParams(const std::initializer_list<ArgumentInfo>& args,
450491
return result;
451492
}
452493
}
453-
454494
}

src/expression_evaluator.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class ExpressionEvaluatorBase
1616
virtual ~ExpressionEvaluatorBase() {}
1717

1818
virtual InternalValue Evaluate(RenderContext& values) = 0;
19+
virtual void Render(OutStream& stream, RenderContext& values);
1920
};
2021

2122
template<typename T = ExpressionEvaluatorBase>
@@ -77,6 +78,7 @@ class FullExpressionEvaluator : public ExpressionEvaluatorBase
7778
m_tester = expr;
7879
}
7980
InternalValue Evaluate(RenderContext& values) override;
81+
void Render(OutStream &stream, RenderContext &values) override;
8082
private:
8183
ExpressionEvaluatorPtr<Expression> m_expression;
8284
ExpressionEvaluatorPtr<ExpressionFilter> m_filter;
@@ -258,7 +260,8 @@ class CallExpression : public Expression
258260
{
259261
}
260262

261-
InternalValue Evaluate(RenderContext &values);
263+
InternalValue Evaluate(RenderContext &values) override;
264+
void Render(OutStream &stream, RenderContext &values) override;
262265

263266
auto& GetValueRef() const {return m_valueRef;}
264267
auto& GetParams() const {return m_params;}

src/internal_value.cpp

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ class ByRef
136136
{}
137137

138138
const T& Get() const {return *m_val;}
139+
T& Get() {return *const_cast<T*>(m_val);}
139140
private:
140141
const T* m_val;
141142
};
@@ -149,6 +150,7 @@ class ByVal
149150
{}
150151

151152
const T& Get() const {return m_val;}
153+
T& Get() {return m_val;}
152154
private:
153155
T m_val;
154156
};
@@ -257,7 +259,7 @@ InternalValueList ListAdapter::ToValueList() const
257259
return result;
258260
}
259261

260-
template<template<typename> class Holder>
262+
template<template<typename> class Holder, bool CanModify>
261263
class InternalValueMapAdapter : public IMapAccessor
262264
{
263265
public:
@@ -298,6 +300,16 @@ class InternalValueMapAdapter : public IMapAccessor
298300

299301
return result;
300302
}
303+
304+
bool SetValue(std::string name, const InternalValue& val) override
305+
{
306+
if (CanModify)
307+
{
308+
m_values.Get()[name] = val;
309+
return true;
310+
}
311+
return false;
312+
}
301313
private:
302314
Holder<InternalValueMap> m_values;
303315
};
@@ -354,6 +366,7 @@ class GenericMapAdapter : public IMapAccessor
354366
return m_values.Get().GetKeys();
355367
}
356368

369+
357370
private:
358371
Holder<GenericMap> m_values;
359372
};
@@ -407,32 +420,32 @@ class ValuesMapAdapter : public IMapAccessor
407420

408421
MapAdapter MapAdapter::CreateAdapter(InternalValueMap&& values)
409422
{
410-
return MapAdapter([accessor = InternalValueMapAdapter<ByVal>(std::move(values))]() {return &accessor;});
423+
return MapAdapter([accessor = InternalValueMapAdapter<ByVal, true>(std::move(values))]() mutable {return &accessor;});
411424
}
412425

413426
MapAdapter MapAdapter::CreateAdapter(const InternalValueMap* values)
414427
{
415-
return MapAdapter([accessor = InternalValueMapAdapter<ByRef>(*values)]() {return &accessor;});
428+
return MapAdapter([accessor = InternalValueMapAdapter<ByRef, false>(*values)]() mutable {return &accessor;});
416429
}
417430

418431
MapAdapter MapAdapter::CreateAdapter(const GenericMap& values)
419432
{
420-
return MapAdapter([accessor = GenericMapAdapter<ByRef>(values)]() {return &accessor;});
433+
return MapAdapter([accessor = GenericMapAdapter<ByRef>(values)]() mutable {return &accessor;});
421434
}
422435

423436
MapAdapter MapAdapter::CreateAdapter(GenericMap&& values)
424437
{
425-
return MapAdapter([accessor = GenericMapAdapter<ByVal>(std::move(values))]() {return &accessor;});
438+
return MapAdapter([accessor = GenericMapAdapter<ByVal>(std::move(values))]() mutable {return &accessor;});
426439
}
427440

428441
MapAdapter MapAdapter::CreateAdapter(const ValuesMap& values)
429442
{
430-
return MapAdapter([accessor = ValuesMapAdapter<ByRef>(values)]() {return &accessor;});
443+
return MapAdapter([accessor = ValuesMapAdapter<ByRef>(values)]() mutable {return &accessor;});
431444
}
432445

433446
MapAdapter MapAdapter::CreateAdapter(ValuesMap&& values)
434447
{
435-
return MapAdapter([accessor = ValuesMapAdapter<ByVal>(std::move(values))]() {return &accessor;});
448+
return MapAdapter([accessor = ValuesMapAdapter<ByVal>(std::move(values))]() mutable {return &accessor;});
436449
}
437450

438451
} // jinja2

src/internal_value.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,10 @@ struct IMapAccessor : public IListAccessor
6767
virtual bool HasValue(const std::string& name) const = 0;
6868
virtual InternalValue GetValueByName(const std::string& name) const = 0;
6969
virtual std::vector<std::string> GetKeys() const = 0;
70+
virtual bool SetValue(std::string name, const InternalValue& val) {return false;}
7071
};
7172

72-
using MapAccessorProvider = std::function<const IMapAccessor*()>;
73+
using MapAccessorProvider = std::function<IMapAccessor*()>;
7374

7475
class ListAdapter
7576
{
@@ -153,6 +154,15 @@ class MapAdapter
153154

154155
return std::vector<std::string>();
155156
}
157+
bool SetValue(std::string name, const InternalValue& val)
158+
{
159+
if (m_accessorProvider && m_accessorProvider())
160+
{
161+
return m_accessorProvider()->SetValue(name, val);
162+
}
163+
164+
return false;
165+
}
156166

157167
private:
158168
MapAccessorProvider m_accessorProvider;
@@ -281,6 +291,16 @@ class Callable
281291
return m_callable;
282292
}
283293

294+
auto& GetExpressionCallable() const
295+
{
296+
return boost::get<ExpressionCallable>(m_callable);
297+
}
298+
299+
auto& GetStatementCallable() const
300+
{
301+
return boost::get<StatementCallable>(m_callable);
302+
}
303+
284304
private:
285305
CallableHolder m_callable;
286306
};

src/render_context.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ class RenderContext
7373
{
7474
return *m_currentScope;
7575
}
76+
auto& GetGlobalScope()
77+
{
78+
return m_scopes.front();
79+
}
7680
auto GetRendererCallback()
7781
{
7882
return m_rendererCallback;

src/renderer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class ExpressionRenderer : public RendererBase
6868

6969
void Render(OutStream& os, RenderContext& values) override
7070
{
71-
os.WriteValue(m_expression->Evaluate(values));
71+
m_expression->Render(os, values);
7272
}
7373
private:
7474
ExpressionEvaluatorPtr<> m_expression;

src/statements.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,18 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values)
148148

149149
auto& scope = innerContext.EnterScope();
150150
scope["$$__super_block"] = static_cast<RendererBase*>(this);
151+
scope["super"] = Callable([this](const CallParams&, OutStream& stream, RenderContext& context) {
152+
m_mainBody->Render(stream, context);
153+
});
151154
blockRenderer->RenderBlock(m_name, os, innerContext);
152155
innerContext.ExitScope();
156+
157+
auto& globalScope = values.GetGlobalScope();
158+
auto selfMap = boost::get<MapAdapter>(&globalScope[std::string("self")]);
159+
if (!selfMap->HasValue(m_name))
160+
selfMap->SetValue(m_name, Callable([this](const CallParams&, OutStream& stream, RenderContext& context) {
161+
Render(stream, context);
162+
}));
153163
}
154164

155165
void BlockStatement::Render(OutStream& os, RenderContext& values)

src/template_impl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class TemplateImpl : public ITemplateImpl
8585
}
8686
RendererCallback callback(this);
8787
RenderContext context(intParams, &callback);
88+
InitRenderContext(context);
8889
OutStream outStream(
8990
[this, &os](const void* ptr, size_t length) {
9091
os.write(reinterpret_cast<const CharT*>(ptr), length);
@@ -97,6 +98,13 @@ class TemplateImpl : public ITemplateImpl
9798
}
9899
}
99100

101+
InternalValueMap& InitRenderContext(RenderContext& context)
102+
{
103+
auto& curScope = context.GetCurrentScope();
104+
curScope["self"] = MapAdapter::CreateAdapter(InternalValueMap());
105+
return curScope;
106+
}
107+
100108
auto LoadTemplate(const std::string& fileName)
101109
{
102110
using ResultType = boost::variant<EmptyValue, std::shared_ptr<TemplateImpl<char>>, std::shared_ptr<TemplateImpl<wchar_t>>>;

test/extends_test.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,53 @@ TEST_F(ExtendsTest, DoubleBlocksExtends)
7373
EXPECT_STREQ(expectedResult.c_str(), result.c_str());
7474
}
7575

76+
TEST_F(ExtendsTest, SuperBlocksExtends)
77+
{
78+
m_templateFs->AddFile("base.j2tpl", "Hello World! ->{% block b1 %}=>block b1<={% endblock %}<- ->{% block b2 %}{% endblock b2%}<-");
79+
m_templateFs->AddFile("derived.j2tpl", R"({% extends "base.j2tpl" %}{%block b1%}Extended block b1!{{super()}}{%endblock%}Some Stuff{%block b2%}Extended block b2!{%endblock%})");
80+
81+
auto baseTpl = m_env.LoadTemplate("base.j2tpl");
82+
auto tpl = m_env.LoadTemplate("derived.j2tpl");
83+
84+
std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{});
85+
std::cout << baseResult << std::endl;
86+
std::string expectedResult = "Hello World! -><- -><-";
87+
EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str());
88+
std::string result = tpl.RenderAsString(jinja2::ValuesMap{});
89+
std::cout << result << std::endl;
90+
expectedResult = "Hello World! ->Extended block b1!=>block b1<=<- ->Extended block b2!<-";
91+
EXPECT_STREQ(expectedResult.c_str(), result.c_str());
92+
}
93+
94+
TEST_F(ExtendsTest, SuperAndSelfBlocksExtends)
95+
{
96+
m_templateFs->AddFile("base.j2tpl", R"(Hello World!{%set testVal='first entry' %}
97+
->{% block b1 scoped %}=>block b1 - {{testVal}}<={% endblock %}<-
98+
-->{{self.b1()}}<---->{{self.b2()}}<--{%set testVal='second entry' %}
99+
->{% block b2 %}{% endblock b2%}<-
100+
-->{{self.b1()}}<---->{{self.b2()}}<--
101+
)");
102+
m_templateFs->AddFile("derived.j2tpl", R"({% extends "base.j2tpl" %}{%block b1%}Extended block b1!{{super()}}{%endblock%}Some Stuff{%block b2%}Extended block b2!{%endblock%})");
103+
104+
auto baseTpl = m_env.LoadTemplate("base.j2tpl");
105+
auto tpl = m_env.LoadTemplate("derived.j2tpl");
106+
107+
std::string baseResult = baseTpl.RenderAsString(jinja2::ValuesMap{});
108+
std::cout << baseResult << std::endl;
109+
std::string expectedResult = R"(Hello World!-><-
110+
--><----><---><-
111+
--><----><--
112+
)";
113+
EXPECT_STREQ(expectedResult.c_str(), baseResult.c_str());
114+
std::string result = tpl.RenderAsString(jinja2::ValuesMap{});
115+
std::cout << result << std::endl;
116+
expectedResult = R"(Hello World!->Extended block b1!=>block b1 - first entry<=<-
117+
-->Extended block b1!=>block b1 - first entry<=<----><--->Extended block b2!<-
118+
-->Extended block b1!=>block b1 - second entry<=<---->Extended block b2!<--
119+
)";
120+
EXPECT_STREQ(expectedResult.c_str(), result.c_str());
121+
}
122+
76123
TEST_F(ExtendsTest, ScopedBlocksExtends)
77124
{
78125
m_templateFs->AddFile("base.j2tpl", R"(Hello World!

0 commit comments

Comments
 (0)