OSDN Git Service

actually edit pro files instead of rewriting them from the "AST"
authorOswald Buddenhagen <oswald.buddenhagen@nokia.com>
Thu, 14 Jan 2010 21:58:55 +0000 (22:58 +0100)
committerOswald Buddenhagen <oswald.buddenhagen@nokia.com>
Mon, 25 Jan 2010 15:53:56 +0000 (16:53 +0100)
that way the file formatting is better preserved.

.gitignore
src/plugins/qt4projectmanager/qt4nodes.cpp
src/plugins/qt4projectmanager/qt4nodes.h
src/shared/proparser/prowriter.cpp
src/shared/proparser/prowriter.h
tests/auto/profilewriter/main.cpp [new file with mode: 0644]
tests/auto/profilewriter/profilewriter.pro [new file with mode: 0644]

index 15a20a7..47abad5 100644 (file)
@@ -76,3 +76,4 @@ tests/manual/cplusplus/cplusplus0
 tests/auto/qml/qmldesigner/bauhaustests/tst_bauhaus
 tests/auto/qml/qmldesigner/coretests/tst_qmldesigner_core
 tests/auto/qml/qmldesigner/propertyeditortests/tst_propertyeditor
+tests/auto/profilewriter/tst_profilewriter
index 8c41d62..d8715d5 100644 (file)
@@ -606,23 +606,6 @@ bool Qt4PriFileNode::saveModifiedEditors(const QString &path)
     return true;
 }
 
