diff --git a/share/qtcreator/templates/wizards/helloworld/project.pro b/share/qtcreator/templates/wizards/helloworld/project.pro
index 4b791337a1b..f33dcf1d99e 100644
--- a/share/qtcreator/templates/wizards/helloworld/project.pro
+++ b/share/qtcreator/templates/wizards/helloworld/project.pro
@@ -1,6 +1,8 @@
QT = core
%NETWORK%QT += network
-
+@if "%SCRIPT%" == "true"
+QT += script
+@endif
CONFIG += console
CONFIG -= app_bundle
diff --git a/share/qtcreator/templates/wizards/helloworld/wizard_sample.xml b/share/qtcreator/templates/wizards/helloworld/wizard_sample.xml
index fe5c1fbaad0..dfcf4e00aae 100644
--- a/share/qtcreator/templates/wizards/helloworld/wizard_sample.xml
+++ b/share/qtcreator/templates/wizards/helloworld/wizard_sample.xml
@@ -55,11 +55,17 @@ leave room for the Qt 4 target page.
Hello world message:
Hallo-Welt-Nachricht:
-
+
Include network module
Netzwerk-Modul verwenden
+
+
+
+ Include script module
+ Script-Modul verwenden
+
diff --git a/src/plugins/projectexplorer/customwizard/customwizard.cpp b/src/plugins/projectexplorer/customwizard/customwizard.cpp
index b9c8e7e18a7..9ec1483b213 100644
--- a/src/plugins/projectexplorer/customwizard/customwizard.cpp
+++ b/src/plugins/projectexplorer/customwizard/customwizard.cpp
@@ -155,11 +155,10 @@ static inline bool createFile(Internal::CustomWizardFile cwFile,
return false;
}
// Field replacement on contents
- QString contents = QString::fromLocal8Bit(file.readAll());
- if (!contents.isEmpty() && !fm.isEmpty())
- Internal::CustomWizardContext::replaceFields(fm, &contents);
+ const QString contentsIn = QString::fromLocal8Bit(file.readAll());
+
Core::GeneratedFile generatedFile;
- generatedFile.setContents(contents);
+ generatedFile.setContents(Internal::CustomWizardContext::processFile(fm, contentsIn));
generatedFile.setPath(targetPath);
Core::GeneratedFile::Attributes attributes = 0;
if (cwFile.openEditor)
diff --git a/src/plugins/projectexplorer/customwizard/customwizard.pri b/src/plugins/projectexplorer/customwizard/customwizard.pri
index 61475f3ae41..ac8bf2ea260 100644
--- a/src/plugins/projectexplorer/customwizard/customwizard.pri
+++ b/src/plugins/projectexplorer/customwizard/customwizard.pri
@@ -1,7 +1,9 @@
INCLUDEPATH *= $$PWD
HEADERS += $$PWD/customwizard.h \
$$PWD/customwizardparameters.h \
- $$PWD/customwizardpage.h
+ $$PWD/customwizardpage.h \
+ customwizard/customwizardpreprocessor.h
SOURCES += $$PWD/customwizard.cpp \
$$PWD/customwizardparameters.cpp \
- $$PWD/customwizardpage.cpp
+ $$PWD/customwizardpage.cpp \
+ customwizard/customwizardpreprocessor.cpp
diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
index 487c3aef373..d821ed52037 100644
--- a/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
+++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.cpp
@@ -28,6 +28,7 @@
**************************************************************************/
#include "customwizardparameters.h"
+#include "customwizardpreprocessor.h"
#include
#include
@@ -644,5 +645,24 @@ void CustomWizardContext::reset()
mdb->preferredSuffixByType(QLatin1String(CppTools::Constants::CPP_HEADER_MIMETYPE)));
}
+QString CustomWizardContext::processFile(const FieldReplacementMap &fm, QString in)
+{
+
+ if (in.isEmpty())
+ return in;
+
+ if (!fm.isEmpty())
+ replaceFields(fm, &in);
+
+ QString out;
+ QString errorMessage;
+ if (!customWizardPreprocess(in, &out, &errorMessage)) {
+ qWarning("Error preprocessing custom widget file: %s\nFile:\n%s",
+ qPrintable(errorMessage), qPrintable(in));
+ return QString();
+ }
+ return out;
+}
+
} // namespace Internal
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/customwizard/customwizardparameters.h b/src/plugins/projectexplorer/customwizard/customwizardparameters.h
index e5c26abf3f4..5d92843f04e 100644
--- a/src/plugins/projectexplorer/customwizard/customwizardparameters.h
+++ b/src/plugins/projectexplorer/customwizard/customwizardparameters.h
@@ -105,6 +105,7 @@ struct CustomWizardContext {
// %Field:l% -> lower case replacement, 'u' upper case,
// 'c' capitalize first letter.
static void replaceFields(const FieldReplacementMap &fm, QString *s);
+ static QString processFile(const FieldReplacementMap &fm, QString in);
FieldReplacementMap baseReplacements;
};
diff --git a/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.cpp b/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.cpp
new file mode 100644
index 00000000000..2d0a2790419
--- /dev/null
+++ b/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.cpp
@@ -0,0 +1,268 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "customwizardpreprocessor.h"
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+namespace ProjectExplorer {
+namespace Internal {
+
+enum { debug = 0 };
+
+// Preprocessor: Conditional section type.
+enum PreprocessorSection {
+ IfSection,
+ ElsifSection,
+ ElseSection,
+ EndifSection,
+ OtherSection
+};
+
+// Preprocessor: Section stack entry containing nested '@if' section
+// state.
+struct PreprocessStackEntry {
+ 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();
+ bool evaluateExpression(const QString &expression, bool *result, QString *errorMessage);
+ PreprocessorSection preprocessorLine(const QString & in, QString *ifExpression) const;
+
+ mutable QRegExp m_ifPattern;
+ mutable QRegExp m_elsifPattern;
+ const QRegExp m_elsePattern;
+ const QRegExp m_endifPattern;
+
+ QStack m_sectionStack;
+ QScriptEngine 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_ASSERT(m_ifPattern.isValid() && m_elsifPattern.isValid()
+ && m_elsePattern.isValid() &&m_endifPattern.isValid(), return);
+}
+
+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;
+}
+
+// Evaluate an expression within an 'if'/'elsif' to a bool via QScript
+bool PreprocessContext::evaluateExpression(const QString &expression,
+ bool *result,
+ QString *errorMessage)
+{
+ errorMessage->clear();
+ *result = false;
+ m_scriptEngine.clearExceptions();
+ const QScriptValue value = m_scriptEngine.evaluate(expression);
+ if (m_scriptEngine.hasUncaughtException()) {
+ *errorMessage = QString::fromLatin1("Error in '%1': %2").
+ arg(expression, m_scriptEngine.uncaughtException().toString());
+ return false;
+ }
+ // Try to convert to bool, be that an int or whatever.
+ if (value.isBool()) {
+ *result = value.toBool();
+ return true;
+ }
+ if (value.isNumber()) {
+ *result = !qFuzzyCompare(value.toNumber(), 0);
+ return true;
+ }
+ if (value.isString()) {
+ *result = !value.toString().isEmpty();
+ return true;
+ }
+ *errorMessage = QString::fromLatin1("Cannot convert result of '%1' ('%2'to bool.").
+ arg(expression, value.toString());
+ return false;
+}
+
+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()) {
+ *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.parentEnabled) {
+ if (!evaluateExpression(expression, &expressionValue, errorMessage)) {
+ *errorMessage = QString::fromLatin1("Error in @if at %1: %2").
+ arg(l + 1).arg(*errorMessage);
+ return false;
+ }
+ }
+ if (debug)
+ qDebug("'%s' : expr='%s' -> %d", qPrintable(lines.at(l)), qPrintable(expression), expressionValue);
+ m_sectionStack.push(PreprocessStackEntry(IfSection,
+ top.condition, expressionValue, expressionValue));
+ break;
+ case ElsifSection: // '@elsif': Check condition.
+ if (top.section != IfSection && top.section != ElsifSection) {
+ *errorMessage = QString::fromLatin1("No preceding @if found for @elsif at %1").
+ arg(l + 1);
+ return false;
+ }
+ if (top.parentEnabled) {
+ if (!evaluateExpression(expression, &expressionValue, errorMessage)) {
+ *errorMessage = QString::fromLatin1("Error in @elsif at %1: %2").
+ arg(l + 1).arg(*errorMessage);
+ return false;
+ }
+ }
+ if (debug)
+ qDebug("'%s' : expr='%s' -> %d", qPrintable(lines.at(l)), qPrintable(expression), expressionValue);
+ 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) {
+ *errorMessage = QString::fromLatin1("No preceding @if/@elsif found for @else at %1").
+ arg(l + 1);
+ return false;
+ }
+ expressionValue = top.parentEnabled && !top.anyIfClauseMatched;
+ if (debug)
+ qDebug("%s -> %d", qPrintable(lines.at(l)), expressionValue);
+ 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;
+}
+
+bool customWizardPreprocess(const QString &in, QString *out, QString *errorMessage)
+{
+ PreprocessContext context;
+ return context.process(in, out, errorMessage);
+}
+
+} // namespace Internal
+} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.h b/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.h
new file mode 100644
index 00000000000..be84a04f72c
--- /dev/null
+++ b/src/plugins/projectexplorer/customwizard/customwizardpreprocessor.h
@@ -0,0 +1,58 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** 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 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef CUSTOMWIZARDPREPROCESSOR_H
+#define CUSTOMWIZARDPREPROCESSOR_H
+
+#include
+
+namespace ProjectExplorer {
+namespace Internal {
+
+/* Preprocess a string using simple syntax: \code
+
+Text
+@if
+Bla...
+@elsif
+Blup
+@endif
+\endcode
+
+* JavaScript-expressions must evaluate to integers or boolean, like
+* '2 == 1 + 1', '"a" == "a"'. The variables of the custom wizard will be
+* expanded beforem, so , "%VAR%" should be used for strings and %VAR% for integers.
+*/
+
+bool customWizardPreprocess(const QString &in, QString *out, QString *errorMessage);
+
+} // namespace Internal
+} // namespace ProjectExplorer
+
+#endif // CUSTOMWIZARDPREPROCESSOR_H