diff options
author | Orgad Shaneh <orgad.shaneh@audiocodes.com> | 2016-06-26 16:51:11 +0300 |
---|---|---|
committer | Orgad Shaneh <orgads@gmail.com> | 2016-07-15 13:26:41 +0000 |
commit | 2ec82a2a933bb5e6ea2ff33d8785e88f19194f99 (patch) | |
tree | 8d655e17eb5a786f23d0d4d61b2ce4d3f623c677 | |
parent | 7e1b4ccebb7f6a0827c1937fd5f3c8fda0f39792 (diff) | |
download | qt-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.cpp | 45 | ||||
-rw-r--r-- | tests/auto/utils/stringutils/tst_stringutils.cpp | 24 |
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++) |