diff --git a/share/qtcreator/templates/wizards/helloworld/project.pro b/share/qtcreator/templates/wizards/helloworld/project.pro index 4b791337a1b1ead7b4434b6b93954de0cb93cdc6..f33dcf1d99ea414860ed512cddb671d1d6c47acf 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 fe5c1fbaad0d0170c5d4d3f96de0fc7de58e8973..dfcf4e00aaee04e3d9a22b7dc13abd2c769453d4 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. <fielddescription>Hello world message:</fielddescription> <fielddescription xml:lang="de">Hallo-Welt-Nachricht:</fielddescription> </field> - <!-- Comment out network in profile according to user's wishes --> + <!-- Checkbox technique 1: Comment out network in profile according to user's wishes --> <field name="NETWORK"> <fieldcontrol class="QCheckBox" truevalue="" falsevalue="# "/> <fielddescription>Include network module</fielddescription> <fielddescription xml:lang="de">Netzwerk-Modul verwenden</fielddescription> </field> + <!-- Checkbox technique 2: Use preprocessor for profile according to user's wishes --> + <field name="SCRIPT"> + <fieldcontrol class="QCheckBox"/> + <fielddescription>Include script module</fielddescription> + <fielddescription xml:lang="de">Script-Modul verwenden</fielddescription> + </field> </fields> </wizard> diff --git a/src/plugins/projectexplorer/customwizard/customwizard.cpp b/src/plugins/projectexplorer/customwizard/customwizard.cpp index b9c8e7e18a749dd0fdbc599b89000fa48c0ad926..9ec1483b213f56035f5afb691f5bb60cc373ec7e 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 61475f3ae4170657f21b38dd870d6c7139f6f7fd..ac8bf2ea2601015aae5bd8eb4ee0a90556c2a35c 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 487c3aef373d1dbaea00b1396f778950cd2743b1..d821ed52037771f71748bf3b50c206c6266270f3 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 <coreplugin/mimedatabase.h> #include <coreplugin/icore.h> @@ -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 e5c26abf3f4f5879cdad684c7105a81ee7c4952e..5d92843f04e1839b9fa1e704c4a8b14e630922a1 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 0000000000000000000000000000000000000000..2d0a27904193c1dda6e109208857ff56a21a9db2 --- /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 <utils/qtcassert.h> + +#include <QtCore/QStringList> +#include <QtCore/QStack> +#include <QtCore/QRegExp> +#include <QtCore/QDebug> + +#include <QtScript/QScriptEngine> +#include <QtScript/QScriptValue> + +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<PreprocessStackEntry> 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 0000000000000000000000000000000000000000..be84a04f72c54aec16cc7c67f4cd54900cae6837 --- /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 <QtCore/QString> + +namespace ProjectExplorer { +namespace Internal { + +/* Preprocess a string using simple syntax: \code + +Text +@if <JavaScript-expression> +Bla... +@elsif <JavaScript-expression2> +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