Skip to content

Commit 6cd8e31

Browse files
committed
Implement 'applymacro' filter
1 parent dc01330 commit 6cd8e31

File tree

8 files changed

+205
-25
lines changed

8 files changed

+205
-25
lines changed

src/filters.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "filters.h"
2+
#include "out_stream.h"
23
#include "testers.h"
34
#include "value_visitors.h"
45
#include "value_helpers.h"
@@ -31,6 +32,7 @@ struct FilterFactory
3132

3233
std::unordered_map<std::string, ExpressionFilter::FilterFactoryFn> s_filters = {
3334
{"abs", FilterFactory<filters::ValueConverter>::MakeCreator(filters::ValueConverter::AbsMode)},
35+
{"applymacro", &FilterFactory<filters::ApplyMacro>::Create},
3436
{"attr", &FilterFactory<filters::Attribute>::Create},
3537
{"batch", FilterFactory<filters::Slice>::MakeCreator(filters::Slice::BatchMode)},
3638
{"camelize", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::CamelMode)},
@@ -308,6 +310,50 @@ InternalValue GroupBy::Filter(const InternalValue& baseVal, RenderContext& conte
308310
return ListAdapter::CreateAdapter(std::move(result));
309311
}
310312

313+
ApplyMacro::ApplyMacro(FilterParams params)
314+
{
315+
ParseParams({{"name", true}}, params);
316+
m_mappingParams.kwParams = m_args.extraKwArgs;
317+
m_mappingParams.posParams = m_args.extraPosArgs;
318+
}
319+
320+
InternalValue ApplyMacro::Filter(const InternalValue& baseVal, RenderContext& context)
321+
{
322+
InternalValue macroName = GetArgumentValue("name", context);
323+
if (IsEmpty(macroName))
324+
return InternalValue();
325+
326+
bool macroFound = false;
327+
auto macroValPtr = context.FindValue(AsString(macroName), macroFound);
328+
if (!macroFound)
329+
return InternalValue();
330+
331+
const Callable* callable = GetIf<Callable>(&macroValPtr->second);
332+
if (callable == nullptr || callable->GetKind() != Callable::Macro)
333+
return InternalValue();
334+
335+
CallParams callParams;
336+
callParams.kwParams = m_mappingParams.kwParams;
337+
callParams.posParams.reserve(m_mappingParams.posParams.size() + 1);
338+
callParams.posParams.push_back(std::make_shared<ConstantExpression>(baseVal));
339+
callParams.posParams.insert(callParams.posParams.end(), m_mappingParams.posParams.begin(), m_mappingParams.posParams.end());
340+
341+
InternalValue result;
342+
if (callable->GetType() == Callable::Type::Expression)
343+
{
344+
result = callable->GetExpressionCallable()(callParams, context);
345+
}
346+
else
347+
{
348+
TargetString resultStr;
349+
auto stream = context.GetRendererCallback()->GetStreamOnString(resultStr);
350+
callable->GetStatementCallable()(callParams, stream, context);
351+
result = std::move(resultStr);
352+
}
353+
354+
return result;
355+
}
356+
311357
Map::Map(FilterParams params)
312358
{
313359
FilterParams newParams;

src/filters.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ class FilterBase : public FunctionBase, public ExpressionFilter::IExpressionFilt
2222
{
2323
};
2424

25+
class ApplyMacro : public FilterBase
26+
{
27+
public:
28+
ApplyMacro(FilterParams params);
29+
30+
InternalValue Filter(const InternalValue& baseVal, RenderContext& context);
31+
32+
private:
33+
FilterParams m_mappingParams;
34+
};
35+
2536
class Attribute : public FilterBase
2637
{
2738
public:

src/internal_value.h

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,13 @@ struct KeyValuePair
362362
class Callable
363363
{
364364
public:
365+
enum Kind
366+
{
367+
GlobalFunc,
368+
SpecialFunc,
369+
Macro,
370+
UserCallable
371+
};
365372
using ExpressionCallable = std::function<InternalValue (const CallParams&, RenderContext&)>;
366373
using StatementCallable = std::function<void (const CallParams&, OutStream&, RenderContext&)>;
367374

@@ -373,13 +380,15 @@ class Callable
373380
Statement
374381
};
375382

376-
Callable(ExpressionCallable&& callable)
377-
: m_callable(std::move(callable))
383+
Callable(Kind kind, ExpressionCallable&& callable)
384+
: m_kind(kind)
385+
, m_callable(std::move(callable))
378386
{
379387
}
380388

381-
Callable(StatementCallable&& callable)
382-
: m_callable(std::move(callable))
389+
Callable(Kind kind, StatementCallable&& callable)
390+
: m_kind(kind)
391+
, m_callable(std::move(callable))
383392
{
384393
}
385394

@@ -388,6 +397,11 @@ class Callable
388397
return m_callable.index() == 0 ? Type::Expression : Type::Statement;
389398
}
390399

400+
auto GetKind() const
401+
{
402+
return m_kind;
403+
}
404+
391405
auto& GetCallable() const
392406
{
393407
return m_callable;
@@ -404,6 +418,7 @@ class Callable
404418
}
405419

406420
private:
421+
Kind m_kind;
407422
CallableHolder m_callable;
408423
};
409424

