qdoc: Removed several #if 0 blocks.
[profile/ivi/qtbase.git] / src / tools / qdoc / config.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 /*
43   config.cpp
44 */
45
46 #include <QDir>
47 #include <QVariant>
48 #include <QFile>
49 #include <QTemporaryFile>
50 #include <QTextStream>
51 #include <qdebug.h>
52 #include "config.h"
53 #include <stdlib.h>
54
55 QT_BEGIN_NAMESPACE
56
57 /*
58   An entry on the MetaStack.
59  */
60 class MetaStackEntry
61 {
62 public:
63     void open();
64     void close();
65
66     QStringList accum;
67     QStringList next;
68 };
69
70 /*
71  */
72 void MetaStackEntry::open()
73 {
74     next.append(QString());
75 }
76
77 /*
78  */
79 void MetaStackEntry::close()
80 {
81     accum += next;
82     next.clear();
83 }
84
85 /*
86   ###
87 */
88 class MetaStack : private QStack<MetaStackEntry>
89 {
90 public:
91     MetaStack();
92
93     void process(QChar ch, const Location& location);
94     QStringList getExpanded(const Location& location);
95 };
96
97 MetaStack::MetaStack()
98 {
99     push(MetaStackEntry());
100     top().open();
101 }
102
103 void MetaStack::process(QChar ch, const Location& location)
104 {
105     if (ch == QLatin1Char('{')) {
106         push(MetaStackEntry());
107         top().open();
108     }
109     else if (ch == QLatin1Char('}')) {
110         if (count() == 1)
111             location.fatal(tr("Unexpected '}'"));
112
113         top().close();
114         QStringList suffixes = pop().accum;
115         QStringList prefixes = top().next;
116
117         top().next.clear();
118         QStringList::ConstIterator pre = prefixes.constBegin();
119         while (pre != prefixes.constEnd()) {
120             QStringList::ConstIterator suf = suffixes.constBegin();
121             while (suf != suffixes.constEnd()) {
122                 top().next << (*pre + *suf);
123                 ++suf;
124             }
125             ++pre;
126         }
127     }
128     else if (ch == QLatin1Char(',') && count() > 1) {
129         top().close();
130         top().open();
131     }
132     else {
133         QStringList::Iterator pre = top().next.begin();
134         while (pre != top().next.end()) {
135             *pre += ch;
136             ++pre;
137         }
138     }
139 }
140
141 QStringList MetaStack::getExpanded(const Location& location)
142 {
143     if (count() > 1)
144         location.fatal(tr("Missing '}'"));
145
146     top().close();
147     return top().accum;
148 }
149
150 QT_STATIC_CONST_IMPL QString Config::dot = QLatin1String(".");
151 bool Config::generateExamples = true;
152 QString Config::overrideOutputDir;
153 QString Config::installDir;
154 QSet<QString> Config::overrideOutputFormats;
155 QMap<QString, QString> Config::extractedDirs;
156 int Config::numInstances;
157
158 /*!
159   \class Config
160   \brief The Config class contains the configuration variables
161   for controlling how qdoc produces documentation.
162
163   Its load() function, reads, parses, and processes a qdocconf file.
164  */
165
166 /*!
167   The constructor sets the \a programName and initializes all
168   internal state variables to empty values.
169  */
170 Config::Config(const QString& programName)
171     : prog(programName)
172 {
173     loc = Location::null;
174     lastLoc = Location::null;
175     locMap.clear();
176     stringValueMap.clear();
177     stringListValueMap.clear();
178     numInstances++;
179 }
180
181 /*!
182   The destructor has nothing special to do.
183  */
184 Config::~Config()
185 {
186 }
187
188 /*!
189   Loads and parses the qdoc configuration file \a fileName.
190   This function calls the other load() function, which does
191   the loading, parsing, and processing of the configuration
192   file.
193
194   Intializes the location variables returned by location()
195   and lastLocation().
196  */
197 void Config::load(const QString& fileName)
198 {
199     load(Location::null, fileName);
200     if (loc.isEmpty()) {
201         loc = Location(fileName);
202     }
203     else {
204         loc.setEtc(true);
205     }
206     lastLoc = Location::null;
207 }
208
209 /*!
210   Writes the qdoc configuration data to the named file.
211   The previous contents of the file are overwritten.
212  */
213 void Config::unload(const QString& fileName)
214 {
215     QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
216     while (v != stringValueMap.constEnd()) {
217         qDebug() << v.key() << " = " << v.value();
218         ++v;
219     }
220     qDebug() << "fileName:" << fileName;
221 }
222
223 /*!
224   Joins all the strings in \a values into a single string with the
225   individual \a values separated by ' '. Then it inserts the result
226   into the string list map with \a var as the key.
227
228   It also inserts the \a values string list into a separate map,
229   also with \a var as the key.
230  */
231 void Config::setStringList(const QString& var, const QStringList& values)
232 {
233     stringValueMap[var] = values.join(QLatin1String(" "));
234     stringListValueMap[var] = values;
235 }
236
237 /*!
238   Looks up the configuarion variable \a var in the string
239   map and returns the boolean value.
240  */
241 bool Config::getBool(const QString& var) const
242 {
243     return QVariant(getString(var)).toBool();
244 }
245
246 /*!
247   Looks up the configuration variable \a var in the string list
248   map. Iterates through the string list found, interpreting each
249   string in the list as an integer and adding it to a total sum.
250   Returns the sum.
251  */
252 int Config::getInt(const QString& var) const
253 {
254     QStringList strs = getStringList(var);
255     QStringList::ConstIterator s = strs.constBegin();
256     int sum = 0;
257
258     while (s != strs.constEnd()) {
259         sum += (*s).toInt();
260         ++s;
261     }
262     return sum;
263 }
264
265 /*!
266   Function to return the correct outputdir.
267   outputdir can be set using the qdocconf or the command-line
268   variable -outputdir.
269   */
270 QString Config::getOutputDir() const
271 {
272     if (overrideOutputDir.isNull())
273         return getString(QLatin1String(CONFIG_OUTPUTDIR));
274     else
275         return overrideOutputDir;
276 }
277
278 /*!
279   Function to return the correct outputformats.
280   outputformats can be set using the qdocconf or the command-line
281   variable -outputformat.
282   */
283 QSet<QString> Config::getOutputFormats() const
284 {
285     if (overrideOutputFormats.isEmpty())
286         return getStringSet(QLatin1String(CONFIG_OUTPUTFORMATS));
287     else
288         return overrideOutputFormats;
289 }
290
291 /*!
292   First, this function looks up the configuration variable \a var
293   in the location map and, if found, sets the internal variable
294   \c{lastLoc} to the Location that \a var maps to.
295
296   Then it looks up the configuration variable \a var in the string
297   map, and returns the string that \a var maps to.
298  */
299 QString Config::getString(const QString& var) const
300 {
301     if (!locMap[var].isEmpty())
302         (Location&) lastLoc = locMap[var];
303     return stringValueMap[var];
304 }
305
306 /*!
307   Looks up the configuration variable \a var in the string
308   list map, converts the string list it maps to into a set
309   of strings, and returns the set.
310  */
311 QSet<QString> Config::getStringSet(const QString& var) const
312 {
313     return QSet<QString>::fromList(getStringList(var));
314 }
315
316 /*!
317   First, this function looks up the configuration variable \a var
318   in the location map and, if found, sets the internal variable
319   \c{lastLoc} the Location that \a var maps to.
320
321   Then it looks up the configuration variable \a var in the string
322   list map, and returns the string list that \a var maps to.
323  */
324 QStringList Config::getStringList(const QString& var) const
325 {
326     if (!locMap[var].isEmpty())
327         (Location&) lastLoc = locMap[var];
328     return stringListValueMap[var];
329 }
330
331 /*!
332   This function should only be called when the configuration
333   variable \a var maps to a string list that contains file paths.
334   It cleans the paths with QDir::cleanPath() before returning
335   them.
336
337   First, this function looks up the configuration variable \a var
338   in the location map and, if found, sets the internal variable
339   \c{lastLoc} the Location that \a var maps to.
340
341   Then it looks up the configuration variable \a var in the string
342   list map, which maps to a string list that contains file paths.
343   These paths might not be clean, so QDir::cleanPath() is called
344   for each one. The string list returned contains cleaned paths.
345  */
346 QStringList Config::getCleanPathList(const QString& var) const
347 {
348     if (!locMap[var].isEmpty())
349         (Location&) lastLoc = locMap[var];
350     QStringList t;
351     QMap<QString,QStringList>::const_iterator it = stringListValueMap.constFind(var);
352     if (it != stringListValueMap.constEnd()) {
353         const QStringList& sl = it.value();
354         if (!sl.isEmpty()) {
355             t.reserve(sl.size());
356             for (int i=0; i<sl.size(); ++i) {
357                 t.append(QDir::cleanPath(sl[i]));
358             }
359         }
360     }
361     return t;
362 }
363
364 /*!
365   Calls getRegExpList() with the control variable \a var and
366   iterates through the resulting list of regular expressions,
367   concatening them with some extras characters to form a single
368   QRegExp, which is returned/
369
370   \sa getRegExpList()
371  */
372 QRegExp Config::getRegExp(const QString& var) const
373 {
374     QString pattern;
375     QList<QRegExp> subRegExps = getRegExpList(var);
376     QList<QRegExp>::ConstIterator s = subRegExps.constBegin();
377
378     while (s != subRegExps.constEnd()) {
379         if (!(*s).isValid())
380             return *s;
381         if (!pattern.isEmpty())
382             pattern += QLatin1Char('|');
383         pattern += QLatin1String("(?:") + (*s).pattern() + QLatin1Char(')');
384         ++s;
385     }
386     if (pattern.isEmpty())
387         pattern = QLatin1String("$x"); // cannot match
388     return QRegExp(pattern);
389 }
390
391 /*!
392   Looks up the configuration variable \a var in the string list
393   map, converts the string list to a list of regular expressions,
394   and returns it.
395  */
396 QList<QRegExp> Config::getRegExpList(const QString& var) const
397 {
398     QStringList strs = getStringList(var);
399     QStringList::ConstIterator s = strs.constBegin();
400     QList<QRegExp> regExps;
401
402     while (s != strs.constEnd()) {
403         regExps += QRegExp(*s);
404         ++s;
405     }
406     return regExps;
407 }
408
409 /*!
410   This function is slower than it could be. What it does is
411   find all the keys that begin with \a var + dot and return
412   the matching keys in a set, stripped of the matching prefix
413   and dot.
414  */
415 QSet<QString> Config::subVars(const QString& var) const
416 {
417     QSet<QString> result;
418     QString varDot = var + QLatin1Char('.');
419     QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
420     while (v != stringValueMap.constEnd()) {
421         if (v.key().startsWith(varDot)) {
422             QString subVar = v.key().mid(varDot.length());
423             int dot = subVar.indexOf(QLatin1Char('.'));
424             if (dot != -1)
425                 subVar.truncate(dot);
426             result.insert(subVar);
427         }
428         ++v;
429     }
430     return result;
431 }
432
433 /*!
434   Same as subVars(), but in this case we return a string map
435   with the matching keys (stripped of the prefix \a var and
436   mapped to their values. The pairs are inserted into \a t
437  */
438 void Config::subVarsAndValues(const QString& var, QStringMultiMap& t) const
439 {
440     QString varDot = var + QLatin1Char('.');
441     QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
442     while (v != stringValueMap.constEnd()) {
443         if (v.key().startsWith(varDot)) {
444             QString subVar = v.key().mid(varDot.length());
445             int dot = subVar.indexOf(QLatin1Char('.'));
446             if (dot != -1)
447                 subVar.truncate(dot);
448             t.insert(subVar,v.value());
449         }
450         ++v;
451     }
452 }
453
454 /*!
455   Builds and returns a list of file pathnames for the file
456   type specified by \a filesVar (e.g. "headers" or "sources").
457   The files are found in the directories specified by
458   \a dirsVar, and they are filtered by \a defaultNameFilter
459   if a better filter can't be constructed from \a filesVar.
460   The directories in \a excludedDirs are avoided. The files
461   in \a excludedFiles are not included in the return list.
462  */
463 QStringList Config::getAllFiles(const QString &filesVar,
464                                 const QString &dirsVar,
465                                 const QSet<QString> &excludedDirs,
466                                 const QSet<QString> &excludedFiles)
467 {
468     QStringList result = getStringList(filesVar);
469     QStringList dirs = getStringList(dirsVar);
470
471     QString nameFilter = getString(filesVar + dot + QLatin1String(CONFIG_FILEEXTENSIONS));
472
473     QStringList::ConstIterator d = dirs.constBegin();
474     while (d != dirs.constEnd()) {
475         result += getFilesHere(*d, nameFilter, excludedDirs, excludedFiles);
476         ++d;
477     }
478     return result;
479 }
480
481 /*!
482   \a fileName is the path of the file to find.
483
484   \a files and \a dirs are the lists where we must find the
485   components of \a fileName.
486
487   \a location is used for obtaining the file and line numbers
488   for report qdoc errors.
489  */
490 QString Config::findFile(const Location& location,
491                          const QStringList& files,
492                          const QStringList& dirs,
493                          const QString& fileName,
494                          QString& userFriendlyFilePath)
495 {
496     if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
497         userFriendlyFilePath = fileName;
498         return fileName;
499     }
500
501     QFileInfo fileInfo;
502     QStringList components = fileName.split(QLatin1Char('?'));
503     QString firstComponent = components.first();
504
505     QStringList::ConstIterator f = files.constBegin();
506     while (f != files.constEnd()) {
507         if (*f == firstComponent ||
508                 (*f).endsWith(QLatin1Char('/') + firstComponent)) {
509             fileInfo.setFile(*f);
510             if (!fileInfo.exists())
511                 location.fatal(tr("File '%1' does not exist").arg(*f));
512             break;
513         }
514         ++f;
515     }
516
517     if (fileInfo.fileName().isEmpty()) {
518         QStringList::ConstIterator d = dirs.constBegin();
519         while (d != dirs.constEnd()) {
520             fileInfo.setFile(QDir(*d), firstComponent);
521             if (fileInfo.exists()) {
522                 break;
523             }
524             ++d;
525         }
526     }
527
528     userFriendlyFilePath = QString();
529     if (!fileInfo.exists())
530         return QString();
531
532     QStringList::ConstIterator c = components.constBegin();
533     for (;;) {
534         bool isArchive = (c != components.constEnd() - 1);
535         QString userFriendly = *c;
536
537         userFriendlyFilePath += userFriendly;
538
539         if (isArchive) {
540             QString extracted = extractedDirs[fileInfo.filePath()];
541             ++c;
542             fileInfo.setFile(QDir(extracted), *c);
543         }
544         else
545             break;
546
547         userFriendlyFilePath += QLatin1Char('?');
548     }
549     return fileInfo.filePath();
550 }
551
552 /*!
553  */
554 QString Config::findFile(const Location& location,
555                          const QStringList& files,
556                          const QStringList& dirs,
557                          const QString& fileBase,
558                          const QStringList& fileExtensions,
559                          QString& userFriendlyFilePath)
560 {
561     QStringList::ConstIterator e = fileExtensions.constBegin();
562     while (e != fileExtensions.constEnd()) {
563         QString filePath = findFile(location,
564                                     files,
565                                     dirs,
566                                     fileBase + QLatin1Char('.') + *e,
567                                     userFriendlyFilePath);
568         if (!filePath.isEmpty())
569             return filePath;
570         ++e;
571     }
572     return findFile(location, files, dirs, fileBase, userFriendlyFilePath);
573 }
574
575 /*!
576   Copies the \a sourceFilePath to the file name constructed by
577   concatenating \a targetDirPath and \a userFriendlySourceFilePath.
578   \a location is for identifying the file and line number where
579   a qdoc error occurred. The constructed output file name is
580   returned.
581  */
582 QString Config::copyFile(const Location& location,
583                          const QString& sourceFilePath,
584                          const QString& userFriendlySourceFilePath,
585                          const QString& targetDirPath)
586 {
587     QFile inFile(sourceFilePath);
588     if (!inFile.open(QFile::ReadOnly)) {
589         location.fatal(tr("Cannot open input file '%1': %2")
590                        .arg(sourceFilePath).arg(inFile.errorString()));
591         return QString();
592     }
593
594     QString outFileName = userFriendlySourceFilePath;
595     int slash = outFileName.lastIndexOf(QLatin1Char('/'));
596     if (slash != -1)
597         outFileName = outFileName.mid(slash);
598
599     QFile outFile(targetDirPath + QLatin1Char('/') + outFileName);
600     if (!outFile.open(QFile::WriteOnly)) {
601         location.fatal(tr("Cannot open output file '%1': %2")
602                        .arg(outFile.fileName()).arg(outFile.errorString()));
603         return QString();
604     }
605
606     char buffer[1024];
607     int len;
608     while ((len = inFile.read(buffer, sizeof(buffer))) > 0) {
609         outFile.write(buffer, len);
610     }
611     return outFileName;
612 }
613
614 /*!
615   Finds the largest unicode digit in \a value in the range
616   1..7 and returns it.
617  */
618 int Config::numParams(const QString& value)
619 {
620     int max = 0;
621     for (int i = 0; i != value.length(); i++) {
622         uint c = value[i].unicode();
623         if (c > 0 && c < 8)
624             max = qMax(max, (int)c);
625     }
626     return max;
627 }
628
629 /*!
630   Removes everything from \a dir. This function is recursive.
631   It doesn't remove \a dir itself, but if it was called
632   recursively, then the caller will remove \a dir.
633  */
634 bool Config::removeDirContents(const QString& dir)
635 {
636     QDir dirInfo(dir);
637     QFileInfoList entries = dirInfo.entryInfoList();
638
639     bool ok = true;
640
641     QFileInfoList::Iterator it = entries.begin();
642     while (it != entries.end()) {
643         if ((*it).isFile()) {
644             if (!dirInfo.remove((*it).fileName()))
645                 ok = false;
646         }
647         else if ((*it).isDir()) {
648             if ((*it).fileName() != QLatin1String(".") && (*it).fileName() != QLatin1String("..")) {
649                 if (removeDirContents((*it).absoluteFilePath())) {
650                     if (!dirInfo.rmdir((*it).fileName()))
651                         ok = false;
652                 }
653                 else {
654                     ok = false;
655                 }
656             }
657         }
658         ++it;
659     }
660     return ok;
661 }
662
663 /*!
664   Returns true if \a ch is a letter, number, '_', '.',
665   '{', '}', or ','.
666  */
667 bool Config::isMetaKeyChar(QChar ch)
668 {
669     return ch.isLetterOrNumber()
670             || ch == QLatin1Char('_')
671             || ch == QLatin1Char('.')
672             || ch == QLatin1Char('{')
673             || ch == QLatin1Char('}')
674             || ch == QLatin1Char(',');
675 }
676
677 /*!
678   Load, parse, and process a qdoc configuration file. This
679   function is only called by the other load() function, but
680   this one is recursive, i.e., it calls itself when it sees
681   an \c{include} statement in the qdoc configuration file.
682  */
683 void Config::load(Location location, const QString& fileName)
684 {
685     QRegExp keySyntax(QLatin1String("\\w+(?:\\.\\w+)*"));
686
687 #define SKIP_CHAR() \
688     do { \
689     location.advance(c); \
690     ++i; \
691     c = text.at(i); \
692     cc = c.unicode(); \
693 } while (0)
694
695 #define SKIP_SPACES() \
696     while (c.isSpace() && cc != '\n') \
697     SKIP_CHAR()
698
699 #define PUT_CHAR() \
700     word += c; \
701     SKIP_CHAR();
702
703     if (location.depth() > 16)
704         location.fatal(tr("Too many nested includes"));
705
706     QFile fin(fileName);
707     if (!fin.open(QFile::ReadOnly | QFile::Text)) {
708         if (!Config::installDir.isEmpty()) {
709             int prefix = location.filePath().length() - location.fileName().length();
710             fin.setFileName(Config::installDir + "/" + fileName.right(fileName.length() - prefix));
711         }
712         if (!fin.open(QFile::ReadOnly | QFile::Text))
713             location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString()));
714     }
715
716     QTextStream stream(&fin);
717     stream.setCodec("UTF-8");
718     QString text = stream.readAll();
719     text += QLatin1String("\n\n");
720     text += QLatin1Char('\0');
721     fin.close();
722
723     location.push(fileName);
724     location.start();
725
726     int i = 0;
727     QChar c = text.at(0);
728     uint cc = c.unicode();
729     while (i < (int) text.length()) {
730         if (cc == 0)
731             ++i;
732         else if (c.isSpace()) {
733             SKIP_CHAR();
734         }
735         else if (cc == '#') {
736             do {
737                 SKIP_CHAR();
738             } while (cc != '\n');
739         }
740         else if (isMetaKeyChar(c)) {
741             Location keyLoc = location;
742             bool plus = false;
743             QString stringValue;
744             QStringList stringListValue;
745             QString word;
746             bool inQuote = false;
747             bool prevWordQuoted = true;
748             bool metWord = false;
749
750             MetaStack stack;
751             do {
752                 stack.process(c, location);
753                 SKIP_CHAR();
754             } while (isMetaKeyChar(c));
755
756             QStringList keys = stack.getExpanded(location);
757             SKIP_SPACES();
758
759             if (keys.count() == 1 && keys.first() == QLatin1String("include")) {
760                 QString includeFile;
761
762                 if (cc != '(')
763                     location.fatal(tr("Bad include syntax"));
764                 SKIP_CHAR();
765                 SKIP_SPACES();
766
767                 while (!c.isSpace() && cc != '#' && cc != ')') {
768
769                     if (cc == '$') {
770                         QString var;
771                         SKIP_CHAR();
772                         while (c.isLetterOrNumber() || cc == '_') {
773                             var += c;
774                             SKIP_CHAR();
775                         }
776                         if (!var.isEmpty()) {
777                             char *val = getenv(var.toLatin1().data());
778                             if (val == 0) {
779                                 location.fatal(tr("Environment variable '%1' undefined").arg(var));
780                             }
781                             else {
782                                 includeFile += QString::fromLatin1(val);
783                             }
784                         }
785                     } else {
786                         includeFile += c;
787                         SKIP_CHAR();
788                     }
789                 }
790                 SKIP_SPACES();
791                 if (cc != ')')
792                     location.fatal(tr("Bad include syntax"));
793                 SKIP_CHAR();
794                 SKIP_SPACES();
795                 if (cc != '#' && cc != '\n')
796                     location.fatal(tr("Trailing garbage"));
797
798                 /*
799                   Here is the recursive call.
800                  */
801                 load(location,
802                      QFileInfo(QFileInfo(fileName).dir(), includeFile)
803                      .filePath());
804             }
805             else {
806                 /*
807                   It wasn't an include statement, so it's something else.
808                  */
809                 if (cc == '+') {
810                     plus = true;
811                     SKIP_CHAR();
812                 }
813                 if (cc != '=')
814                     location.fatal(tr("Expected '=' or '+=' after key"));
815                 SKIP_CHAR();
816                 SKIP_SPACES();
817
818                 for (;;) {
819                     if (cc == '\\') {
820                         int metaCharPos;
821
822                         SKIP_CHAR();
823                         if (cc == '\n') {
824                             SKIP_CHAR();
825                         }
826                         else if (cc > '0' && cc < '8') {
827                             word += QChar(c.digitValue());
828                             SKIP_CHAR();
829                         }
830                         else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) {
831                             word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]);
832                             SKIP_CHAR();
833                         }
834                         else {
835                             PUT_CHAR();
836                         }
837                     }
838                     else if (c.isSpace() || cc == '#') {
839                         if (inQuote) {
840                             if (cc == '\n')
841                                 location.fatal(tr("Unterminated string"));
842                             PUT_CHAR();
843                         }
844                         else {
845                             if (!word.isEmpty()) {
846                                 if (metWord)
847                                     stringValue += QLatin1Char(' ');
848                                 stringValue += word;
849                                 stringListValue << word;
850                                 metWord = true;
851                                 word.clear();
852                                 prevWordQuoted = false;
853                             }
854                             if (cc == '\n' || cc == '#')
855                                 break;
856                             SKIP_SPACES();
857                         }
858                     }
859                     else if (cc == '"') {
860                         if (inQuote) {
861                             if (!prevWordQuoted)
862                                 stringValue += QLatin1Char(' ');
863                             stringValue += word;
864                             if (!word.isEmpty())
865                                 stringListValue << word;
866                             metWord = true;
867                             word.clear();
868                             prevWordQuoted = true;
869                         }
870                         inQuote = !inQuote;
871                         SKIP_CHAR();
872                     }
873                     else if (cc == '$') {
874                         QString var;
875                         SKIP_CHAR();
876                         while (c.isLetterOrNumber() || cc == '_') {
877                             var += c;
878                             SKIP_CHAR();
879                         }
880                         if (!var.isEmpty()) {
881                             char *val = getenv(var.toLatin1().data());
882                             if (val == 0) {
883                                 location.fatal(tr("Environment variable '%1' undefined").arg(var));
884                             }
885                             else {
886                                 word += QString::fromLatin1(val);
887                             }
888                         }
889                     }
890                     else {
891                         if (!inQuote && cc == '=')
892                             location.fatal(tr("Unexpected '='"));
893                         PUT_CHAR();
894                     }
895                 }
896
897                 QStringList::ConstIterator key = keys.constBegin();
898                 while (key != keys.constEnd()) {
899                     if (!keySyntax.exactMatch(*key))
900                         keyLoc.fatal(tr("Invalid key '%1'").arg(*key));
901
902                     if (plus) {
903                         if (locMap[*key].isEmpty()) {
904                             locMap[*key] = keyLoc;
905                         }
906                         else {
907                             locMap[*key].setEtc(true);
908                         }
909                         if (stringValueMap[*key].isEmpty()) {
910                             stringValueMap[*key] = stringValue;
911                         }
912                         else {
913                             stringValueMap[*key] +=
914                                     QLatin1Char(' ') + stringValue;
915                         }
916                         stringListValueMap[*key] += stringListValue;
917                     }
918                     else {
919                         locMap[*key] = keyLoc;
920                         stringValueMap[*key] = stringValue;
921                         stringListValueMap[*key] = stringListValue;
922                     }
923                     ++key;
924                 }
925             }
926         }
927         else {
928             location.fatal(tr("Unexpected character '%1' at beginning of line")
929                            .arg(c));
930         }
931     }
932 }
933
934 QStringList Config::getFilesHere(const QString& dir,
935                                  const QString& nameFilter,
936                                  const QSet<QString> &excludedDirs,
937                                  const QSet<QString> &excludedFiles)
938 {
939     QStringList result;
940     if (excludedDirs.contains(dir))
941         return result;
942
943     QDir dirInfo(dir);
944     QStringList fileNames;
945     QStringList::const_iterator fn;
946
947     dirInfo.setNameFilters(nameFilter.split(QLatin1Char(' ')));
948     dirInfo.setSorting(QDir::Name);
949     dirInfo.setFilter(QDir::Files);
950     fileNames = dirInfo.entryList();
951     fn = fileNames.constBegin();
952     while (fn != fileNames.constEnd()) {
953         if (!fn->startsWith(QLatin1Char('~'))) {
954             QString s = dirInfo.filePath(*fn);
955             QString c = QDir::cleanPath(s);
956             if (!excludedFiles.contains(c)) {
957                 result.append(c);
958             }
959         }
960         ++fn;
961     }
962
963     dirInfo.setNameFilters(QStringList(QLatin1String("*")));
964     dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot);
965     fileNames = dirInfo.entryList();
966     fn = fileNames.constBegin();
967     while (fn != fileNames.constEnd()) {
968         result += getFilesHere(dirInfo.filePath(*fn), nameFilter, excludedDirs, excludedFiles);
969         ++fn;
970     }
971     return result;
972 }
973
974 QT_END_NAMESPACE