changeset 118:2a63c8ec45cd

Irccd: fix some errors in util::format, #408
author David Demelier <markand@malikania.fr>
date Fri, 29 Apr 2016 14:19:30 +0200
parents 9b9b09543d2a
children b39573fc066e
files lib/irccd/util.cpp tests/util/main.cpp
diffstat 2 files changed, 64 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/lib/irccd/util.cpp	Fri Apr 29 10:08:17 2016 +0200
+++ b/lib/irccd/util.cpp	Fri Apr 29 14:19:30 2016 +0200
@@ -65,6 +65,11 @@
 	{ "reverse",	'\x16'	}
 };
 
+inline bool isReserved(char token) noexcept
+{
+	return token == '#' || token == '@' || token == '$';
+}
+
 std::string substituteDate(const std::string &text, const Substitution &params)
 {
 	std::ostringstream oss;
@@ -195,48 +200,58 @@
 
 std::string format(std::string text, const Substitution &params)
 {
-	std::ostringstream oss;
-
 	/*
 	 * Change the date format before anything else to avoid interpolation with keywords and
 	 * user input.
 	 */
 	text = substituteDate(text, params);
 
-	std::string::const_iterator it = text.begin();
-	std::string::const_iterator end = text.end();
+	std::ostringstream oss;
 
-	while (it != end) {
+	for (auto it = text.cbegin(), end = text.cend(); it != end; ) {
 		auto token = *it;
 
-		if (token == '#' || token == '@' || token == '$') {
-			++ it;
+		/* Is the current character a reserved token or not? */
+		if (!isReserved(token)) {
+			oss << *it++;
+			continue;
+		}
 
-			if (it == end) {
-				oss << token;
-				continue;
-			}
+		/* The token was at the end, just write it and return now. */
+		if (++it == end) {
+			oss << token;
+			continue;
+		}
+
+		/* The token is declaring a template variable, substitute it. */
+		if (*it == '{') {
+			oss << substitute(++it, end, token, params);
+			continue;
+		}
 
-			if (*it == '{') {
-				/* Do we have a variable? */
-				oss << substitute(++it, end, token, params);
-			} else if (*it == token) {
-				/* Need one for sure */
-				oss << token;
+		/*
+		 * If the next token is different from the previous one, just let the next iteration parse the string because
+		 * we can have the following constructs.
+		 *
+		 * "@#{var}" -> "@value"
+		 */
+		if (*it != token) {
+			oss << token;
+			continue;
+		}
 
-				/* Do we have a double token followed by a { for escaping? */
-				if (++it == end) {
-					continue;
-				}
-
-				if (*it != '{') {
-					oss << token;
-				}
-			} else {
-				oss << *it++;
-			}
-		} else {
-			oss << *it++;
+		/*
+		 * Write the token only if it's not a variable because at this step we may have the following
+		 * constructs.
+		 *
+		 * "##" -> "##"
+		 * "##hello" -> "##hello"
+		 * "##{hello}" -> "#{hello}"
+		 */
+		if (++it == end) {
+			oss << token << token;
+		} else if (*it == '{') {
+			oss << token;
 		}
 	}
 
--- a/tests/util/main.cpp	Fri Apr 29 10:08:17 2016 +0200
+++ b/tests/util/main.cpp	Fri Apr 29 14:19:30 2016 +0200
@@ -37,6 +37,25 @@
 	ASSERT_EQ(expected, result);
 }
 
+TEST(Format, escape)
+{
+	util::Substitution params;
+
+	params.keywords.emplace("target", "hello");
+
+	ASSERT_EQ("$@#", util::format("$@#"));
+	ASSERT_EQ(" $ @ # ", util::format(" $ @ # "));
+	ASSERT_EQ("#", util::format("#"));
+	ASSERT_EQ(" # ", util::format(" # "));
+	ASSERT_EQ("#@", util::format("#@"));
+	ASSERT_EQ("##", util::format("##"));
+	ASSERT_EQ("#!", util::format("#!"));
+	ASSERT_EQ("#{target}", util::format("##{target}"));
+	ASSERT_EQ("@hello", util::format("@#{target}", params));
+	ASSERT_EQ("hello#", util::format("#{target}#", params));
+	ASSERT_ANY_THROW(util::format("#{failure"));
+}
+
 TEST(Format, keywordSimple)
 {
 	util::Substitution params;