src/out_stream.h

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,40 @@
33

44
#include "internal_value.h"
55
#include <functional>
6+
#include <iostream>
7+
#include <sstream>
68

79
namespace jinja2
810
{
911
class OutStream
1012
{
1113
public:
12-
OutStream(std::function<void (const void*, size_t)> chunkWriter, std::function<void (const InternalValue& val)> valueWriter)
13-
: m_bufferWriter(std::move(chunkWriter))
14-
, m_valueWriter(valueWriter)
14+
struct StreamWriter
1515
{
16-
}
16+
virtual ~StreamWriter() {}
17+
18+
virtual void WriteBuffer(const void* ptr, size_t length) = 0;
19+
virtual void WriteValue(const InternalValue &val) = 0;
20+
};
21+
22+
OutStream(std::function<StreamWriter*()> writerGetter)
23+
: m_writerGetter(std::move(writerGetter))
24+
{}
1725

1826
void WriteBuffer(const void* ptr, size_t length)
1927
{
20-
m_bufferWriter(ptr, length);
28+
m_writerGetter()->WriteBuffer(ptr, length);
2129
}
2230

2331
void WriteValue(const InternalValue& val)
2432
{
25-
m_valueWriter(val);
33+
m_writerGetter()->WriteValue(val);
2634
}
2735

2836
private:
29-
std::function<void (const void*, size_t)> m_bufferWriter;
30-
std::function<void (const InternalValue& value)> m_valueWriter;
37+
std::function<StreamWriter*()> m_writerGetter;
3138
};
39+
3240
} // jinja2
3341

3442
#endif // OUT_STREAM_H