-static void findProVariables(ProBlock *block, const QStringList &vars,
-                             QList<ProVariable *> *proVars)
-{
-    foreach (ProItem *item, block->items()) {
-        if (item->kind() == ProItem::BlockKind) {
-            ProBlock *subBlock = static_cast<ProBlock *>(item);
-            if (subBlock->blockKind() == ProBlock::VariableKind) {
-                ProVariable *proVar = static_cast<ProVariable*>(subBlock);
-                if (vars.contains(proVar->variable()))
-                    *proVars << proVar;
-            } else {
-                findProVariables(subBlock, vars, proVars);
-            }
-        }
-    }
-}
-
 void Qt4PriFileNode::changeFiles(const FileType fileType,
                                  const QStringList &filePaths,
                                  QStringList *notChanged,
@@ -637,101 +620,67 @@ void Qt4PriFileNode::changeFiles(const FileType fileType,
     if (!saveModifiedEditors(m_projectFilePath))
         return;
 
-    ProFileReader *reader = m_project->createProFileReader(m_qt4ProFileNode);
-    ProFile *includeFile = reader->parsedProFile(m_projectFilePath);
-    m_project->destroyProFileReader(reader);
-    if (!includeFile) {
-        m_project->proFileParseError(tr("Error while changing pro file %1.").arg(m_projectFilePath));
-        return;
+    QStringList lines;
+    ProFile *includeFile;
+    {
+        QString contents;
+        {
+            QFile qfile(m_projectFilePath);
+            if (qfile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+                contents = QString::fromLatin1(qfile.readAll()); // yes, really latin1
+                qfile.close();
+                lines = contents.split(QLatin1Char('\n'));
+                while (lines.last().isEmpty())
+                    lines.removeLast();
+            } else {
+                m_project->proFileParseError(tr("Error while reading PRO file %1: %2")
+                                             .arg(m_projectFilePath, qfile.errorString()));
+                return;
+            }
+        }
+
+        ProFileReader *reader = m_project->createProFileReader(m_qt4ProFileNode);
+        includeFile = reader->parsedProFile(m_projectFilePath, contents);
+        m_project->destroyProFileReader(reader);
     }
 
     const QStringList vars = varNames(fileType);
     QDir priFileDir = QDir(m_qt4ProFileNode->m_projectDir);
 
     if (change == AddToProFile) {
-        ProVariable *proVar = 0;
-
-        // Check if variable item exists as child of root item
-        foreach (ProItem *item, includeFile->items()) {
-            if (item->kind() == ProItem::BlockKind) {
-                ProBlock *block = static_cast<ProBlock *>(item);
-                if (block->blockKind() == ProBlock::VariableKind) {
-                    proVar = static_cast<ProVariable*>(block);
-                    if (vars.contains(proVar->variable())
-                        && proVar->variableOperator() != ProVariable::RemoveOperator
-                        && proVar->variableOperator() != ProVariable::ReplaceOperator)
-                        break;
-                    proVar = 0;
-                }
-            }
-        }
-
-        if (!proVar) {
-            // Create & append new variable item
-
-            // TODO: This will always store e.g. a source file in SOURCES and not OBJECTIVE_SOURCES
-            proVar = new ProVariable(vars.first(), includeFile);
-            proVar->setVariableOperator(ProVariable::AddOperator);
-            includeFile->appendItem(proVar);
-        }
-
-        const QString &proFilePath = includeFile->fileName();
-        foreach (const QString &filePath, filePaths) {
-            if (filePath == proFilePath)
-                continue;
-            const QString &relativeFilePath = priFileDir.relativeFilePath(filePath);
-            proVar->appendItem(new ProValue(relativeFilePath, proVar));
-            notChanged->removeOne(filePath);
-        }
+        ProWriter::addFiles(includeFile, &lines, priFileDir, filePaths, vars);
+        notChanged->clear();
     } else { // RemoveFromProFile
-        QList<ProVariable *> proVars;
-        findProVariables(includeFile, vars, &proVars);
-
-        QStringList relativeFilePaths;
-        foreach (const QString &absoluteFilePath, filePaths)
-            relativeFilePaths << priFileDir.relativeFilePath(absoluteFilePath);
-
-        foreach (ProVariable *proVar, proVars) {
-            if (proVar->variableOperator() != ProVariable::RemoveOperator
-                && proVar->variableOperator() != ProVariable::ReplaceOperator) {
-                QList<ProItem *> values = proVar->items();
-                for (int i = values.count(); --i >= 0; ) {
-                    ProItem *item = values.at(i);
-                    if (item->kind() == ProItem::ValueKind) {
-                        ProValue *val = static_cast<ProValue *>(item);
-                        if (relativeFilePaths.contains(val->value())) {
-                            notChanged->removeOne(priFileDir.absoluteFilePath(val->value()));
-                            delete values.takeAt(i);
-                        }
-                    }
-                }
-                proVar->setItems(values);
-            }
-        }
+        *notChanged = ProWriter::removeFiles(includeFile, &lines, priFileDir, filePaths, vars);
     }
 
     // save file
-    save(includeFile);
+    save(lines);
 
     includeFile->deref();
 }
 
-void Qt4PriFileNode::save(ProFile *includeFile)
+void Qt4PriFileNode::save(const QStringList &lines)
 {
     Core::ICore *core = Core::ICore::instance();
     Core::FileManager *fileManager = core->fileManager();
-    QList<Core::IFile *> allFileHandles = fileManager->managedFiles(includeFile->fileName());
+    QList<Core::IFile *> allFileHandles = fileManager->managedFiles(m_projectFilePath);
     Core::IFile *modifiedFileHandle = 0;
     foreach (Core::IFile *file, allFileHandles)
-        if (file->fileName() == includeFile->fileName())
+        if (file->fileName() == m_projectFilePath)
             modifiedFileHandle = file;
 
     if (modifiedFileHandle)
         fileManager->blockFileChange(modifiedFileHandle);
-    ProWriter pw;
-    const bool ok = pw.write(includeFile, includeFile->fileName());
-    Q_UNUSED(ok)
-    m_project->qt4ProjectManager()->notifyChanged(includeFile->fileName());
+    QFile qfile(m_projectFilePath);
+    if (qfile.open(QIODevice::WriteOnly | QIODevice::Text)) {
+        foreach (const QString &str, lines) {
+            qfile.write(str.toLatin1()); // yes, really latin1
+            qfile.write("\n");
+        }
+        qfile.close();
+    }
+    m_project->qt4ProjectManager()->notifyChanged(m_projectFilePath);
     if (modifiedFileHandle)
         fileManager->unblockFileChange(modifiedFileHandle);
 
index e03a4bf..d26787f 100644 (file)
@@ -174,7 +174,7 @@ private slots:
     void scheduleUpdate();
 
 private:
-    void save(ProFile *includeFile);
+    void save(const QStringList &lines);
     bool priFileWritable(const QString &path);
     bool saveModifiedEditors(const QString &path);
 
index 39236c0..5e0539a 100644 (file)
 #include "proitems.h"
 #include "prowriter.h"
 
-#include <QtCore/QFile>
+#include <QtCore/QDir>
+#include <QtCore/QPair>
 
 using namespace Qt4ProjectManager::Internal;
 
-bool ProWriter::write(ProFile *profile, const QString &fileName)
+void ProWriter::addFiles(ProFile *profile, QStringList *lines,
+                         const QDir &proFileDir, const QStringList &filePaths,
+                         const QStringList &vars)
 {
-    QFile data(fileName);
-    if (!data.open(QFile::WriteOnly|QFile::Text))
-         return false;
-
-    m_writeState = 0;
-    m_comment.clear();
-    m_out.setDevice(&data);
-    writeItem(profile, QString());
-    data.close();
-    
-    return true;
-}
-
-QString ProWriter::contents(ProFile *profile)
-{
-    QString result;
-
-    m_writeState = 0;
-    m_comment.clear();
-    m_out.setString(&result, QIODevice::WriteOnly);
-    writeItem(profile, QString());
-
-    return result;
-}
-
-QString ProWriter::fixComment(const QString &comment, const QString &indent) const
-{
-    QString result = comment;
-    result = result.replace(QLatin1Char('\n'), 
-        QLatin1Char('\n') + indent + QLatin1String("# "));
-    return QLatin1String("# ") + result;
-}
-
-void ProWriter::writeValue(ProValue *value, const QString &indent)
-{
-    if (m_writeState & NewLine) {
-        m_out << indent << QLatin1String("    ");
-        m_writeState &= ~NewLine;
-    }
-    
-    m_out << value->value();
-
-    if (!(m_writeState & LastItem))
-        m_out << QLatin1String(" \\");
-
-    if (!value->comment().isEmpty())
-        m_out << QLatin1Char(' ') << fixComment(value->comment(), indent);
-
-    m_out << endl;
-    m_writeState |= NewLine;
-}
-
-void ProWriter::writeOther(ProItem *item, const QString &indent)
-{
-    if (m_writeState & NewLine) {
-        m_out << indent;
-        m_writeState &= ~NewLine;
-    }
-
-    if (item->kind() == ProItem::FunctionKind) {
-        ProFunction *v = static_cast<ProFunction*>(item);
-        m_out << v->text();
-    } else  if (item->kind() == ProItem::ConditionKind) {
-        ProCondition *v = static_cast<ProCondition*>(item);
-        m_out << v->text();
-    } else if (item->kind() == ProItem::OperatorKind) {
-        ProOperator *v = static_cast<ProOperator*>(item);
-        if (v->operatorKind() == ProOperator::OrOperator)
-            m_out << QLatin1Char('|');
-        else
-            m_out << QLatin1Char('!');
+    // Check if variable item exists as child of root item
+    foreach (ProItem *item, profile->items()) {
+        if (item->kind() == ProItem::BlockKind) {
+            ProBlock *block = static_cast<ProBlock *>(item);
+            if (block->blockKind() == ProBlock::VariableKind) {
+                ProVariable *proVar = static_cast<ProVariable*>(block);
+                if (vars.contains(proVar->variable())
+                    && proVar->variableOperator() != ProVariable::RemoveOperator
+                    && proVar->variableOperator() != ProVariable::ReplaceOperator) {
+
+                    int lineNo = proVar->lineNumber() - 1;
+                    for (; lineNo < lines->count(); lineNo++) {
+                        QString line = lines->at(lineNo);
+                        int idx = line.indexOf(QLatin1Char('#'));
+                        if (idx >= 0)
+                            line.truncate(idx);
+                        while (line.endsWith(QLatin1Char(' ')) || line.endsWith(QLatin1Char('\t')))
+                            line.chop(1);
+                        if (line.isEmpty()) {
+                            if (idx >= 0)
+                                continue;
+                            break;
+                        }
+                        if (!line.endsWith(QLatin1Char('\\'))) {
+                            (*lines)[lineNo].insert(line.length(), QLatin1String(" \\"));
+                            lineNo++;
+                            break;
+                        }
+                    }
+                    QString added;
+                    foreach (const QString &filePath, filePaths)
+                        added += QLatin1String("    ") + proFileDir.relativeFilePath(filePath)
+                                 + QLatin1String(" \\\n");
+                    added.chop(3);
+                    lines->insert(lineNo, added);
+                    return;
+                }
+            }
+        }
     }
 
-    if (!item->comment().isEmpty()) {
-        if (!m_comment.isEmpty())
-            m_comment += QLatin1Char('\n');
-        m_comment += item->comment();
-    }
+    // Create & append new variable item
+    QString added = QLatin1Char('\n') + vars.first() + QLatin1String(" +=");
+    foreach (const QString &filePath, filePaths)
+        added += QLatin1String(" \\\n    ") + proFileDir.relativeFilePath(filePath);
+    *lines << added;
 }
 
-void ProWriter::writeBlock(ProBlock *block, const QString &indent)
+static void findProVariables(ProBlock *block, const QStringList &vars,
+                             QList<ProVariable *> *proVars)
 {
-    if (m_writeState & NewLine) {
-        m_out << indent;
-        m_writeState &= ~NewLine;
-    }
-
-    if (!block->comment().isEmpty()) {
-        if (!(m_writeState & FirstItem))
-            m_out << endl << indent;
-        m_out << fixComment(block->comment(), indent) << endl << indent;
-    }
-
-    QString newindent = indent;
-    if (block->blockKind() & ProBlock::VariableKind) {
-        ProVariable *v = static_cast<ProVariable*>(block);
-        m_out << v->variable();
-        switch (v->variableOperator()) {
-            case ProVariable::AddOperator:
-                m_out << QLatin1String(" += "); break;
-            case ProVariable::RemoveOperator:
-                m_out << QLatin1String(" -= "); break;
-            case ProVariable::ReplaceOperator:
-                m_out << QLatin1String(" ~= "); break;
-            case ProVariable::SetOperator:
-                m_out << QLatin1String(" = "); break;
-            case ProVariable::UniqueAddOperator:
-                m_out << QLatin1String(" *= "); break;
-        }
-    } else if (block->blockKind() & ProBlock::ScopeContentsKind) {
-        if (block->items().count() > 1) {
-            newindent = indent + QLatin1String("    ");
-            m_out << QLatin1String(" { ");
-            if (!m_comment.isEmpty()) {
-                m_out << fixComment(m_comment, indent);
-                m_comment.clear();
+    foreach (ProItem *item, block->items()) {
+        if (item->kind() == ProItem::BlockKind) {
+            ProBlock *subBlock = static_cast<ProBlock *>(item);
+            if (subBlock->blockKind() == ProBlock::VariableKind) {
+                ProVariable *proVar = static_cast<ProVariable*>(subBlock);
+                if (vars.contains(proVar->variable()))
+                    *proVars << proVar;
+            } else {
+                findProVariables(subBlock, vars, proVars);
             }
-            m_out << endl;
-            m_writeState |= NewLine;
-        } else {
-            m_out << QLatin1Char(':');
-        }
-    }
-
-    QList<ProItem*> items = block->items();
-    for (int i = 0; i < items.count(); ++i) {
-        m_writeState &= ~LastItem;
-        m_writeState &= ~FirstItem;
-        if (i == 0)
-            m_writeState |= FirstItem;
-        if (i == items.count() - 1)
-            m_writeState |= LastItem;
-        writeItem(items.at(i), newindent);
-    }
-
-    if ((block->blockKind() & ProBlock::ScopeContentsKind) && (block->items().count() > 1)) {
-        if (m_writeState & NewLine) {
-            m_out << indent;
-            m_writeState &= ~NewLine;
         }
-        m_out << QLatin1Char('}');
-    }
-
-    if (!m_comment.isEmpty()) {
-        m_out << fixComment(m_comment, indent);
-        m_out << endl;
-        m_writeState |= NewLine;
-        m_comment.clear();
-    }
-
-    if (!(m_writeState & NewLine)) {
-        m_out << endl;
-        m_writeState |= NewLine;
     }
 }
 
-void ProWriter::writeItem(ProItem *item, const QString &indent)
+QStringList ProWriter::removeFiles(ProFile *profile, QStringList *lines,
+                                   const QDir &proFileDir, const QStringList &filePaths,
+                                   const QStringList &vars)
 {
-    if (item->kind() == ProItem::ValueKind) {
-        writeValue(static_cast<ProValue*>(item), indent);
-    } else if (item->kind() == ProItem::BlockKind) {
-        writeBlock(static_cast<ProBlock*>(item), indent);
-    } else {
-        writeOther(item, indent);
+    QStringList notChanged = filePaths;
+
+    QList<ProVariable *> proVars;
+    findProVariables(profile, vars, &proVars);
+
+    // This is a tad stupid - basically, it can remove only entries which
+    // the above code added.
+    QStringList relativeFilePaths;
+    foreach (const QString &absoluteFilePath, filePaths)
+        relativeFilePaths << proFileDir.relativeFilePath(absoluteFilePath);
+
+    // This code expects proVars to be sorted by the variables' appearance in the file.
+    int delta = 1;
+    foreach (ProVariable *proVar, proVars) {
+        if (proVar->variableOperator() != ProVariable::RemoveOperator
+            && proVar->variableOperator() != ProVariable::ReplaceOperator) {
+
+            bool first = true;
+            int lineNo = proVar->lineNumber() - delta;
+            typedef QPair<int, int> ContPos;
+            QList<ContPos> contPos;
+            while (lineNo < lines->count()) {
+                QString &line = (*lines)[lineNo];
+                int lineLen = line.length();
+                bool killed = false;
+                bool saved = false;
+                int idx = line.indexOf(QLatin1Char('#'));
+                if (idx >= 0)
+                    lineLen = idx;
+                QChar *chars = line.data();
+                forever {
+                    if (!lineLen) {
+                        if (idx >= 0)
+                            goto nextLine;
+                        goto nextVar;
+                    }
+                    QChar c = chars[lineLen - 1];
+                    if (c != QLatin1Char(' ') && c != QLatin1Char('\t'))
+                        break;
+                    lineLen--;
+                }
+                {
+                    int contCol = -1;
+                    if (chars[lineLen - 1] == QLatin1Char('\\'))
+                        contCol = --lineLen;
+                    int colNo = 0;
+                    if (first) {
+                        colNo = line.indexOf(QLatin1Char('=')) + 1;
+                        first = false;
+                        saved = true;
+                    }
+                    while (colNo < lineLen) {
+                        QChar c = chars[colNo];
+                        if (c == QLatin1Char(' ') || c == QLatin1Char('\t')) {
+                            colNo++;
+                            continue;
+                        }
+                        int varCol = colNo;
+                        while (colNo < lineLen) {
+                            QChar c = chars[colNo];
+                            if (c == QLatin1Char(' ') || c == QLatin1Char('\t'))
+                                break;
+                            colNo++;
+                        }
+                        QString fn = line.mid(varCol, colNo - varCol);
+                        if (relativeFilePaths.contains(fn)) {
+                            notChanged.removeOne(QDir::cleanPath(proFileDir.absoluteFilePath(fn)));
+                            if (colNo < lineLen)
+                                colNo++;
+                            else if (varCol)
+                                varCol--;
+                            int len = colNo - varCol;
+                            colNo = varCol;
+                            line.remove(varCol, len);
+                            lineLen -= len;
+                            contCol -= len;
+                            idx -= len;
+                            if (idx >= 0)
+                                line.insert(idx, QLatin1String("# ") + fn + QLatin1Char(' '));
+                            killed = true;
+                        } else {
+                            saved = true;
+                        }
+                    }
+                    if (saved) {
+                        // Entries remained
+                        contPos.clear();
+                    } else if (killed) {
+                        // Entries existed, but were all removed
+                        if (contCol < 0) {
+                            // This is the last line, so clear continuations leading to it
+                            foreach (const ContPos &pos, contPos) {
+                                QString &bline = (*lines)[pos.first];
+                                bline.remove(pos.second, 1);
+                                if (pos.second == bline.length())
+                                    while (bline.endsWith(QLatin1Char(' '))
+                                           || bline.endsWith(QLatin1Char('\t')))
+                                        bline.chop(1);
+                            }
+                            contPos.clear();
+                        }
+                        if (idx < 0) {
+                            // Not even a comment stayed behind, so zap the line
+                            lines->removeAt(lineNo);
+                            delta++;
+                            continue;
+                        }
+                    }
+                    if (contCol >= 0)
+                        contPos.append(qMakePair(lineNo, contCol));
+                }
+              nextLine:
+                lineNo++;
+            }
+          nextVar: ;
+        }
     }
+    return notChanged;
 }
index 27a2bea..804c11e 100644 (file)
 
 #include "namespace_global.h"
 
-#include <QtCore/QTextStream>
+#include <QStringList>
 
 QT_BEGIN_NAMESPACE
+class QDir;
 class ProFile;
-class ProValue;
-class ProItem;
-class ProBlock;
 QT_END_NAMESPACE
 
 namespace Qt4ProjectManager {
@@ -47,26 +45,12 @@ namespace Internal {
 class ProWriter
 {
 public:
-    bool write(ProFile *profile, const QString &fileName);
-    QString contents(ProFile *profile);
-
-protected:
-    QString fixComment(const QString &comment, const QString &indent) const;
-    void writeValue(ProValue *value, const QString &indent);
-    void writeOther(ProItem *item, const QString &indent);
-    void writeBlock(ProBlock *block, const QString &indent);
-    void writeItem(ProItem *item, const QString &indent);
-
-private:
-    enum ProWriteState {
-        NewLine     = 0x01,
-        FirstItem   = 0x02,
-        LastItem    = 0x04
-    };
-
-    QTextStream m_out;
-    int m_writeState;
-    QString m_comment;
+    static void addFiles(ProFile *profile, QStringList *lines,
+                         const QDir &proFileDir, const QStringList &filePaths,
+                         const QStringList &vars);
+    static QStringList removeFiles(ProFile *profile, QStringList *lines,
+                                   const QDir &proFileDir, const QStringList &filePaths,
+                                   const QStringList &vars);
 };
 
 } // namespace Internal
diff --git a/tests/auto/profilewriter/main.cpp b/tests/auto/profilewriter/main.cpp
new file mode 100644 (file)
index 0000000..f37eb76
--- /dev/null
@@ -0,0 +1,299 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2009 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 "profileevaluator.h"
+#include "prowriter.h"
+
+#include <QtTest/QtTest>
+//#include <QtCore/QSet>
+
+#define BASE_DIR "/some/stuff"
+
+class tst_ProFileWriter : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void edit_data();
+    void edit();
+    void multiVar();
+};
+
+void tst_ProFileWriter::edit_data()
+{
+    QTest::addColumn<bool>("add");
+    QTest::addColumn<QStringList>("files");
+    QTest::addColumn<QString>("input");
+    QTest::addColumn<QString>("output");
+
+    struct Case {
+        bool add;
+        const char *title;
+        const char * const *files;
+        const char *input;
+        const char *output;
+    };
+
+    static const char *f_foo[] = { "foo", 0 };
+    static const char *f_foo_bar[] = { "foo", "bar", 0 };
+    static const Case cases[] = {
+        // Adding entries
+        {
+            true, "add new", f_foo,
+            "# test file",
+            "# test file\n"
+            "\n"
+            "SOURCES += \\\n"
+            "    foo"
+        },
+        {
+            true, "add new ignoring scoped", f_foo,
+            "unix:SOURCES = some files",
+            "unix:SOURCES = some files\n"
+            "\n"
+            "SOURCES += \\\n"
+            "    foo"
+        },
+        {
+            true, "add to existing", f_foo,
+            "SOURCES = some files",
+            "SOURCES = some files \\\n"
+            "    foo"
+        },
+        {
+            true, "add to existing after comment", f_foo,
+            "SOURCES = some files   # comment",
+            "SOURCES = some files \\   # comment\n"
+            "    foo"
+        },
+        {
+            true, "add to existing after comment line", f_foo,
+            "SOURCES = some \\\n"
+            "   # comment\n"
+            "    files",
+            "SOURCES = some \\\n"
+            "   # comment\n"
+            "    files \\\n"
+            "    foo"
+        },
+
+        // Removing entries
+        {
+            false, "remove fail", f_foo,
+            "SOURCES = bak bar",
+            "SOURCES = bak bar"
+        },
+        {
+            false, "remove one-line middle", f_foo,
+            "SOURCES = bak foo bar",
+            "SOURCES = bak bar"
+        },
+        {
+            false, "remove one-line trailing", f_foo,
+            "SOURCES = bak bar foo",
+            "SOURCES = bak bar"
+        },
+        {
+            false, "remove multi-line single leading", f_foo,
+            "SOURCES = foo \\\n"
+            "    bak \\\n"
+            "    bar",
+            "SOURCES = \\\n"
+            "    bak \\\n"
+            "    bar"
+        },
+        {
+            false, "remove multi-line single middle", f_foo,
+            "SOURCES = bak \\\n"
+            "    foo \\\n"
+            "    bar",
+            "SOURCES = bak \\\n"
+            "    bar"
+        },
+        {
+            false, "remove multi-line single trailing", f_foo,
+            "SOURCES = bak \\\n"
+            "    bar \\\n"
+            "    foo",
+            "SOURCES = bak \\\n"
+            "    bar"
+        },
+        {
+            false, "remove multi-line single leading with comment", f_foo,
+            "SOURCES = foo \\  # comment\n"
+            "    bak \\\n"
+            "    bar",
+            "SOURCES = \\  # foo # comment\n"
+            "    bak \\\n"
+            "    bar"
+        },
+        {
+            false, "remove multi-line single middle with comment", f_foo,
+            "SOURCES = bak \\\n"
+            "    foo \\  # comment\n"
+            "    bar",
+            "SOURCES = bak \\\n"
+            "    \\  # foo # comment\n"
+            "    bar"
+        },
+        {
+            false, "remove multi-line single trailing with comment", f_foo,
+            "SOURCES = bak \\\n"
+            "    bar \\\n"
+            "    foo  # comment",
+            "SOURCES = bak \\\n"
+            "    bar\n"
+            "     # foo # comment"
+        },
+        {
+            false, "remove multi-line single trailing after empty line", f_foo,
+            "SOURCES = bak \\\n"
+            "    bar \\\n"
+            "    \\\n"
+            "    foo",
+            "SOURCES = bak \\\n"
+            "    bar\n"
+        },
+        {
+            false, "remove multi-line single trailing after comment line", f_foo,
+            "SOURCES = bak \\\n"
+            "    bar \\\n"
+            "       # just a comment\n"
+            "    foo",
+            "SOURCES = bak \\\n"
+            "    bar\n"
+            "       # just a comment"
+        },
+        {
+            false, "remove multi-line single trailing after empty line with comment", f_foo,
+            "SOURCES = bak \\\n"
+            "    bar \\\n"
+            "    \\ # just a comment\n"
+            "    foo",
+            "SOURCES = bak \\\n"
+            "    bar\n"
+            "     # just a comment"
+        },
+        {
+            false, "remove multiple one-line middle", f_foo_bar,
+            "SOURCES = bak foo bar baz",
+            "SOURCES = bak baz"
+        },
+        {
+            false, "remove multiple one-line trailing", f_foo_bar,
+            "SOURCES = bak baz foo bar",
+            "SOURCES = bak baz"
+        },
+        {
+            false, "remove multiple one-line interleaved", f_foo_bar,
+            "SOURCES = bak foo baz bar",
+            "SOURCES = bak baz"
+        },
+        {
+            false, "remove multiple one-line middle with comment", f_foo_bar,
+            "SOURCES = bak foo bar baz   # comment",
+            "SOURCES = bak baz   # bar # foo # comment"
+        },
+        {
+            false, "remove multi-line multiple trailing with empty line with comment", f_foo_bar,
+            "SOURCES = bak \\\n"
+            "    bar \\\n"
+            "    \\ # just a comment\n"
+            "    foo",
+            "SOURCES = bak\n"
+            "     # just a comment"
+        },
+    };
+
+    for (uint i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
+        const Case *_case = &cases[i];
+        QStringList files;
+        for (const char * const *file = _case->files; *file; file++)
+            files << QString::fromLatin1(BASE_DIR "/") + QString::fromLatin1(*file);
+        QTest::newRow(_case->title)
+                << _case->add
+                << files
+                << QString::fromLatin1(_case->input)
+                << QString::fromLatin1(_case->output);
+    }
+}
+
+void tst_ProFileWriter::edit()
+{
+    QFETCH(bool, add);
+    QFETCH(QStringList, files);
+    QFETCH(QString, input);
+    QFETCH(QString, output);
+
+    QDir baseDir(BASE_DIR);
+    QStringList lines = input.split(QLatin1String("\n"));
+    QStringList vars; vars << QLatin1String("SOURCES");
+
+    ProFileOption option;
+    ProFileEvaluator reader(&option);
+    ProFile *proFile = reader.parsedProFile(BASE_DIR "/test.pro", input);
+    QVERIFY(proFile);
+    if (add)
+        Qt4ProjectManager::Internal::ProWriter::addFiles(proFile, &lines, baseDir, files, vars);
+    else
+        Qt4ProjectManager::Internal::ProWriter::removeFiles(proFile, &lines, baseDir, files, vars);
+
+    QCOMPARE(lines.join(QLatin1String("\n")), output);
+}
+
+void tst_ProFileWriter::multiVar()
+{
+    QDir baseDir(BASE_DIR);
+    QString input = QLatin1String(
+            "SOURCES = foo bar\n"
+            "# comment line\n"
+            "HEADERS = baz bak"
+            );
+    QStringList lines = input.split(QLatin1String("\n"));
+    QString output = QLatin1String(
+            "SOURCES = bar\n"
+            "# comment line\n"
+            "HEADERS = baz"
+            );
+    QStringList files; files
+            << QString::fromLatin1(BASE_DIR "/foo")
+            << QString::fromLatin1(BASE_DIR "/bak");
+    QStringList vars; vars << QLatin1String("SOURCES") << QLatin1String("HEADERS");
+
+    ProFileOption option;
+    ProFileEvaluator reader(&option);
+    ProFile *proFile = reader.parsedProFile(BASE_DIR "/test.pro", input);
+    QVERIFY(proFile);
+    Qt4ProjectManager::Internal::ProWriter::removeFiles(proFile, &lines, baseDir, files, vars);
+
+    QCOMPARE(lines.join(QLatin1String("\n")), output);
+}
+
+QTEST_MAIN(tst_ProFileWriter)
+#include "main.moc"
diff --git a/tests/auto/profilewriter/profilewriter.pro b/tests/auto/profilewriter/profilewriter.pro
new file mode 100644 (file)
index 0000000..774e09f
--- /dev/null
@@ -0,0 +1,6 @@
+load(qttest_p4)
+
+include(../../../src/shared/proparser/proparser.pri)
+
+SOURCES += main.cpp
+QT -= gui