tools/qdoc3/config.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/qdoc3/config.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,892 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** 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.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*
+  config.cpp
+*/
+
+#include <QtCore>
+
+#include "archiveextractor.h"
+#include "config.h"
+#include "uncompressor.h"
+#include <stdlib.h>
+
+QT_BEGIN_NAMESPACE
+
+/*
+  An entry on the MetaStack.
+ */
+class MetaStackEntry
+{
+public:
+    void open();
+    void close();
+
+    QStringList accum;
+    QStringList next;
+};
+
+/*
+ */
+void MetaStackEntry::open()
+{
+    next.append(QString());
+}
+
+/*
+ */
+void MetaStackEntry::close()
+{
+    accum += next;
+    next.clear();
+}
+
+/*
+  ###
+*/
+class MetaStack : private QStack<MetaStackEntry>
+{
+public:
+    MetaStack();
+
+    void process(QChar ch, const Location& location);
+    QStringList getExpanded(const Location& location);
+};
+
+MetaStack::MetaStack()
+{
+    push(MetaStackEntry());
+    top().open();
+}
+
+void MetaStack::process(QChar ch, const Location& location)
+{
+    if (ch == QLatin1Char('{')) {
+        push(MetaStackEntry());
+        top().open();
+    }
+    else if (ch == QLatin1Char('}')) {
+        if (count() == 1)
+            location.fatal(tr("Unexpected '}'"));
+
+        top().close();
+        QStringList suffixes = pop().accum;
+        QStringList prefixes = top().next;
+
+        top().next.clear();
+        QStringList::ConstIterator pre = prefixes.begin();
+        while (pre != prefixes.end()) {
+                QStringList::ConstIterator suf = suffixes.begin();
+            while (suf != suffixes.end()) {
+            top().next << (*pre + *suf);
+            ++suf;
+            }
+            ++pre;
+        }
+    }
+    else if (ch == QLatin1Char(',') && count() > 1) {
+        top().close();
+        top().open();
+    }
+    else {
+        QStringList::Iterator pre = top().next.begin();
+        while (pre != top().next.end()) {
+            *pre += ch;
+            ++pre;
+        }
+    }
+}
+
+QStringList MetaStack::getExpanded(const Location& location)
+{
+    if (count() > 1)
+        location.fatal(tr("Missing '}'"));
+
+    top().close();
+    return top().accum;
+}
+
+QT_STATIC_CONST_IMPL QString Config::dot = QLatin1String(".");
+QMap<QString, QString> Config::uncompressedFiles;
+QMap<QString, QString> Config::extractedDirs;
+int Config::numInstances;
+
+/*!
+  \class Config
+  \brief The Config class contains the configuration variables
+  for controlling how qdoc produces documentation.
+
+  Its load() function, reads, parses, and processes a qdocconf file.
+ */
+
+/*!
+  The constructor sets the \a programName and initializes all
+  internal state variables to empty values.
+ */
+Config::Config(const QString& programName)
+    : prog(programName)
+{
+    loc = Location::null;
+    lastLoc = Location::null;
+    locMap.clear();
+    stringValueMap.clear();
+    stringListValueMap.clear();
+    numInstances++;
+}
+
+/*!
+  The destructor deletes all the temporary files and
+  directories it built.
+ */
+Config::~Config()
+{
+    if (--numInstances == 0) {
+	QMap<QString, QString>::ConstIterator f = uncompressedFiles.begin();
+	while (f != uncompressedFiles.end()) {
+	    QDir().remove(*f);
+	    ++f;
+	}
+	uncompressedFiles.clear();
+
+	QMap<QString, QString>::ConstIterator d = extractedDirs.begin();
+	while (d != extractedDirs.end()) {
+	    removeDirContents(*d);
+	    QDir dir(*d);
+	    QString name = dir.dirName();
+	    dir.cdUp();
+	    dir.rmdir(name);
+	    ++d;
+	}
+	extractedDirs.clear();
+    }
+}
+
+/*!
+  Loads and parses the qdoc configuration file \a fileName.
+  This function calls the other load() function, which does
+  the loading, parsing, and processing of the configuration
+  file.
+
+  Intializes the location variables returned by location()
+  and lastLocation().
+ */
+void Config::load(const QString& fileName)
+{
+    load(Location::null, fileName);
+    if (loc.isEmpty()) {
+	loc = Location(fileName);
+    }
+    else {
+	loc.setEtc(true);
+    }
+    lastLoc = Location::null;
+}
+
+/*!
+  Joins all the strings in \a values into a single string with the
+  individual \a values separated by ' '. Then it inserts the result
+  into the string list map with \a var as the key.
+
+  It also inserts the \a values string list into a separate map,
+  also with \a var as the key.
+ */
+void Config::setStringList(const QString& var, const QStringList& values)
+{
+    stringValueMap[var] = values.join(QLatin1String(" "));
+    stringListValueMap[var] = values;
+}
+
+/*!
+  Looks up the configuarion variable \a var in the string
+  map and returns the boolean value.
+ */
+bool Config::getBool(const QString& var) const
+{
+    return QVariant(getString(var)).toBool();
+}
+
+/*!
+  Looks up the configuration variable \a var in the string list
+  map. Iterates through the string list found, interpreting each
+  string in the list as an integer and adding it to a total sum.
+  Returns the sum.
+ */
+int Config::getInt(const QString& var) const
+{
+    QStringList strs = getStringList(var);
+    QStringList::ConstIterator s = strs.begin();
+    int sum = 0;
+
+    while (s != strs.end()) {
+	sum += (*s).toInt();
+	++s;
+    }
+    return sum;
+}
+
+/*!
+  First, this function looks up the configuration variable \a var
+  in the location map and, if found, sets the internal variable
+  \c{lastLoc} to the Location that \a var maps to.
+  
+  Then it looks up the configuration variable \a var in the string
+  map, and returns the string that \a var maps to.
+ */
+QString Config::getString(const QString& var) const
+{
+    if (!locMap[var].isEmpty())
+	(Location&) lastLoc = locMap[var];
+    return stringValueMap[var];
+}
+
+/*!
+  Looks up the configuration variable \a var in the string
+  list map, converts the string list it maps to into a set
+  of strings, and returns the set.
+ */
+QSet<QString> Config::getStringSet(const QString& var) const
+{
+    return QSet<QString>::fromList(getStringList(var));
+}
+
+/*!
+  First, this function looks up the configuration variable \a var
+  in the location map and, if found, sets the internal variable
+  \c{lastLoc} the Location that \a var maps to.
+  
+  Then it looks up the configuration variable \a var in the string
+  list map, and returns the string list that \a var maps to.
+ */
+QStringList Config::getStringList(const QString& var) const
+{
+    if (!locMap[var].isEmpty())
+	(Location&) lastLoc = locMap[var];
+    return stringListValueMap[var];
+}
+
+/*!
+  Calls getRegExpList() with the control variable \a var and
+  iterates through the resulting list of regular expressions,
+  concatening them with some extras characters to form a single
+  QRegExp, which is returned/
+
+  \sa getRegExpList()
+ */
+QRegExp Config::getRegExp(const QString& var) const
+{
+    QString pattern;
+    QList<QRegExp> subRegExps = getRegExpList(var);
+    QList<QRegExp>::ConstIterator s = subRegExps.begin();
+
+    while (s != subRegExps.end()) {
+        if (!(*s).isValid())
+            return *s;
+        if (!pattern.isEmpty())
+            pattern += QLatin1Char('|');
+        pattern += QLatin1String("(?:") + (*s).pattern() + QLatin1Char(')');
+        ++s;
+    }
+    if (pattern.isEmpty())
+        pattern = QLatin1String("$x"); // cannot match
+    return QRegExp(pattern);
+}
+
+/*!
+  Looks up the configuration variable \a var in the string list
+  map, converts the string list to a list of regular expressions,
+  and returns it.
+ */
+QList<QRegExp> Config::getRegExpList(const QString& var) const
+{
+    QStringList strs = getStringList(var);
+    QStringList::ConstIterator s = strs.begin();
+    QList<QRegExp> regExps;
+
+    while (s != strs.end()) {
+	regExps += QRegExp(*s);
+	++s;
+    }
+    return regExps;
+}
+
+/*!
+  This function is slower than it could be.
+ */
+QSet<QString> Config::subVars(const QString& var) const
+{
+    QSet<QString> result;
+    QString varDot = var + QLatin1Char('.');
+    QMap<QString, QString>::ConstIterator v = stringValueMap.begin();
+    while (v != stringValueMap.end()) {
+        if (v.key().startsWith(varDot)) {
+            QString subVar = v.key().mid(varDot.length());
+            int dot = subVar.indexOf(QLatin1Char('.'));
+            if (dot != -1)
+                subVar.truncate(dot);
+            result.insert(subVar);
+        }
+        ++v;
+    }
+    return result;
+}
+
+/*!
+  Builds and returns a list of file pathnames for the file
+  type specified by \a filesVar (e.g. "headers" or "sources").
+  The files are found in the directories specified by
+  \a dirsVar, and they are filtered by \a defaultNameFilter
+  if a better filter can't be constructed from \a filesVar.
+  The directories in \a excludedDirs are avoided.
+ */
+QStringList Config::getAllFiles(const QString &filesVar,
+                                const QString &dirsVar,
+				const QString &defaultNameFilter,
+                                const QSet<QString> &excludedDirs)
+{
+    QStringList result = getStringList(filesVar);
+    QStringList dirs = getStringList(dirsVar);
+
+    QString nameFilter = getString(filesVar + dot +
+        QLatin1String(CONFIG_FILEEXTENSIONS));
+    if (nameFilter.isEmpty())
+        nameFilter = defaultNameFilter;
+
+    QStringList::ConstIterator d = dirs.begin();
+    while (d != dirs.end()) {
+	result += getFilesHere(*d, nameFilter, excludedDirs);
+	++d;
+    }
+    return result;
+}
+
+/*!
+ */
+QString Config::findFile(const Location& location,
+                         const QStringList& files,
+                         const QStringList& dirs,
+                         const QString& fileName,
+                         QString& userFriendlyFilePath)
+{
+    if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
+        userFriendlyFilePath = fileName;
+        return fileName;
+    }
+
+    QFileInfo fileInfo;
+    QStringList components = fileName.split(QLatin1Char('?'));
+    QString firstComponent = components.first();
+
+    QStringList::ConstIterator f = files.begin();
+    while (f != files.end()) {
+	if (*f == firstComponent ||
+            (*f).endsWith(QLatin1Char('/') + firstComponent)) {
+	    fileInfo.setFile(*f);
+	    if (!fileInfo.exists())
+		location.fatal(tr("File '%1' does not exist").arg(*f));
+	    break;
+	}
+	++f;
+    }
+
+    if (fileInfo.fileName().isEmpty()) {
+	QStringList::ConstIterator d = dirs.begin();
+	while (d != dirs.end()) {
+	    fileInfo.setFile(QDir(*d), firstComponent);
+	    if (fileInfo.exists()) {
+		break;
+            }
+	    ++d;
+	}
+    }
+
+    userFriendlyFilePath = QString();
+    if (!fileInfo.exists())
+	    return QString();
+
+    QStringList::ConstIterator c = components.begin();
+    for (;;) {
+	bool isArchive = (c != components.end() - 1);
+	ArchiveExtractor *extractor = 0;
+	QString userFriendly = *c;
+
+	if (isArchive) {
+	    extractor = ArchiveExtractor::extractorForFileName(userFriendly);
+        }
+
+	if (extractor == 0) {
+	    Uncompressor *uncompressor =
+		    Uncompressor::uncompressorForFileName(userFriendly);
+	    if (uncompressor != 0) {
+		QString fileNameWithCorrectExtension =
+			uncompressor->uncompressedFilePath(
+				fileInfo.filePath());
+		QString uncompressed = uncompressedFiles[fileInfo.filePath()];
+		if (uncompressed.isEmpty()) {
+		    uncompressed =
+                        QTemporaryFile(fileInfo.filePath()).fileName();
+		    uncompressor->uncompressFile(location,
+                                                 fileInfo.filePath(),
+                                                 uncompressed);
+		    uncompressedFiles[fileInfo.filePath()] = uncompressed;
+		}
+		fileInfo.setFile(uncompressed);
+
+		if (isArchive) {
+		    extractor = ArchiveExtractor::extractorForFileName(
+					fileNameWithCorrectExtension);
+		}
+                else {
+		    userFriendly = fileNameWithCorrectExtension;
+		}
+	    }
+	}
+	userFriendlyFilePath += userFriendly;
+
+	if (isArchive) {
+	    if (extractor == 0)
+		location.fatal(tr("Unknown archive type '%1'")
+				.arg(userFriendlyFilePath));
+	    QString extracted = extractedDirs[fileInfo.filePath()];
+	    if (extracted.isEmpty()) {
+		extracted = QTemporaryFile(fileInfo.filePath()).fileName();
+		if (!QDir().mkdir(extracted))
+		    location.fatal(tr("Cannot create temporary directory '%1'")
+				    .arg(extracted));
+		extractor->extractArchive(location, fileInfo.filePath(),
+					   extracted);
+		extractedDirs[fileInfo.filePath()] = extracted;
+	    }
+	    ++c;
+	    fileInfo.setFile(QDir(extracted), *c);
+	}
+        else {
+	    break;
+	}
+	userFriendlyFilePath += "?";
+    }
+    return fileInfo.filePath();
+}
+
+/*!
+ */
+QString Config::findFile(const Location& location,
+                         const QStringList& files,
+                         const QStringList& dirs,
+                         const QString& fileBase,
+                         const QStringList& fileExtensions,
+                         QString& userFriendlyFilePath)
+{
+    QStringList::ConstIterator e = fileExtensions.begin();
+    while (e != fileExtensions.end()) {
+	QString filePath = findFile(location, files, dirs, fileBase + "." + *e,
+				     userFriendlyFilePath);
+	if (!filePath.isEmpty())
+	    return filePath;
+	++e;
+    }
+    return findFile(location, files, dirs, fileBase, userFriendlyFilePath);
+}
+
+/*!
+ */
+QString Config::copyFile(const Location& location,
+                         const QString& sourceFilePath,
+                         const QString& userFriendlySourceFilePath,
+                         const QString& targetDirPath)
+{
+    QFile inFile(sourceFilePath);
+    if (!inFile.open(QFile::ReadOnly)) {
+	location.fatal(tr("Cannot open input file '%1': %2")
+			.arg(inFile.fileName()).arg(inFile.errorString()));
+	return "";
+    }
+
+    QString outFileName = userFriendlySourceFilePath;
+    int slash = outFileName.lastIndexOf("/");
+    if (slash != -1)
+	outFileName = outFileName.mid(slash);
+
+    QFile outFile(targetDirPath + "/" + outFileName);
+    if (!outFile.open(QFile::WriteOnly)) {
+	location.fatal(tr("Cannot open output file '%1': %2")
+			.arg(outFile.fileName()).arg(outFile.errorString()));
+	return "";
+    }
+
+    char buffer[1024];
+    int len;
+    while ((len = inFile.read(buffer, sizeof(buffer))) > 0) {
+	outFile.write(buffer, len);
+    }
+    return outFileName;
+}
+
+/*!
+  Finds the largest unicode digit in \a value in the range
+  1..7 and returns it.
+ */
+int Config::numParams(const QString& value)
+{
+    int max = 0;
+    for (int i = 0; i != value.length(); i++) {
+        uint c = value[i].unicode();
+        if (c > 0 && c < 8)
+            max = qMax(max, (int)c);
+    }
+    return max;
+}
+
+/*!
+  Removes everything from \a dir. This function is recursive.
+  It doesn't remove \a dir itself, but if it was called
+  recursively, then the caller will remove \a dir.
+ */
+bool Config::removeDirContents(const QString& dir)
+{
+    QDir dirInfo(dir);
+    QFileInfoList entries = dirInfo.entryInfoList();
+
+    bool ok = true;
+
+    QFileInfoList::Iterator it = entries.begin();
+    while (it != entries.end()) {
+	if ((*it).isFile()) {
+	    if (!dirInfo.remove((*it).fileName()))
+		ok = false;
+	}
+        else if ((*it).isDir()) {
+	    if ((*it).fileName() != "." && (*it).fileName() != "..") {
+		if (removeDirContents((*it).absoluteFilePath())) {
+		    if (!dirInfo.rmdir((*it).fileName()))
+			ok = false;
+		}
+                else {
+		    ok = false;
+		}
+	    }
+	}
+	++it;
+    }
+    return ok;
+}
+
+/*!
+  Returns true if \a ch is a letter, number, '_', '.',
+  '{', '}', or ','.
+ */
+bool Config::isMetaKeyChar(QChar ch)
+{
+    return ch.isLetterOrNumber()
+        || ch == QLatin1Char('_')
+        || ch == QLatin1Char('.')
+        || ch == QLatin1Char('{')
+        || ch == QLatin1Char('}')
+        || ch == QLatin1Char(',');
+}
+
+/*!
+  Load, parse, and process a qdoc configuration file. This
+  function is only called by the other load() function, but
+  this one is recursive, i.e., it calls itself when it sees
+  an \c{include} statement in the qdog configuration file.
+ */
+void Config::load(Location location, const QString& fileName)
+{
+    QRegExp keySyntax("\\w+(?:\\.\\w+)*");
+
+#define SKIP_CHAR() \
+    do { \
+        location.advance(c); \
+        ++i; \
+        c = text.at(i); \
+        cc = c.unicode(); \
+    } while (0)
+
+#define SKIP_SPACES() \
+    while (c.isSpace() && cc != '\n') \
+        SKIP_CHAR()
+
+#define PUT_CHAR() \
+    word += c; \
+    SKIP_CHAR();
+
+    if (location.depth() > 16)
+        location.fatal(tr("Too many nested includes"));
+
+    QFile fin(fileName);
+    if (!fin.open(QFile::ReadOnly | QFile::Text)) {
+        fin.setFileName(fileName + ".qdoc");
+        if (!fin.open(QFile::ReadOnly | QFile::Text))
+            location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString()));
+    }
+
+    QString text = fin.readAll();
+    text += QLatin1String("\n\n");
+    text += QChar('\0');
+    fin.close();
+
+    location.push(fileName);
+    location.start();
+
+    int i = 0;
+    QChar c = text.at(0);
+    uint cc = c.unicode();
+    while (i < (int) text.length()) {
+        if (cc == 0)
+            ++i;
+        else if (c.isSpace()) {
+            SKIP_CHAR();
+        }
+        else if (cc == '#') {
+            do {
+                SKIP_CHAR();
+            } while (cc != '\n');
+        }
+        else if (isMetaKeyChar(c)) {
+            Location keyLoc = location;
+            bool plus = false;
+            QString stringValue;
+            QStringList stringListValue;
+            QString word;
+            bool inQuote = false;
+            bool prevWordQuoted = true;
+            bool metWord = false;
+
+            MetaStack stack;
+            do {
+                stack.process(c, location);
+                SKIP_CHAR();
+            } while (isMetaKeyChar(c));
+
+            QStringList keys = stack.getExpanded(location);
+            SKIP_SPACES();
+
+            if (keys.count() == 1 && keys.first() == "include") {
+                QString includeFile;
+
+                if (cc != '(')
+                    location.fatal(tr("Bad include syntax"));
+                SKIP_CHAR();
+                SKIP_SPACES();
+                while (!c.isSpace() && cc != '#' && cc != ')') {
+                    includeFile += c;
+                    SKIP_CHAR();
+                }
+                SKIP_SPACES();
+                if (cc != ')')
+                    location.fatal(tr("Bad include syntax"));
+                SKIP_CHAR();
+                SKIP_SPACES();
+                if (cc != '#' && cc != '\n')
+                    location.fatal(tr("Trailing garbage"));
+
+                /*
+                  Here is the recursive call.
+                 */
+                load(location,
+                      QFileInfo(QFileInfo(fileName).dir(), includeFile)
+                      .filePath());
+            }
+            else {
+                /*
+                  It wasn't an include statement, so it;s something else.
+                 */
+                if (cc == '+') {
+                    plus = true;
+                    SKIP_CHAR();
+                }
+                if (cc != '=')
+                    location.fatal(tr("Expected '=' or '+=' after key"));
+                SKIP_CHAR();
+                SKIP_SPACES();
+
+                for (;;) {
+                    if (cc == '\\') {
+                        int metaCharPos;
+
+                        SKIP_CHAR();
+                        if (cc == '\n') {
+                            SKIP_CHAR();
+                        }
+                        else if (cc > '0' && cc < '8') {
+                            word += QChar(c.digitValue());
+                            SKIP_CHAR();
+                        }
+                        else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) {
+                            word += "\a\b\f\n\r\t\v"[metaCharPos];
+                            SKIP_CHAR();
+                        }
+                        else {
+                            PUT_CHAR();
+                        }
+                    }
+                    else if (c.isSpace() || cc == '#') {
+                        if (inQuote) {
+                            if (cc == '\n')
+                                location.fatal(tr("Unterminated string"));
+                            PUT_CHAR();
+                        }
+                        else {
+                            if (!word.isEmpty()) {
+                                if (metWord)
+                                    stringValue += QLatin1Char(' ');
+                                stringValue += word;
+                                stringListValue << word;
+                                metWord = true;
+                                word.clear();
+                                prevWordQuoted = false;
+                            }
+                            if (cc == '\n' || cc == '#')
+                                break;
+                            SKIP_SPACES();
+                        }
+                    }
+                    else if (cc == '"') {
+                        if (inQuote) {
+                            if (!prevWordQuoted)
+                                stringValue += QLatin1Char(' ');
+                            stringValue += word;
+                            if (!word.isEmpty())
+                                stringListValue << word;
+                            metWord = true;
+                            word.clear();
+                            prevWordQuoted = true;
+                        }
+                        inQuote = !inQuote;
+                        SKIP_CHAR();
+                    }
+                    else if (cc == '$') {
+                        QString var;
+                        SKIP_CHAR();
+                        while (c.isLetterOrNumber() || cc == '_') {
+                            var += c;
+                            SKIP_CHAR();
+                        }
+                        if (!var.isEmpty()) {
+                            char *val = getenv(var.toLatin1().data());
+                            if (val == 0) {
+                                location.fatal(tr("Environment variable '%1' undefined").arg(var));
+                            }
+                            else {
+                                word += QString(val);
+                            }
+                        }
+                    }
+                    else {
+                        if (!inQuote && cc == '=')
+                            location.fatal(tr("Unexpected '='"));
+                        PUT_CHAR();
+                    }
+                }
+
+                QStringList::ConstIterator key = keys.begin();
+                while (key != keys.end()) {
+                    if (!keySyntax.exactMatch(*key))
+                        keyLoc.fatal(tr("Invalid key '%1'").arg(*key));
+
+                    if (plus) {
+                        if (locMap[*key].isEmpty()) {
+                            locMap[*key] = keyLoc;
+                        }
+                        else {
+                            locMap[*key].setEtc(true);
+                        }
+                        if (stringValueMap[*key].isEmpty()) {
+                            stringValueMap[*key] = stringValue;
+                        }
+                        else {
+                            stringValueMap[*key] +=
+                                QLatin1Char(' ') + stringValue;
+                        }
+                        stringListValueMap[*key] += stringListValue;
+                    }
+                    else {
+                        locMap[*key] = keyLoc;
+                        stringValueMap[*key] = stringValue;
+                        stringListValueMap[*key] = stringListValue;
+                    }
+                    ++key;
+                }
+            }
+        }
+        else {
+            location.fatal(tr("Unexpected character '%1' at beginning of line")
+                            .arg(c));
+        }
+    }
+}
+
+QStringList Config::getFilesHere(const QString& dir,
+                                 const QString& nameFilter,
+                                 const QSet<QString> &excludedDirs)
+{
+    QStringList result;
+    if (excludedDirs.contains(dir))
+        return result;
+
+    QDir dirInfo(dir);
+    QStringList fileNames;
+    QStringList::const_iterator fn;
+
+    dirInfo.setNameFilters(nameFilter.split(' '));
+    dirInfo.setSorting(QDir::Name);
+    dirInfo.setFilter(QDir::Files);
+    fileNames = dirInfo.entryList();
+    fn = fileNames.constBegin();
+    while (fn != fileNames.constEnd()) {
+        if (!fn->startsWith(QLatin1Char('~')))
+            result.append(dirInfo.filePath(*fn));
+	++fn;
+    }    
+    
+    dirInfo.setNameFilters(QStringList("*"));
+    dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot);
+    fileNames = dirInfo.entryList();
+    fn = fileNames.constBegin();
+    while (fn != fileNames.constEnd()) {
+        result += getFilesHere(dirInfo.filePath(*fn), nameFilter, excludedDirs);
+	++fn;
+    }
+    return result;
+}
+
+QT_END_NAMESPACE