src/render_context.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class TemplateImpl;
1616
struct IRendererCallback
1717
{
1818
virtual TargetString GetAsTargetString(const InternalValue& val) = 0;
19+
virtual OutStream GetStreamOnString(TargetString& str) = 0;
1920
virtual nonstd::variant<EmptyValue,
2021
nonstd::expected<std::shared_ptr<TemplateImpl<char>>, ErrorInfo>,
2122
nonstd::expected<std::shared_ptr<TemplateImpl<wchar_t>>, ErrorInfoW>> LoadTemplate(const std::string& fileName) const = 0;

src/statements.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, Rende
2424
context["loop"] = MapAdapter::CreateAdapter(&loopVar);
2525
if (m_isRecursive)
2626
{
27-
loopVar["operator()"] = Callable([this](const CallParams& params, OutStream& stream, RenderContext& context) {
27+
loopVar["operator()"] = Callable(Callable::GlobalFunc, [this](const CallParams& params, OutStream& stream, RenderContext& context) {
2828
bool isSucceeded = false;
2929
auto parsedParams = helpers::ParseCallParams({{"var", true}}, params, isSucceeded);
3030
if (!isSucceeded)
@@ -200,7 +200,7 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values)
200200

201201
auto& scope = innerContext.EnterScope();
202202
scope["$$__super_block"] = static_cast<RendererBase*>(this);
203-
scope["super"] = Callable([this](const CallParams&, OutStream& stream, RenderContext& context) {
203+
scope["super"] = Callable(Callable::SpecialFunc, [this](const CallParams&, OutStream& stream, RenderContext& context) {
204204
m_mainBody->Render(stream, context);
205205
});
206206
if (!m_isScoped)
@@ -212,7 +212,7 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values)
212212
auto& globalScope = values.GetGlobalScope();
213213
auto selfMap = GetIf<MapAdapter>(&globalScope[std::string("self")]);
214214
if (!selfMap->HasValue(m_name))
215-
selfMap->SetValue(m_name, MakeWrapped(Callable([this](const CallParams&, OutStream& stream, RenderContext& context) {
215+
selfMap->SetValue(m_name, MakeWrapped(Callable(Callable::SpecialFunc, [this](const CallParams&, OutStream& stream, RenderContext& context) {
216216
Render(stream, context);
217217
})));
218218
}
@@ -322,7 +322,7 @@ void MacroStatement::Render(OutStream& os, RenderContext& values)
322322
{
323323
PrepareMacroParams(values);
324324

325-
values.GetCurrentScope()[m_name] = Callable([this](const CallParams& callParams, OutStream& stream, RenderContext& context) {
325+
values.GetCurrentScope()[m_name] = Callable(Callable::Macro, [this](const CallParams& callParams, OutStream& stream, RenderContext& context) {
326326
InvokeMacroRenderer(callParams, stream, context);
327327
});
328328
}
@@ -398,7 +398,7 @@ void MacroCallStatement::Render(OutStream& os, RenderContext& values)
398398
if (hasCallerVal)
399399
prevCaller = callerP->second;
400400

401-
curScope["caller"] = Callable([this](const CallParams& callParams, OutStream& stream, RenderContext& context) {
401+
curScope["caller"] = Callable(Callable::Macro, [this](const CallParams& callParams, OutStream& stream, RenderContext& context) {
402402
InvokeMacroRenderer(callParams, stream, context);
403403
});
404404

src/template_impl.h

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,53 @@ struct TemplateLoader<wchar_t>
4444
}
4545
};
4646

47+
template<typename CharT>
48+
class GenericStreamWriter : public OutStream::StreamWriter
49+
{
50+
public:
51+
GenericStreamWriter(std::basic_ostream<CharT>& os)
52+
: m_os(os)
53+
{}
54+
55+
// StreamWriter interface
56+
void WriteBuffer(const void* ptr, size_t length) override
57+
{
58+
m_os.write(reinterpret_cast<const CharT*>(ptr), length);
59+
}
60+
void WriteValue(const InternalValue& val) override
61+
{
62+
Apply<visitors::ValueRenderer<CharT>>(val, m_os);
63+
}
64+
65+
private:
66+
std::basic_ostream<CharT>& m_os;
67+
};
68+
69+
template<typename CharT>
70+
class StringStreamWriter : public OutStream::StreamWriter
71+
{
72+
public:
73+
StringStreamWriter(std::basic_string<CharT>* targetStr)
74+
: m_targetStr(targetStr)
75+
{}
76+
77+
// StreamWriter interface
78+
void WriteBuffer(const void* ptr, size_t length) override
79+
{
80+
m_targetStr->append(reinterpret_cast<const CharT*>(ptr), length);
81+
// m_os.write(reinterpret_cast<const CharT*>(ptr), length);
82+
}
83+
void WriteValue(const InternalValue& val) override
84+
{
85+
std::basic_ostringstream<CharT> os;
86+
Apply<visitors::ValueRenderer<CharT>>(val, os);
87+
(*m_targetStr) += os.str();
88+
}
89+
90+
private:
91+
std::basic_string<CharT>* m_targetStr;
92+
};
93+
4794
template<typename CharT>
4895
class TemplateImpl : public ITemplateImpl
4996
{
@@ -90,14 +137,8 @@ class TemplateImpl : public ITemplateImpl
90137
RendererCallback callback(this);
91138
RenderContext context(intParams, &callback);
92139
InitRenderContext(context);
93-
OutStream outStream(
94-
[this, &os](const void* ptr, size_t length) {
95-
os.write(reinterpret_cast<const CharT*>(ptr), length);
96-
},
97-
[this, &os](const InternalValue& val) {
98-
Apply<visitors::ValueRenderer<CharT>>(val, os);
99-
}
100-
);
140+
OutStream outStream([writer = GenericStreamWriter<CharT>(os)]() mutable -> OutStream::StreamWriter* {return &writer;});
141+
101142
m_renderer->Render(outStream, context);
102143
}
103144

@@ -139,6 +180,13 @@ class TemplateImpl : public ITemplateImpl
139180
return TargetString(os.str());
140181
}
141182

183+
OutStream GetStreamOnString(TargetString& str) override
184+
{
185+
using string_t = std::basic_string<CharT>;
186+
str = string_t();
187+
return OutStream([writer = StringStreamWriter<CharT>(&str.get<string_t>())]() mutable -> OutStream::StreamWriter* {return &writer;});
188+
}
189+
142190
nonstd::variant<EmptyValue,
143191
nonstd::expected<std::shared_ptr<TemplateImpl<char>>, ErrorInfo>,
144192
nonstd::expected<std::shared_ptr<TemplateImpl<wchar_t>>, ErrorInfoW>> LoadTemplate(const std::string& fileName) const override

test/filters_test.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,57 @@ TEST_P(FilterGroupByTest, Test)
7070
EXPECT_EQ(expectedResult, result);
7171
}
7272

73+
TEST(FilterGenericTestSingle, ApplyMacroTest)
74+
{
75+
std::string source = R"(
76+
{% macro test(str) %}{{ str | upper }}{% endmacro %}
77+
{{ 'Hello World!' | applymacro(name='test') }}
78+
{{ ['str1', 'str2', 'str3'] | map('applymacro', name='test') | join(', ') }}
79+
)";
80+
81+
Template tpl;
82+
auto parseRes = tpl.Load(source);
83+
EXPECT_TRUE(parseRes.has_value());
84+
if (!parseRes)
85+
{
86+
std::cout << parseRes.error() << std::endl;
87+
return;
88+
}
89+
90+
std::string result = tpl.RenderAsString(PrepareTestData());
91+
std::cout << result << std::endl;
92+
std::string expectedResult = R"(
93+
HELLO WORLD!
94+
STR1, STR2, STR3
95+
)";
96+
EXPECT_EQ(expectedResult, result);
97+
}
98+
99+
TEST(FilterGenericTestSingle, ApplyMacroWithCallbackTest)
100+
{
101+
std::string source = R"(
102+
{% macro joiner(list, delim) %}{{ list | map('applymacro', name='caller') | join(delim) }}{% endmacro %}
103+
{% call(item) joiner(['str1', 'str2', 'str3'], '->') %}{{item | upper}}{% endcall %}
104+
105+
)";
106+
107+
Template tpl;
108+
auto parseRes = tpl.Load(source);
109+
EXPECT_TRUE(parseRes.has_value());
110+
if (!parseRes)
111+
{
112+
std::cout << parseRes.error() << std::endl;
113+
return;
114+
}
115+
116+
std::string result = tpl.RenderAsString(PrepareTestData());
117+
std::cout << result << std::endl;
118+
std::string expectedResult = R"(
119+
STR1->STR2->STR3
120+
)";
121+
EXPECT_EQ(expectedResult, result);
122+
}
123+
73124
INSTANTIATE_TEST_CASE_P(StringJoin, FilterGenericTest, ::testing::Values(
74125
InputOutputPair{"['str1', 'str2', 'str3'] | join", "str1str2str3"},
75126
InputOutputPair{"['str1', 'str2', 'str3'] | join(' ')", "str1 str2 str3"},

0 commit comments

Comments
 (0)