Skip to content

Commit d3e2aef

Browse files
authored
Implement 'Replace' and 'Truncate' filters (#19)
* Implement 'Replace' and 'Truncate' filters
1 parent b514a5d commit d3e2aef

File tree

3 files changed

+108
-8
lines changed

3 files changed

+108
-8
lines changed

src/string_converter_filter.cpp

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <sstream>
99

1010
#include <boost/algorithm/string/trim_all.hpp>
11+
#include <boost/algorithm/string/replace.hpp>
1112

1213
namespace ba = boost::algorithm;
1314

@@ -52,13 +53,13 @@ template<typename Fn>
5253
struct GenericStringEncoder : public StringEncoder<GenericStringEncoder<Fn>>
5354
{
5455
GenericStringEncoder(Fn fn) : m_fn(std::move(fn)) {}
55-
56+
5657
template<typename CharT, typename AppendFn>
5758
void EncodeChar(CharT ch, AppendFn&& fn) const
5859
{
5960
m_fn(ch, std::forward<AppendFn>(fn));
60-
}
61-
61+
}
62+
6263
mutable Fn m_fn;
6364
};
6465

@@ -158,28 +159,48 @@ struct StringConverterImpl : public visitors::BaseVisitor<>
158159
const Fn& m_fn;
159160
};
160161

162+
template<typename CharT>
163+
struct SameStringGetter : public visitors::BaseVisitor<std::basic_string<CharT>>
164+
{
165+
using ResultString = std::basic_string<CharT>;
166+
using visitors::BaseVisitor<ResultString>::operator ();
167+
168+
ResultString operator()(const ResultString& str) const
169+
{
170+
return str;
171+
}
172+
};
173+
161174
template<template<typename> class Cvt = StringConverterImpl, typename Fn>
162175
auto ApplyConverter(const InternalValue& str, Fn&& fn)
163176
{
164177
return Apply<Cvt<Fn>>(str, std::forward<Fn>(fn));
165178
}
166179

180+
template<typename CharT>
181+
auto GetAsSameString(const std::basic_string<CharT>& s, const InternalValue& val)
182+
{
183+
return Apply<SameStringGetter<CharT>>(val);
184+
}
167185

168186
StringConverter::StringConverter(FilterParams params, StringConverter::Mode mode)
169187
: m_mode(mode)
170188
{
171189
switch (m_mode)
172190
{
173191
case ReplaceMode:
174-
ParseParams({{"old", true}, {"new", true}, {"count", false}}, params);
192+
ParseParams({{"old", true}, {"new", true}, {"count", false, static_cast<int64_t>(0)}}, params);
193+
break;
194+
case TruncateMode:
195+
ParseParams({{"length", false, static_cast<int64_t>(255)}, {"killwords", false, false}, {"end", false, std::string("...")}, {"leeway", false}}, params);
175196
break;
176197
}
177198
}
178199

179200
InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContext& context)
180201
{
181202
InternalValue result;
182-
203+
183204
auto isAlpha = ba::is_alpha();
184205
auto isAlNum = ba::is_alnum();
185206

@@ -199,7 +220,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex
199220
fn(std::toupper(ch, std::locale()));
200221
return;
201222
}
202-
223+
203224
isDelim = !isAlNum(ch);
204225
fn(ch);
205226
});
@@ -217,7 +238,7 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex
217238
isDelim = !isAlNum(ch);
218239
});
219240
result = wc;
220-
break;
241+
break;
221242
}
222243
case UpperMode:
223244
result = ApplyConverter<GenericStringEncoder>(baseVal, [&isAlpha](auto ch, auto&& fn) mutable {
@@ -236,6 +257,57 @@ InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContex
236257
});
237258
break;
238259
case ReplaceMode:
260+
result = ApplyConverter(baseVal, [this, &context](auto str) {
261+
auto oldStr = GetAsSameString(str, this->GetArgumentValue("old", context));
262+
auto newStr = GetAsSameString(str, this->GetArgumentValue("new", context));
263+
auto count = ConvertToInt(this->GetArgumentValue("count", context));
264+
if (count == 0)
265+
ba::replace_all(str, oldStr, newStr);
266+
else
267+
{
268+
for (int64_t n = 0; n < count; ++ n)
269+
ba::replace_first(str, oldStr, newStr);
270+
}
271+
return str;
272+
});
273+
break;
274+
case TruncateMode:
275+
result = ApplyConverter(baseVal, [this, &context, &isAlNum](auto str) {
276+
auto length = ConvertToInt(this->GetArgumentValue("length", context));
277+
auto killWords = ConvertToBool(this->GetArgumentValue("killwords", context));
278+
auto end = GetAsSameString(str, this->GetArgumentValue("end", context));
279+
auto leeway = ConvertToInt(this->GetArgumentValue("leeway", context), 5);
280+
if (str.size() <= length)
281+
return str;
282+
283+
if (killWords)
284+
{
285+
if (str.size() > (length + leeway))
286+
{
287+
str.erase(str.begin() + length, str.end());
288+
str += end;
289+
}
290+
return str;
291+
}
292+
293+
auto p = str.begin() + length;
294+
if (leeway != 0)
295+
{
296+
for (; leeway != 0 && p != str.end() && isAlNum(*p); -- leeway, ++ p);
297+
if (p == str.end())
298+
return str;
299+
}
300+
301+
if (isAlNum(*p))
302+
{
303+
for (; p != str.begin() && isAlNum(*p); -- p);
304+
}
305+
str.erase(p, str.end());
306+
ba::trim_right(str);
307+
str += end;
308+
309+
return str;
310+
});
239311
break;
240312
case UrlEncodeMode:
241313
result = Apply<UrlStringEncoder>(baseVal);

