aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs
diff options
context:
space:
mode:
authorTobias Hunger <[email protected]>2015-10-01 15:03:18 +0200
committerTobias Hunger <[email protected]>2015-10-12 11:53:01 +0000
commit95b0dc9120dba73571ac6df9e5106f95b69b99d7 (patch)
tree40598171c0b86df32d5279ddb3b2360586ee25f9 /src/libs
parentb899684a89b2c6880cf346b3ea24d5705fdf42ff (diff)
Utils: Move template file processing from projectexplorer to utils
I want to use it e.g. for snippets and the TextEditor plugin may not depend on the ProjectExplorer, so the code has to move. This adds a dependency on QtQml to Utils, but that does not really matter since that is loaded into QtCreator anyway. Change-Id: Iada9f40b2966a1fc41631ab33da09812ad67d967 Reviewed-by: Daniel Teske <[email protected]>
Diffstat (limited to 'src/libs')
-rw-r--r--src/libs/utils/templateengine.cpp316
-rw-r--r--src/libs/utils/templateengine.h57
-rw-r--r--src/libs/utils/utils-lib.pri2
-rw-r--r--src/libs/utils/utils.pro2
-rw-r--r--src/libs/utils/utils.qbs2
5 files changed, 377 insertions, 2 deletions
diff --git a/src/libs/utils/templateengine.cpp b/src/libs/utils/templateengine.cpp
new file mode 100644
index 00000000000..c372276e7a5
--- /dev/null
+++ b/src/libs/utils/templateengine.cpp
@@ -0,0 +1,316 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms and
+** conditions see http://www.qt.io/terms-conditions. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include "templateengine.h"
+
+#include "qtcassert.h"
+
+#include <QJSEngine>
+#include <QStack>
+
+namespace Utils {
+
+namespace Internal {
+
+// Preprocessor: Conditional section type.
+enum PreprocessorSection {
+ IfSection,
+ ElsifSection,
+ ElseSection,
+ EndifSection,
+ OtherSection
+};
+
+// Preprocessor: Section stack entry containing nested '@if' section
+// state.
+class PreprocessStackEntry {
+public:
+ PreprocessStackEntry(PreprocessorSection section = OtherSection,
+ bool parentEnabled = true,
+ bool condition = false,
+ bool anyIfClauseMatched = false);
+
+ PreprocessorSection section;
+ bool parentEnabled;
+ bool condition; // Current 'if/elsif' section is enabled.
+ bool anyIfClauseMatched; // Determines if 'else' triggers
+};
+
+PreprocessStackEntry::PreprocessStackEntry(PreprocessorSection s, bool p, bool c, bool a) :
+ section(s), parentEnabled(p), condition(c), anyIfClauseMatched(a)
+{ }
+
+// Context for preprocessing.
+class PreprocessContext {
+public:
+ PreprocessContext();
+ bool process(const QString &in, QString *out, QString *errorMessage);
+
+private:
+ void reset();
+ PreprocessorSection preprocessorLine(const QString & in, QString *ifExpression) const;
+
+ mutable QRegExp m_ifPattern;
+ mutable QRegExp m_elsifPattern;
+ mutable QRegExp m_elsePattern;
+ mutable QRegExp m_endifPattern;
+
+ QStack<PreprocessStackEntry> m_sectionStack;
+ QJSEngine m_scriptEngine;
+};
+
+PreprocessContext::PreprocessContext() :
+ // Cut out expression for 'if/elsif '
+ m_ifPattern(QLatin1String("^([\\s]*@[\\s]*if[\\s]*)(.*)$")),
+ m_elsifPattern(QLatin1String("^([\\s]*@[\\s]*elsif[\\s]*)(.*)$")),
+ m_elsePattern(QLatin1String("^[\\s]*@[\\s]*else.*$")),
+ m_endifPattern(QLatin1String("^[\\s]*@[\\s]*endif.*$"))
+{
+ QTC_CHECK(m_ifPattern.isValid() && m_elsifPattern.isValid()
+ && m_elsePattern.isValid() && m_endifPattern.isValid());
+}
+
+void PreprocessContext::reset()
+{
+ m_sectionStack.clear();
+ // Add a default, enabled section.
+ m_sectionStack.push(PreprocessStackEntry(OtherSection, true, true));
+}
+
+// Determine type of line and return enumeration, cut out
+// expression for '@if/@elsif'.
+PreprocessorSection PreprocessContext::preprocessorLine(const QString &in,
+ QString *ifExpression) const
+{
+ if (m_ifPattern.exactMatch(in)) {
+ *ifExpression = m_ifPattern.cap(2).trimmed();
+ return IfSection;
+ }
+ if (m_elsifPattern.exactMatch(in)) {
+ *ifExpression = m_elsifPattern.cap(2).trimmed();
+ return ElsifSection;
+ }
+
+ ifExpression->clear();
+
+ if (m_elsePattern.exactMatch(in))
+ return ElseSection;
+ if (m_endifPattern.exactMatch(in))
+ return EndifSection;
+ return OtherSection;
+}
+
+static inline QString msgEmptyStack(int line)
+{
+ return QString::fromLatin1("Unmatched '@endif' at line %1.").arg(line);
+}
+
+bool PreprocessContext::process(const QString &in, QString *out, QString *errorMessage)
+{
+ out->clear();
+ if (in.isEmpty())
+ return true;
+
+ out->reserve(in.size());
+ reset();
+
+ const QChar newLine = QLatin1Char('\n');
+ const QStringList lines = in.split(newLine, QString::KeepEmptyParts);
+ const int lineCount = lines.size();
+ for (int l = 0; l < lineCount; l++) {
+ // Check for element of the stack (be it dummy, else something is wrong).
+ if (m_sectionStack.isEmpty()) {
+ if (errorMessage)
+ *errorMessage = msgEmptyStack(l);
+ return false;
+ }
+ QString expression;
+ bool expressionValue = false;
+ PreprocessStackEntry &top = m_sectionStack.back();
+
+ switch (preprocessorLine(lines.at(l), &expression)) {
+ case IfSection:
+ // '@If': Push new section
+ if (top.condition) {
+ if (!TemplateEngine::evaluateBooleanJavaScriptExpression(m_scriptEngine, expression,
+ &expressionValue, errorMessage)) {
+ if (errorMessage)
+ *errorMessage = QString::fromLatin1("Error in @if at %1: %2")
+ .arg(l + 1).arg(*errorMessage);
+ return false;
+ }
+ }
+ m_sectionStack.push(PreprocessStackEntry(IfSection,
+ top.condition, expressionValue, expressionValue));
+ break;
+ case ElsifSection: // '@elsif': Check condition.
+ if (top.section != IfSection && top.section != ElsifSection) {
+ if (errorMessage)
+ *errorMessage = QString::fromLatin1("No preceding @if found for @elsif at %1")
+ .arg(l + 1);
+ return false;
+ }
+ if (top.parentEnabled) {
+ if (!TemplateEngine::evaluateBooleanJavaScriptExpression(m_scriptEngine, expression,
+ &expressionValue, errorMessage)) {
+ if (errorMessage)
+ *errorMessage = QString::fromLatin1("Error in @elsif at %1: %2")
+ .arg(l + 1).arg(*errorMessage);
+ return false;
+ }
+ }
+ top.section = ElsifSection;
+ // ignore consecutive '@elsifs' once something matched
+ if (top.anyIfClauseMatched) {
+ top.condition = false;
+ } else {
+ if ( (top.condition = expressionValue) )
+ top.anyIfClauseMatched = true;
+ }
+ break;
+ case ElseSection: // '@else': Check condition.
+ if (top.section != IfSection && top.section != ElsifSection) {
+ if (errorMessage)
+ *errorMessage = QString::fromLatin1("No preceding @if/@elsif found for @else at %1")
+ .arg(l + 1);
+ return false;
+ }
+ expressionValue = top.parentEnabled && !top.anyIfClauseMatched;
+ top.section = ElseSection;
+ top.condition = expressionValue;
+ break;
+ case EndifSection: // '@endif': Discard section.
+ m_sectionStack.pop();
+ break;
+ case OtherSection: // Rest: Append according to current condition.
+ if (top.condition) {
+ out->append(lines.at(l));
+ out->append(newLine);
+ }
+ break;
+ } // switch section
+
+ } // for lines
+ return true;
+}
+
+} // namespace Internal
+
+bool TemplateEngine::preprocessText(const QString &in, QString *out, QString *errorMessage)
+{
+ Internal::PreprocessContext context;
+ return context.process(in, out, errorMessage);
+}
+
+QString TemplateEngine::processText(MacroExpander *expander, const QString &input,
+ QString *errorMessage)
+{
+ if (errorMessage)
+ errorMessage->clear();
+
+ if (input.isEmpty())
+ return input;
+
+ // Recursively expand macros:
+ QString in = input;
+ QString oldIn;
+ for (int i = 0; i < 5 && in != oldIn; ++i) {
+ oldIn = in;
+ in = expander->expand(oldIn);
+ }
+
+ QString out;
+ if (!preprocessText(in, &out, errorMessage))
+ return QString();
+
+ // Expand \n, \t and handle line continuation:
+ QString result;
+ result.reserve(out.count());
+ bool isEscaped = false;
+ for (int i = 0; i < out.count(); ++i) {
+ const QChar c = out.at(i);
+
+ if (isEscaped) {
+ if (c == QLatin1Char('n'))
+ result.append(QLatin1Char('\n'));
+ else if (c == QLatin1Char('t'))
+ result.append(QLatin1Char('\t'));
+ else if (c != QLatin1Char('\n'))
+ result.append(c);
+ isEscaped = false;
+ } else {
+ if (c == QLatin1Char('\\'))
+ isEscaped = true;
+ else
+ result.append(c);
+ }
+ }
+ return result;
+}
+
+bool TemplateEngine::evaluateBooleanJavaScriptExpression(QJSEngine &engine,
+ const QString &expression, bool *result,
+ QString *errorMessage)
+{
+ if (errorMessage)
+ errorMessage->clear();
+ if (result)
+ *result = false;
+ const QJSValue value = engine.evaluate(expression);
+ if (value.isError()) {
+ if (errorMessage)
+ *errorMessage = QString::fromLatin1("Error in \"%1\": %2")
+ .arg(expression, value.toString());
+ return false;
+ }
+ // Try to convert to bool, be that an int or whatever.
+ if (value.isBool()) {
+ if (result)
+ *result = value.toBool();
+ return true;
+ }
+ if (value.isNumber()) {
+ if (result)
+ *result = !qFuzzyCompare(value.toNumber(), 0);
+ return true;
+ }
+ if (value.isString()) {
+ if (result)
+ *result = !value.toString().isEmpty();
+ return true;
+ }
+ if (errorMessage)
+ *errorMessage = QString::fromLatin1("Cannot convert result of \"%1\" (\"%2\"to bool.")
+ .arg(expression, value.toString());
+
+ return false;
+}
+
+} // namespace Utils
diff --git a/src/libs/utils/templateengine.h b/src/libs/utils/templateengine.h
new file mode 100644
index 00000000000..a277841ca09
--- /dev/null
+++ b/src/libs/utils/templateengine.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms and
+** conditions see http://www.qt.io/terms-conditions. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, The Qt Company gives you certain additional
+** rights. These rights are described in The Qt Company LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#ifndef TEMPLATEENGINE_H
+#define TEMPLATEENGINE_H
+
+#include "utils_global.h"
+
+#include "macroexpander.h"
+
+#include <QString>
+
+QT_FORWARD_DECLARE_CLASS(QJSEngine);
+
+namespace Utils {
+
+class QTCREATOR_UTILS_EXPORT TemplateEngine {
+public:
+ static bool preprocessText(const QString &input, QString *output, QString *errorMessage);
+
+ static QString processText(MacroExpander *expander, const QString &input,
+ QString *errorMessage);
+
+ static bool evaluateBooleanJavaScriptExpression(QJSEngine &engine, const QString &expression,
+ bool *result, QString *errorMessage);
+};
+
+} // namespace Utils
+
+#endif // TEMPLATEENGINE_H
diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri
index a4b7ed13818..bb66f7b5a5a 100644
--- a/src/libs/utils/utils-lib.pri
+++ b/src/libs/utils/utils-lib.pri
@@ -16,6 +16,7 @@ SOURCES += $$PWD/environment.cpp \
$$PWD/shellcommandpage.cpp \
$$PWD/settingsselector.cpp \
$$PWD/stringutils.cpp \
+ $$PWD/templateengine.cpp \
$$PWD/textfieldcheckbox.cpp \
$$PWD/textfieldcombobox.cpp \
$$PWD/filesearch.cpp \
@@ -107,6 +108,7 @@ HEADERS += \
$$PWD/shellcommand.h \
$$PWD/shellcommandpage.h \
$$PWD/stringutils.h \
+ $$PWD/templateengine.h \
$$PWD/textfieldcheckbox.h \
$$PWD/textfieldcombobox.h \
$$PWD/filesearch.h \
diff --git a/src/libs/utils/utils.pro b/src/libs/utils/utils.pro
index cb28e4ad43e..d69ff0ff44b 100644
--- a/src/libs/utils/utils.pro
+++ b/src/libs/utils/utils.pro
@@ -1,4 +1,4 @@
-QT += gui network
+QT += gui network qml
include(../../qtcreatorlibrary.pri)
include(utils-lib.pri)
diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs
index 5b705a26bcf..c0e1879ee46 100644
--- a/src/libs/utils/utils.qbs
+++ b/src/libs/utils/utils.qbs
@@ -28,7 +28,7 @@ QtcLibrary {
cpp.frameworks: ["Foundation"]
}
- Depends { name: "Qt"; submodules: ["widgets", "network", "script", "concurrent"] }
+ Depends { name: "Qt"; submodules: ["gui", "network", "qml"] }
Depends { name: "app_version_header" }
files: [