8000 Implement 'random', 'trim', 'title', 'upper', 'lower' and 'wordcount'… · jinja2cpp/Jinja2Cpp@b514a5d · GitHub
[go: up one dir, main page]

Skip to content

Commit b514a5d

Browse files
authored
Implement 'random', 'trim', 'title', 'upper', 'lower' and 'wordcount' filters (#18)
* Bugfix and filter tests * Implement 'trim' and 'random' filters * Implement 'title', 'upper', 'lower' and 'wordcount' filters
1 parent 252ee3b commit b514a5d

File tree

5 files changed

+168
-18
lines changed

5 files changed

+168
-18
lines changed

src/filters.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <numeric>
88
#include <sstream>
99
#include <string>
10+
#include <random>
1011

1112
using namespace std::string_literals;
1213

@@ -48,11 +49,12 @@ std::unordered_map<std::string, ExpressionFilter::FilterFactoryFn> s_filters = {
4849
{"last", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::LastItemMode)},
4950
{"length", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::LengthMode)},
5051
{"list", FilterFactory<filters::ValueConverter>::MakeCreator(filters::ValueConverter::ToListMode)},
52+
{"lower", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::LowerMode)},
5153
{"map", &FilterFactory<filters::Map>::Create},
5254
{"max", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::MaxItemMode)},
5355
{"min", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::MinItemMode)},
5456
{"pprint", &FilterFactory<filters::PrettyPrint>::Create},
55-
{"random", &FilterFactory<filters::Random>::Create},
57+
{"random", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::RandomMode)},
5658
{"reject", FilterFactory<filters::Tester>::MakeCreator(filters::Tester::RejectMode)},
5759
{"rejectattr", FilterFactory<filters::Tester>::MakeCreator(filters::Tester::RejectAttrMode)},
5860
{"replace", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::ReplaceMode)},
@@ -417,12 +419,6 @@ struct PrettyPrinter : visitors::BaseVisitor<InternalValue>
417419
os << val;
418420
return InternalValue(os.str());
419421
}
420-
//
421-
// template<typename U>
422-
// InternalValue operator()(U&& val) const
423-
// {
424-
// return InternalValue();
425-
// }
426422

