summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOrgad Shaneh <orgad.shaneh@audiocodes.com>2016-06-26 16:51:11 +0300
committerOrgad Shaneh <orgads@gmail.com>2016-07-15 13:26:41 +0000
commit2ec82a2a933bb5e6ea2ff33d8785e88f19194f99 (patch)
tree8d655e17eb5a786f23d0d4d61b2ce4d3f623c677
parent7e1b4ccebb7f6a0827c1937fd5f3c8fda0f39792 (diff)
downloadqt-creator-2ec82a2a933bb5e6ea2ff33d8785e88f19194f99.tar.gz
Utils: Support pattern substitution in macro expansion
Syntax is similar to bash substitution: %{variable/pattern/replacement} takes the expansion of variable, and replaces the regular expression pattern with replacement once. %{variable//pattern/replacement} replaces all occurrences. Capture groups are supported for both forms. Change-Id: I67ff91e2dad4dd8be81573ea446cd1093a05ccf6 Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com> Reviewed-by: hjk <hjk@qt.io>
-rw-r--r--src/libs/utils/stringutils.cpp45
-rw-r--r--tests/auto/utils/stringutils/tst_stringutils.cpp24
2 files changed, 67 insertions, 2 deletions
diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp
index aa7a45bb73..274a8bfb7c 100644
--- a/src/libs/utils/stringutils.cpp
+++ b/src/libs/utils/stringutils.cpp
@@ -30,6 +30,7 @@
#include <utils/algorithm.h>
#include <QDir>
+#include <QRegularExpression>
#include <limits.h>
@@ -118,18 +119,34 @@ QTCREATOR_UTILS_EXPORT QString withTildeHomePath(const QString &path)
return outPath;
}
+static bool validateVarName(const QString &varName)
+{
+ return !varName.startsWith("JS:");
+}
+
bool AbstractMacroExpander::expandNestedMacros(const QString &str, int *pos, QString *ret)
{
QString varName;
+ QString pattern, replace;
+ QString *currArg = &varName;
QChar prev;
QChar c;
+ bool replaceAll = false;
int i = *pos;
int strLen = str.length();
varName.reserve(strLen - i);
for (; i < strLen; prev = c) {
c = str.at(i++);
- if (c == '}') {
+ if (c == '\\' && i < strLen && validateVarName(varName)) {
+ c = str.at(i++);
+ // For the replacement, do not skip the escape sequence when followed by a digit.
+ // This is needed for enabling convenient capture group replacement,
+ // like %{var/(.)(.)/\2\1}, without escaping the placeholders.
+ if (currArg == &replace && c.isDigit())
+ *currArg += '\\';
+ *currArg += c;
+ } else if (c == '}') {
if (varName.isEmpty()) { // replace "%{}" with "%"
*ret = QString('%');
*pos = i;
@@ -137,6 +154,22 @@ bool AbstractMacroExpander::expandNestedMacros(const QString &str, int *pos, QSt
}
if (resolveMacro(varName, ret)) {
*pos = i;
+ if (!pattern.isEmpty() && currArg == &replace) {
+ const QRegularExpression regexp(pattern);
+ if (regexp.isValid()) {
+ if (replaceAll) {
+ ret->replace(regexp, replace);
+ } else {
+ // There isn't an API for replacing once...
+ const QRegularExpressionMatch match = regexp.match(*ret);
+ if (match.hasMatch()) {
+ *ret = ret->left(match.capturedStart(0))
+ + match.captured(0).replace(regexp, replace)
+ + ret->mid(match.capturedEnd(0));
+ }
+ }
+ }
+ }
return true;
}
return false;
@@ -145,8 +178,16 @@ bool AbstractMacroExpander::expandNestedMacros(const QString &str, int *pos, QSt
return false;
varName.chop(1);
varName += ret;
+ } else if (currArg == &varName && c == '/' && validateVarName(varName)) {
+ currArg = &pattern;
+ if (i < strLen && str.at(i) == '/') {
+ ++i;
+ replaceAll = true;
+ }
+ } else if (currArg == &pattern && c == '/') {
+ currArg = &replace;
} else {
- varName += c;
+ *currArg += c;
}
}
return false;
diff --git a/tests/auto/utils/stringutils/tst_stringutils.cpp b/tests/auto/utils/stringutils/tst_stringutils.cpp
index 314bf1391d..972d0b35d5 100644
--- a/tests/auto/utils/stringutils/tst_stringutils.cpp
+++ b/tests/auto/utils/stringutils/tst_stringutils.cpp
@@ -50,6 +50,18 @@ public:
*ret = QLatin1String("bar");
return true;
}
+ if (name == "slash") {
+ *ret = "foo/bar";
+ return true;
+ }
+ if (name == "sl/sh") {
+ *ret = "slash";
+ return true;
+ }
+ if (name == "JS:foo") {
+ *ret = "bar";
+ return true;
+ }
return false;
}
};
@@ -129,6 +141,18 @@ void tst_StringUtils::testMacroExpander_data()
{ "%{%{a}}}post", "ho}post" },
{ "%{hi%{a}}", "bar" },
{ "%{hi%{%{foo}}}", "bar" },
+ { "%{hihi/b/c}", "car" },
+ { "%{hihi/a/}", "br" }, // empty replacement
+ { "%{hihi/b}", "bar" }, // incomplete substitution
+ { "%{hihi/./c}", "car" },
+ { "%{hihi//./c}", "ccc" },
+ { "%{hihi/(.)(.)r/\\2\\1c}", "abc" }, // no escape for capture groups
+ { "%{hihi/b/c/d}", "c/dar" },
+ { "%{hihi/a/e{\\}e}", "be{}er" }, // escape closing brace
+ { "%{slash/o\\/b/ol's c}", "fool's car" },
+ { "%{sl\\/sh/(.)(a)(.)/\\2\\1\\3as}", "salsash" }, // escape in variable name
+ { "%{JS:foo/b/c}", "%{JS:foo/b/c}" }, // No replacement for JS (all considered varName)
+ { "%{%{a}%{a}/b/c}", "car" },
};
for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++)