src/value_visitors.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,9 @@ struct BooleanEvaluator : BaseVisitor<bool>
756756

757757
struct IntegerEvaluator : public boost::static_visitor<int64_t>
758758
{
759+
IntegerEvaluator(int64_t def = 0) : m_def(def)
760+
{}
761+
759762
int64_t operator ()(int64_t val) const
760763
{
761764
return val;
@@ -771,8 +774,10 @@ struct IntegerEvaluator : public boost::static_visitor<int64_t>
771774
template<typename U>
772775
int64_t operator()(U&&) const
773776
{
774-
return 0;
777+
return m_def;
775778
}
779+
780+
int64_t m_def;
776781
};
777782

778783
#if 0
@@ -902,6 +907,11 @@ inline bool ConvertToBool(const InternalValue& val)
902907
return Apply<visitors::BooleanEvaluator>(val);
903908
}
904909

910+
inline int64_t ConvertToInt(const InternalValue& val, int64_t def = 0)
911+
{
912+
return Apply<visitors::IntegerEvaluator>(val, def);
913+
}
914+
905915
#if 0
906916
inline auto AsValueList(const InternalValue& val, InternalValue subAttr = InternalValue())
907917
{

test/filters_test.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,21 @@ INSTANTIATE_TEST_CASE_P(WordCount, FilterGenericTest, ::testing::Values(
317317
InputOutputPair{"'hello123ooo, world!' | wordcount", "2"}/*,
318318
InputOutputPair{"wstringValue | trim", "'hello world'"}*/
319319
));
320+
321+
INSTANTIATE_TEST_CASE_P(Replace, FilterGenericTest, ::testing::Values(
322+
InputOutputPair{"'Hello World' | replace('Hello', 'Goodbye') | pprint", "'Goodbye World'"},
323+
InputOutputPair{"'Hello World' | replace(old='l', new='L') | pprint", "'HeLLo WorLd'"},
324+
InputOutputPair{"'Hello World' | replace(old='l', new='L', 2) | pprint", "'HeLLo World'"},
325+
InputOutputPair{"'Hello World' | replace('l', 'L', count=1) | pprint", "'HeLlo World'"}
326+
));
327+
328+
INSTANTIATE_TEST_CASE_P(Truncate, FilterGenericTest, ::testing::Values(
329+
InputOutputPair{"'foo bar baz qux' | truncate(6, leeway=0) | pprint", "'foo...'"},
330+
InputOutputPair{"'foo bar baz qux' | truncate(6, true) | pprint", "'foo ba...'"},
331+
InputOutputPair{"'foo bar baz qux' | truncate(11, true) | pprint", "'foo bar baz qux'"},
332+
InputOutputPair{"'foo bar baz qux' | truncate(11, true, leeway=0) | pprint", "'foo bar baz...'"},
333+
InputOutputPair{"'foo bar baz qux' | truncate(9) | pprint", "'foo bar baz...'"},
334+
InputOutputPair{"'VeryVeryVeryLongWord' | truncate(3) | pprint", "'...'"},
335+
InputOutputPair{"'VeryVeryVeryLongWord' | truncate(16) | pprint", "'VeryVeryVeryLongWord'"},
336+
InputOutputPair{"'foo bar baz qux' | truncate(6, end=' >>', leeway=0) | pprint", "'foo >>'"}
337+
));

0 commit comments

Comments
 (0)