427423
const RenderContext* m_context;
428424
};
@@ -463,6 +459,7 @@ SequenceAccessor::SequenceAccessor(FilterParams params, SequenceAccessor::Mode m
463459
case MinItemMode:
464460
ParseParams({{"case_sensitive", false, InternalValue(false)}, {"attribute", false}}, params);
465461
break;
462+
case RandomMode:
466463
case ReverseMode:
467464
break;
468465
case SumItemsMode:
@@ -523,6 +520,14 @@ InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderConte
523520
case LengthMode:
524521
result = static_cast<int64_t>(list.GetSize());
525522
break;
523+
case RandomMode:
524+
{
525+
std::random_device rd;
526+
std::mt19937 gen(rd());
527+
std::uniform_int_distribution<> dis(0, static_cast<int>(list.GetSize()) - 1);
528+
result = list.GetValueByIndex(dis(gen));
529+
break;
530+
}
526531
case MaxItemMode:
527532
{
528533
auto b = list.begin();

src/filters.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class SequenceAccessor : public FilterBase
105105
LengthMode,
106106
MaxItemMode,
107107
MinItemMode,
108+
RandomMode,
108109
ReverseMode,
109110
SumItemsMode,
110111
UniqueItemsMode,
@@ -165,6 +166,7 @@ class StringConverter : public FilterBase
165166
CamelMode,
166167
EscapeCppMode,
167168
EscapeHtmlMode,
169+
LowerMode,
168170
ReplaceMode,
169171
TitleMode,
170172
TrimMode,

src/string_converter_filter.cpp

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77
#include <numeric>
88
#include <sstream>
99

10+
#include <boost/algorithm/string/trim_all.hpp>
11+
12+
namespace ba = boost::algorithm;
13+
1014
namespace jinja2
1115
{
1216

1317
namespace filters
1418
{
15-
19+
1620
template<typename D>
1721
struct StringEncoder : public visitors::BaseVisitor<>
1822
{
@@ -22,15 +26,15 @@ struct StringEncoder : public visitors::BaseVisitor<>
2226
InternalValue operator() (const std::basic_string<CharT>& str) const
2327
{
2428
std::basic_string<CharT> result;
25-
29+
2630
for (auto& ch : str)
2731
{
28-
D::EncodeChar(ch, [&result](auto ... chs) {AppendChar(result, chs...);});
32+
static_cast<const D*>(this)->EncodeChar(ch, [&result](auto ... chs) {AppendChar(result, chs...);});
2933
}
30-
34+
3135
return result;
3236
}
33-
37+
3438
template<typename Str, typename CharT>
3539
static void AppendChar(Str& str, CharT ch)
3640
{
@@ -42,13 +46,26 @@ struct StringEncoder : public visitors::BaseVisitor<>
4246
str.push_back(static_cast<typename Str::value_type>(ch));
4347
AppendChar(str, chs...);
4448
}
45-
49+
};
50+
51+
template<typename Fn>
52+
struct GenericStringEncoder : public StringEncoder<GenericStringEncoder<Fn>>
53+
{
54+
GenericStringEncoder(Fn fn) : m_fn(std::move(fn)) {}
55+
56+
template<typename CharT, typename AppendFn>
57+
void EncodeChar(CharT ch, AppendFn&& fn) const
58+
{
59+
m_fn(ch, std::forward<AppendFn>(fn));
60+
}
61+
62+
mutable Fn m_fn;
4663
};
4764

4865
struct UrlStringEncoder : public StringEncoder<UrlStringEncoder>
4966
{
5067
template<typename CharT, typename Fn>
51-
static void EncodeChar(CharT ch, Fn&& fn)
68+
void EncodeChar(CharT ch, Fn&& fn) const
5269
{
5370
switch (ch)
5471
{
@@ -121,22 +138,105 @@ struct UrlStringEncoder : public StringEncoder<UrlStringEncoder>
121138
default:
122139
fn(ch);
123140
break;
124-
}
141+
}
142+
}
143+
};
144+
145+
template<typename Fn>
146+
struct StringConverterImpl : public visitors::BaseVisitor<>
147+
{
148+
using BaseVisitor::operator ();
149+
150+
StringConverterImpl(const Fn& fn) : m_fn(fn) {}
151+
152+
template<typename CharT>
153+
InternalValue operator()(const std::basic_string<CharT>& str) const
154+
{
155+
return m_fn(str);
125156
}
126-
};
157+
158+
const Fn& m_fn;
159+
};
160+
161+
template<template<typename> class Cvt = StringConverterImpl, typename Fn>
162+
auto ApplyConverter(const InternalValue& str, Fn&& fn)
163+
{
164+
return Apply<Cvt<Fn>>(str, std::forward<Fn>(fn));
165+
}
166+
127167

128168
StringConverter::StringConverter(FilterParams params, StringConverter::Mode mode)
129169
: m_mode(mode)
130170
{
131-
171+
switch (m_mode)
172+
{
173+
case ReplaceMode:
174+
ParseParams({{"old", true}, {"new", true}, {"count", false}}, params);
175+
break;
176+
}
132177
}
133178

134179
InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContext& context)
135180
{
136181
InternalValue result;
137182

183+
auto isAlpha = ba::is_alpha();
184+
auto isAlNum = ba::is_alnum();
185+
138186
switch (m_mode)
139187
{
188+
case TrimMode:
189+
result = ApplyConverter(baseVal, [](auto str) {
190+
ba::trim_all(str);
191+
return str;
192+
});
193+
break;
194+
case TitleMode:
195+
result = ApplyConverter<GenericStringEncoder>(baseVal, [isDelim = true, &isAlpha, &isAlNum](auto ch, auto&& fn) mutable {
196+
if (isDelim && isAlpha(ch))
197+
{
198+
isDelim = false;
199+
fn(std::toupper(ch, std::locale()));
200+
return;
201+
}
202+
203+
isDelim = !isAlNum(ch);
204+
fn(ch);
205+
});
206+
break;
207+
case WordCountMode:
208+
{
209+
int64_t wc = 0;
210+
ApplyConverter<GenericStringEncoder>(baseVal, [isDelim = true, &wc, &isAlpha, &isAlNum](auto ch, auto&& fn) mutable {
211+
if (isDelim && isAlNum(ch))
212+
{
213+
isDelim = false;
214+
wc ++;
215+
return;
216+
}
217+
isDelim = !isAlNum(ch);
218+
});
219+
result = wc;
220+
break;
221+
}
222+
case UpperMode:
223+
result = ApplyConverter<GenericStringEncoder>(baseVal, [&isAlpha](auto ch, auto&& fn) mutable {
224+
if (isAlpha(ch))
225+
fn(std::toupper(ch, std::locale()));
226+
else
227+
fn(ch);
228+
});
229+
break;
230+
case LowerMode:
231+
result = ApplyConverter<GenericStringEncoder>(baseVal, [&isAlpha](auto ch, auto&& fn) mutable {
232+
if (isAlpha(ch))
233+
fn(std::tolower(ch, std::locale()));
234+
else
235+
fn(ch);
236+
});
237+
break;
238+
case ReplaceMode:
239+
break;
140240
case UrlEncodeMode:
141241
result = Apply<UrlStringEncoder>(baseVal);
142242
break;
@@ -147,4 +247,4 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex
147247
}
148248

149249
}
150-
}
250+
}

