Skip to content

Commit 3ed0930

Browse files
authored
Implement 'groupby' filter (#20)
* Implement 'groupby' filter * Fix 'groupby' filter tests
1 parent d3e2aef commit 3ed0930

File tree

2 files changed

+157
-2
lines changed

2 files changed

+157
-2
lines changed

src/filters.cpp

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,51 @@ InternalValue DictSort::Filter(const InternalValue& baseVal, RenderContext& cont
275275

276276
GroupBy::GroupBy(FilterParams params)
277277
{
278-
278+
ParseParams({{"attribute", true}}, params);
279279
}
280280

281281
InternalValue GroupBy::Filter(const InternalValue& baseVal, RenderContext& context)
282282
{
283-
return InternalValue();
283+
bool isConverted = false;
284+
ListAdapter list = ConvertToList(baseVal, isConverted);
285+
286+
if (!isConverted)
287+
return InternalValue();
288+
289+
InternalValue attrName = GetArgumentValue("attribute", context);
290+
291+
auto equalComparator = [](auto& val1, auto& val2) {
292+
InternalValue cmpRes = Apply2<visitors::BinaryMathOperation>(val1, val2, BinaryExpression::LogicalEq, BinaryExpression::CaseSensitive);
293+
294+
return ConvertToBool(cmpRes);
295+
};
296+
297+
struct GroupInfo
298+
{
299+
InternalValue grouper;
300+
InternalValueList items;
301+
};
302+
303+
std::vector<GroupInfo> groups;
304+
305+
for (auto& item : list)
306+
{
307+
auto attr = Subscript(item, attrName);
308+
auto p = std::find_if(groups.begin(), groups.end(), [&equalComparator, &attr](auto& i) {return equalComparator(i.grouper, attr);});
309+
if (p == groups.end())
310+
groups.push_back(GroupInfo{attr, {item}});
311+
else
312+
p->items.push_back(item);
313+
}
314+
315+
InternalValueList result;
316+
for (auto& g : groups)
317+
{
318+
InternalValueMap groupItem{{"grouper", std::move(g.grouper)}, {"list", ListAdapter::CreateAdapter(std::move(g.items))}};
319+
result.push_back(MapAdapter::CreateAdapter(std::move(groupItem)));
320+
}
321+
322+
return ListAdapter::CreateAdapter(std::move(result));
284323
}
285324

286325
Map::Map(FilterParams params)

test/filters_test.cpp

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ using FilterGenericTest = InputOutputPairTest<FilterGenericTestTag>;
1212
struct ListIteratorTestTag;
1313
using ListIteratorTest = InputOutputPairTest<ListIteratorTestTag>;
1414

15+
struct GroupByTestTag;
16+
using FilterGroupByTest = InputOutputPairTest<GroupByTestTag>;
17+
1518
TEST_P(FilterGenericTest, Test)
1619
{
1720
auto& testParam = GetParam();
@@ -40,6 +43,48 @@ TEST_P(ListIteratorTest, Test)
4043
EXPECT_EQ(expectedResult, result);
4144
}
4245

46+
TEST_P(FilterGroupByTest, Test)
47+
{
48+
auto& testParam = GetParam();
49+
50+
jinja2::ValuesList testData;
51+
for (int n = 0; n < 10; ++ n)
52+
{
53+
TestStruct s;
54+
std::ostringstream str;
55+
std::wostringstream wstr;
56+
57+
str << "test string " << n / 2;
58+
wstr << L"test string " << n;
59+
60+
s.intValue = n / 2;
61+
s.dblValue = static_cast<double>(n / 2) / 2;
62+
s.boolValue = n % 2 == 1;
63+
s.strValue = str.str();
64+
s.wstrValue = wstr.str();
65+
66+
testData.push_back(jinja2::Reflect(std::move(s)));
67+
}
68+
69+
jinja2::ValuesMap params {{"testData", std::move(testData)}};
70+
71+
std::string source = R"(
72+
{% for grouper, list in )" + testParam.tpl + R"(
73+
%}grouper: {{grouper | pprint }}
74+
{% for i in list %}
75+
{'intValue': {{i.intValue}}, 'dblValue': {{i.dblValue}}, 'boolValue': {{i.boolValue}}, 'strValue': '{{i.strValue}}', 'wstrValue': '<wchar_string>'}
76+
{% endfor %}
77+
{% endfor %})";
78+
79+
Template tpl;
80+
ASSERT_TRUE(tpl.Load(source));
81+
82+
std::string result = tpl.RenderAsString(params);
83+
std::cout << result << std::endl;
84+
std::string expectedResult = testParam.result;
85+
EXPECT_EQ(expectedResult, result);
86+
}
87+
4388
INSTANTIATE_TEST_CASE_P(StringJoin, FilterGenericTest, ::testing::Values(
4489
InputOutputPair{"['str1', 'str2', 'str3'] | join", "str1str2str3"},
4590
InputOutputPair{"['str1', 'str2', 'str3'] | join(' ')", "str1 str2 str3"},
@@ -216,6 +261,77 @@ INSTANTIATE_TEST_CASE_P(PPrint, FilterGenericTest, ::testing::Values(
216261
InputOutputPair{"{'key'='itemName'} | pprint", "{'key': 'itemName'}"}
217262
));
218263

264+
INSTANTIATE_TEST_CASE_P(GroupBy, FilterGroupByTest, ::testing::Values(
265+
InputOutputPair{"testData | groupby('intValue')", R"(
266+
grouper: 0
267+
{'intValue': 0, 'dblValue': 0, 'boolValue': false, 'strValue': 'test string 0', 'wstrValue': '<wchar_string>'}
268+
{'intValue': 0, 'dblValue': 0, 'boolValue': true, 'strValue': 'test string 0', 'wstrValue': '<wchar_string>'}
269+
grouper: 1
270+
{'intValue': 1, 'dblValue': 0.5, 'boolValue': false, 'strValue': 'test string 1', 'wstrValue': '<wchar_string>'}
271+
{'intValue': 1, 'dblValue': 0.5, 'boolValue': true, 'strValue': 'test string 1', 'wstrValue': '<wchar_string>'}
272+
grouper: 2
273+
{'intValue': 2, 'dblValue': 1, 'boolValue': false, 'strValue': 'test string 2', 'wstrValue': '<wchar_string>'}
274+
{'intValue': 2, 'dblValue': 1, 'boolValue': true, 'strValue': 'test string 2', 'wstrValue': '<wchar_string>'}
275+
grouper: 3
276+
{'intValue': 3, 'dblValue': 1.5, 'boolValue': false, 'strValue': 'test string 3', 'wstrValue': '<wchar_string>'}
277+
{'intValue': 3, 'dblValue': 1.5, 'boolValue': true, 'strValue': 'test string 3', 'wstrValue': '<wchar_string>'}
278+
grouper: 4
279+
{'intValue': 4, 'dblValue': 2, 'boolValue': false, 'strValue': 'test string 4', 'wstrValue': '<wchar_string>'}
280+
{'intValue': 4, 'dblValue': 2, 'boolValue': true, 'strValue': 'test string 4', 'wstrValue': '<wchar_string>'}
281+
)"
282+
},
283+
InputOutputPair{"testData | groupby('dblValue')", R"(
284+
grouper: 0
285+
{'intValue': 0, 'dblValue': 0, 'boolValue': false, 'strValue': 'test string 0', 'wstrValue': '<wchar_string>'}
286+
{'intValue': 0, 'dblValue': 0, 'boolValue': true, 'strValue': 'test string 0', 'wstrValue': '<wchar_string>'}
287+
grouper: 0.5
288+
{'intValue': 1, 'dblValue': 0.5, 'boolValue': false, 'strValue': 'test string 1', 'wstrValue': '<wchar_string>'}
289+
{'intValue': 1, 'dblValue': 0.5, 'boolValue': true, 'strValue': 'test string 1', 'wstrValue': '<wchar_string>'}
290+
grouper: 1
291+
{'intValue': 2, 'dblValue': 1, 'boolValue': false, 'strValue': 'test string 2', 'wstrValue': '<wchar_string>'}
292+
{'intValue': 2, 'dblValue': 1, 'boolValue': true, 'strValue': 'test string 2', 'wstrValue': '<wchar_string>'}
293+
grouper: 1.5
294+
{'intValue': 3, 'dblValue': 1.5, 'boolValue': false, 'strValue': 'test string 3', 'wstrValue': '<wchar_string>'}
295+
{'intValue': 3, 'dblValue': 1.5, 'boolValue': true, 'strValue': 'test string 3', 'wstrValue': '<wchar_string>'}
296+
grouper: 2
297+
{'intValue': 4, 'dblValue': 2, 'boolValue': false, 'strValue': 'test string 4', 'wstrValue': '<wchar_string>'}
298+
{'intValue': 4, 'dblValue': 2, 'boolValue': true, 'strValue': 'test string 4', 'wstrValue': '<wchar_string>'}
299+
)"
300+
},
301+
InputOutputPair{"testData | groupby('strValue')", R"(
302+
grouper: 'test string 0'
303+
{'intValue': 0, 'dblValue': 0, 'boolValue': false, 'strValue': 'test string 0', 'wstrValue': '<wchar_string>'}
304+
{'intValue': 0, 'dblValue': 0, 'boolValue': true, 'strValue': 'test string 0', 'wstrValue': '<wchar_string>'}
305+
grouper: 'test string 1'
306+
{'intValue': 1, 'dblValue': 0.5, 'boolValue': false, 'strValue': 'test string 1', 'wstrValue': '<wchar_string>'}
307+
{'intValue': 1, 'dblValue': 0.5, 'boolValue': true, 'strValue': 'test string 1', 'wstrValue': '<wchar_string>'}
308+
grouper: 'test string 2'
309+
{'intValue': 2, 'dblValue': 1, 'boolValue': false, 'strValue': 'test string 2', 'wstrValue': '<wchar_string>'}
310+
{'intValue': 2, 'dblValue': 1, 'boolValue': true, 'strValue': 'test string 2', 'wstrValue': '<wchar_string>'}
311+
grouper: 'test string 3'
312+
{'intValue': 3, 'dblValue': 1.5, 'boolValue': false, 'strValue': 'test string 3', 'wstrValue': '<wchar_string>'}
313+
{'intValue': 3, 'dblValue': 1.5, 'boolValue': true, 'strValue': 'test string 3', 'wstrValue': '<wchar_string>'}
314+
grouper: 'test string 4'
315+
{'intValue': 4, 'dblValue': 2, 'boolValue': false, 'strValue': 'test string 4', 'wstrValue': '<wchar_string>'}
316+
{'intValue': 4, 'dblValue': 2, 'boolValue': true, 'strValue': 'test string 4', 'wstrValue': '<wchar_string>'}
317+
)"
318+
},
319+
InputOutputPair{"testData | groupby('boolValue')", R"(
320+
grouper: false
321+
{'intValue': 0, 'dblValue': 0, 'boolValue': false, 'strValue': 'test string 0', 'wstrValue': '<wchar_string>'}
322+
{'intValue': 1, 'dblValue': 0.5, 'boolValue': false, 'strValue': 'test string 1', 'wstrValue': '<wchar_string>'}
323+
{'intValue': 2, 'dblValue': 1, 'boolValue': false, 'strValue': 'test string 2', 'wstrValue': '<wchar_string>'}
324+
{'intValue': 3, 'dblValue': 1.5, 'boolValue': false, 'strValue': 'test string 3', 'wstrValue': '<wchar_string>'}
325+
{'intValue': 4, 'dblValue': 2, 'boolValue': false, 'strValue': 'test string 4', 'wstrValue': '<wchar_string>'}
326+
grouper: true
327+
{'intValue': 0, 'dblValue': 0, 'boolValue': true, 'strValue': 'test string 0', 'wstrValue': '<wchar_string>'}
328+
{'intValue': 1, 'dblValue': 0.5, 'boolValue': true, 'strValue': 'test string 1', 'wstrValue': '<wchar_string>'}
329+
{'intValue': 2, 'dblValue': 1, 'boolValue': true, 'strValue': 'test string 2', 'wstrValue': '<wchar_string>'}
330+
{'intValue': 3, 'dblValue': 1.5, 'boolValue': true, 'strValue': 'test string 3', 'wstrValue': '<wchar_string>'}
331+
{'intValue': 4, 'dblValue': 2, 'boolValue': true, 'strValue': 'test string 4', 'wstrValue': '<wchar_string>'}
332+
)"
333+
}
334+
));
219335

220336
INSTANTIATE_TEST_CASE_P(DictSort, FilterGenericTest, ::testing::Values(
221337
InputOutputPair{"{'key'='itemName', 'Value'='ItemValue'} | dictsort | pprint", "['key': 'itemName', 'Value': 'ItemValue']"},

0 commit comments

Comments
 (0)