test/filters_test.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,45 @@ INSTANTIATE_TEST_CASE_P(Convert, FilterGenericTest, ::testing::Values(
275275
InputOutputPair{"'100' | list | pprint", "['1', '0', '0']"},
276276
InputOutputPair{"{'name'='itemName', 'val'='itemValue'} | list | pprint", "['name': 'itemName', 'val': 'itemValue']"}
277277
));
278+
279+
INSTANTIATE_TEST_CASE_P(Trim, FilterGenericTest, ::testing::Values(
280+
InputOutputPair{"'string' | trim | pprint", "'string'"},
281+
InputOutputPair{"' string' | trim | pprint", "'string'"},
282+
InputOutputPair{"'string ' | trim | pprint", "'string'"},
283+
InputOutputPair{"' string ' | trim | pprint", "'string'"}/*,
284+
InputOutputPair{"wstringValue | trim", "'hello world'"}*/
285+
));
286+
287+
INSTANTIATE_TEST_CASE_P(Title, FilterGenericTest, ::testing::Values(
288+
InputOutputPair{"'string' | title | pprint", "'String'"},
289+
InputOutputPair{"'1234string' | title | pprint", "'1234string'"},
290+
InputOutputPair{"'hello world' | title | pprint", "'Hello World'"},
291+
InputOutputPair{"'hello123ooo, world!' | title | pprint", "'Hello123ooo, World!'"}/*,
292+
InputOutputPair{"wstringValue | trim", "'hello world'"}*/
293+
));
294+
295+
INSTANTIATE_TEST_CASE_P(Upper, FilterGenericTest, ::testing::Values(
296+
InputOutputPair{"'string' | upper | pprint", "'STRING'"},
297+
InputOutputPair{"'1234string' | upper | pprint", "'1234STRING'"},
298+
InputOutputPair{"'hello world' | upper | pprint", "'HELLO WORLD'"},
299+
InputOutputPair{"'hello123ooo, world!' | upper | pprint", "'HELLO123OOO, WORLD!'"}/*,
300+
InputOutputPair{"wstringValue | trim", "'hello world'"}*/
301+
));
302+
303+
304+
INSTANTIATE_TEST_CASE_P(Lower, FilterGenericTest, ::testing::Values(
305+
InputOutputPair{"'String' | lower | pprint", "'string'"},
306+
InputOutputPair{"'1234String' | lower | pprint", "'1234string'"},
307+
InputOutputPair{"'Hello World' | lower | pprint", "'hello world'"},
308+
InputOutputPair{"'Hello123OOO, World!' | lower | pprint", "'hello123ooo, world!'"}/*,
309+
InputOutputPair{"wstringValue | trim", "'hello world'"}*/
310+
));
311+
312+
313+
INSTANTIATE_TEST_CASE_P(WordCount, FilterGenericTest, ::testing::Values(
314+
InputOutputPair{"'string' | wordcount", "1"},
315+
InputOutputPair{"'1234string' | wordcount", "1"},
316+
InputOutputPair{"'hello world' | wordcount", "2"},
317+
InputOutputPair{"'hello123ooo, world!' | wordcount", "2"}/*,
318+
InputOutputPair{"wstringValue | trim", "'hello world'"}*/
319+
));

test/test_tools.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ inline jinja2::ValuesMap PrepareTestData()
6363
{"doubleValue", 12.123f},
6464
{"doubleList", jinja2::ValuesList{9.5, 0.5, 8.5, 1.5, 7.5, 2.5, 6.4, 3.8, 5.2, -4.7}},
6565
{"stringValue", "rain"},
66+
{"wstringValue", std::wstring(L" hello world ")},
6667
{"stringList", jinja2::ValuesList{"string9", "string0", "string8", "string1", "string7", "string2", "string6", "string3", "string5", "string4"}},
6768
{"boolFalseValue", false},
6869
{"boolTrueValue", true},

0 commit comments

Comments
 (0)
0