qdoc: Added support for modularized example doc
[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   Joins all the strings in \a values into a single string with the
224   individual \a values separated by ' '. Then it inserts the result
225   into the string list map with \a var as the key.
226
227   It also inserts the \a values string list into a separate map,
228   also with \a var as the key.
229  */
230 void Config::setStringList(const QString& var, const QStringList& values)
231 {
232     stringValueMap[var] = values.join(QLatin1String(" "));
233     stringListValueMap[var] = values;
234 }
235
236 /*!
237   Looks up the configuarion variable \a var in the string
238   map and returns the boolean value.
239  */
240 bool Config::getBool(const QString& var) const
241 {
242     return QVariant(getString(var)).toBool();
243 }
244
245 /*!
246   Looks up the configuration variable \a var in the string list
247   map. Iterates through the string list found, interpreting each
248   string in the list as an integer and adding it to a total sum.
249   Returns the sum.
250  */
251 int Config::getInt(const QString& var) const
252 {
253     QStringList strs = getStringList(var);
254     QStringList::ConstIterator s = strs.constBegin();
255     int sum = 0;
256
257     while (s != strs.constEnd()) {
258         sum += (*s).toInt();
259         ++s;
260     }
261     return sum;
262 }
263
264 /*!
265   Function to return the correct outputdir.
266   outputdir can be set using the qdocconf or the command-line
267   variable -outputdir.
268   */
269 QString Config::getOutputDir() const
270 {
271     if (overrideOutputDir.isNull())
272         return getString(QLatin1String(CONFIG_OUTPUTDIR));
273     else
274         return overrideOutputDir;
275 }
276
277 /*!
278   Function to return the correct outputformats.
279   outputformats can be set using the qdocconf or the command-line
280   variable -outputformat.
281   */
282 QSet<QString> Config::getOutputFormats() const
283 {
284     if (overrideOutputFormats.isEmpty())
285         return getStringSet(QLatin1String(CONFIG_OUTPUTFORMATS));
286     else
287         return overrideOutputFormats;
288 }
289
290 /*!
291   First, this function looks up the configuration variable \a var
292   in the location map and, if found, sets the internal variable
293   \c{lastLoc} to the Location that \a var maps to.
294
295   Then it looks up the configuration variable \a var in the string
296   map, and returns the string that \a var maps to.
297  */
298 QString Config::getString(const QString& var) const
299 {
300     if (!locMap[var].isEmpty())
301         (Location&) lastLoc = locMap[var];
302     return stringValueMap[var];
303 }
304
305 /*!
306   Looks up the configuration variable \a var in the string
307   list map, converts the string list it maps to into a set
308   of strings, and returns the set.
309  */
310 QSet<QString> Config::getStringSet(const QString& var) const
311 {
312     return QSet<QString>::fromList(getStringList(var));
313 }
314
315 /*!
316   First, this function looks up the configuration variable \a var
317   in the location map and, if found, sets the internal variable
318   \c{lastLoc} to the Location that \a var maps to.
319
320   Then it looks up the configuration variable \a var in the string
321   list map, and returns the string list that \a var maps to.
322  */
323 QStringList Config::getStringList(const QString& var) const
324 {
325     if (!locMap[var].isEmpty())
326         (Location&) lastLoc = locMap[var];
327     return stringListValueMap[var];
328 }
329
330 /*!
331   This function should only be called when the configuration
332   variable \a var maps to a string list that contains file paths.
333   It cleans the paths with QDir::cleanPath() before returning
334   them.
335
336   First, this function looks up the configuration variable \a var
337   in the location map and, if found, sets the internal variable
338   \c{lastLoc} the Location that \a var maps to.
339
340   Then it looks up the configuration variable \a var in the string
341   list map, which maps to a string list that contains file paths.
342   These paths might not be clean, so QDir::cleanPath() is called
343   for each one. The string list returned contains cleaned paths.
344  */
345 QStringList Config::getCleanPathList(const QString& var) const
346 {
347     if (!locMap[var].isEmpty())
348         (Location&) lastLoc = locMap[var];
349     QStringList t;
350     QMap<QString,QStringList>::const_iterator it = stringListValueMap.constFind(var);
351     if (it != stringListValueMap.constEnd()) {
352         const QStringList& sl = it.value();
353         if (!sl.isEmpty()) {
354             t.reserve(sl.size());
355             for (int i=0; i<sl.size(); ++i) {
356                 t.append(QDir::cleanPath(sl[i]));
357             }
358         }
359     }
360     return t;
361 }
362
363 /*!
364   Calls getRegExpList() with the control variable \a var and
365   iterates through the resulting list of regular expressions,
366   concatening them with some extras characters to form a single
367   QRegExp, which is returned/
368
369   \sa getRegExpList()
370  */
371 QRegExp Config::getRegExp(const QString& var) const
372 {
373     QString pattern;
374     QList<QRegExp> subRegExps = getRegExpList(var);
375     QList<QRegExp>::ConstIterator s = subRegExps.constBegin();
376
377     while (s != subRegExps.constEnd()) {
378         if (!(*s).isValid())
379             return *s;
380         if (!pattern.isEmpty())
381             pattern += QLatin1Char('|');
382         pattern += QLatin1String("(?:") + (*s).pattern() + QLatin1Char(')');
383         ++s;
384     }
385     if (pattern.isEmpty())
386         pattern = QLatin1String("$x"); // cannot match
387     return QRegExp(pattern);
388 }
389
390 /*!
391   Looks up the configuration variable \a var in the string list
392   map, converts the string list to a list of regular expressions,
393   and returns it.
394  */
395 QList<QRegExp> Config::getRegExpList(const QString& var) const
396 {
397     QStringList strs = getStringList(var);
398     QStringList::ConstIterator s = strs.constBegin();
399     QList<QRegExp> regExps;
400
401     while (s != strs.constEnd()) {
402         regExps += QRegExp(*s);
403         ++s;
404     }
405     return regExps;
406 }
407
408 /*!
409   This function is slower than it could be. What it does is
410   find all the keys that begin with \a var + dot and return
411   the matching keys in a set, stripped of the matching prefix
412   and dot.
413  */
414 QSet<QString> Config::subVars(const QString& var) const
415 {
416     QSet<QString> result;
417     QString varDot = var + QLatin1Char('.');
418     QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
419     while (v != stringValueMap.constEnd()) {
420         if (v.key().startsWith(varDot)) {
421             QString subVar = v.key().mid(varDot.length());
422             int dot = subVar.indexOf(QLatin1Char('.'));
423             if (dot != -1)
424                 subVar.truncate(dot);
425             result.insert(subVar);
426         }
427         ++v;
428     }
429     return result;
430 }
431
432 /*!
433   Same as subVars(), but in this case we return a string map
434   with the matching keys (stripped of the prefix \a var and
435   mapped to their values. The pairs are inserted into \a t
436  */
437 void Config::subVarsAndValues(const QString& var, QStringMultiMap& t) const
438 {
439     QString varDot = var + QLatin1Char('.');
440     QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
441     while (v != stringValueMap.constEnd()) {
442         if (v.key().startsWith(varDot)) {
443             QString subVar = v.key().mid(varDot.length());
444             int dot = subVar.indexOf(QLatin1Char('.'));
445             if (dot != -1)
446                 subVar.truncate(dot);
447             t.insert(subVar,v.value());
448         }
449         ++v;
450     }
451 }
452
453 /*!
454   Builds and returns a list of file pathnames for the file
455   type specified by \a filesVar (e.g. "headers" or "sources").
456   The files are found in the directories specified by
457   \a dirsVar, and they are filtered by \a defaultNameFilter
458   if a better filter can't be constructed from \a filesVar.
459   The directories in \a excludedDirs are avoided. The files
460   in \a excludedFiles are not included in the return list.
461  */
462 QStringList Config::getAllFiles(const QString &filesVar,
463                                 const QString &dirsVar,
464                                 const QSet<QString> &excludedDirs,
465                                 const QSet<QString> &excludedFiles)
466 {
467     QStringList result = getStringList(filesVar);
468     QStringList dirs = getStringList(dirsVar);
469
470     QString nameFilter = getString(filesVar + dot + QLatin1String(CONFIG_FILEEXTENSIONS));
471
472     QStringList::ConstIterator d = dirs.constBegin();
473     while (d != dirs.constEnd()) {
474         result += getFilesHere(*d, nameFilter, excludedDirs, excludedFiles);
475         ++d;
476     }
477     return result;
478 }
479
480 QStringList Config::getExampleQdocFiles()
481 {
482     QStringList result;
483     QSet<QString> excludedDirs;
484     QSet<QString> excludedFiles;
485     QStringList dirs = getStringList("exampledirs");
486     QString nameFilter = " *.qdoc";
487
488     QStringList::ConstIterator d = dirs.constBegin();
489     while (d != dirs.constEnd()) {
490         result += getFilesHere(*d, nameFilter, excludedDirs, excludedFiles);
491         ++d;
492     }
493     return result;
494 }
495
496 /*!
497   \a fileName is the path of the file to find.
498
499   \a files and \a dirs are the lists where we must find the
500   components of \a fileName.
501
502   \a location is used for obtaining the file and line numbers
503   for report qdoc errors.
504  */
505 QString Config::findFile(const Location& location,
506                          const QStringList& files,
507                          const QStringList& dirs,
508                          const QString& fileName,
509                          QString& userFriendlyFilePath)
510 {
511     if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
512         userFriendlyFilePath = fileName;
513         return fileName;
514     }
515
516     QFileInfo fileInfo;
517     QStringList components = fileName.split(QLatin1Char('?'));
518     QString firstComponent = components.first();
519
520     QStringList::ConstIterator f = files.constBegin();
521     while (f != files.constEnd()) {
522         if (*f == firstComponent ||
523                 (*f).endsWith(QLatin1Char('/') + firstComponent)) {
524             fileInfo.setFile(*f);
525             if (!fileInfo.exists())
526                 location.fatal(tr("File '%1' does not exist").arg(*f));
527             break;
528         }
529         ++f;
530     }
531
532     if (fileInfo.fileName().isEmpty()) {
533         QStringList::ConstIterator d = dirs.constBegin();
534         while (d != dirs.constEnd()) {
535             fileInfo.setFile(QDir(*d), firstComponent);
536             if (fileInfo.exists()) {
537                 break;
538             }
539             ++d;
540         }
541     }
542
543     userFriendlyFilePath = QString();
544     if (!fileInfo.exists())
545         return QString();
546
547     QStringList::ConstIterator c = components.constBegin();
548     for (;;) {
549         bool isArchive = (c != components.constEnd() - 1);
550         QString userFriendly = *c;
551
552         userFriendlyFilePath += userFriendly;
553
554         if (isArchive) {
555             QString extracted = extractedDirs[fileInfo.filePath()];
556             ++c;
557             fileInfo.setFile(QDir(extracted), *c);
558         }
559         else
560             break;
561
562         userFriendlyFilePath += QLatin1Char('?');
563     }
564     return fileInfo.filePath();
565 }
566
567 /*!
568  */
569 QString Config::findFile(const Location& location,
570                          const QStringList& files,
571                          const QStringList& dirs,
572                          const QString& fileBase,
573                          const QStringList& fileExtensions,
574                          QString& userFriendlyFilePath)
575 {
576     QStringList::ConstIterator e = fileExtensions.constBegin();
577     while (e != fileExtensions.constEnd()) {
578         QString filePath = findFile(location,
579                                     files,
580                                     dirs,
581                                     fileBase + QLatin1Char('.') + *e,
582                                     userFriendlyFilePath);
583         if (!filePath.isEmpty())
584             return filePath;
585         ++e;
586     }
587     return findFile(location, files, dirs, fileBase, userFriendlyFilePath);
588 }
589
590 /*!
591   Copies the \a sourceFilePath to the file name constructed by
592   concatenating \a targetDirPath and \a userFriendlySourceFilePath.
593   \a location is for identifying the file and line number where
594   a qdoc error occurred. The constructed output file name is
595   returned.
596  */
597 QString Config::copyFile(const Location& location,
598                          const QString& sourceFilePath,
599                          const QString& userFriendlySourceFilePath,
600                          const QString& targetDirPath)
601 {
602     QFile inFile(sourceFilePath);
603     if (!inFile.open(QFile::ReadOnly)) {
604         location.fatal(tr("Cannot open input file '%1': %2")
605                        .arg(sourceFilePath).arg(inFile.errorString()));
606         return QString();
607     }
608
609     QString outFileName = userFriendlySourceFilePath;
610     int slash = outFileName.lastIndexOf(QLatin1Char('/'));
611     if (slash != -1)
612         outFileName = outFileName.mid(slash);
613
614     QFile outFile(targetDirPath + QLatin1Char('/') + outFileName);
615     if (!outFile.open(QFile::WriteOnly)) {
616         location.fatal(tr("Cannot open output file '%1': %2")
617                        .arg(outFile.fileName()).arg(outFile.errorString()));
618         return QString();
619     }
620
621     char buffer[1024];
622     int len;
623     while ((len = inFile.read(buffer, sizeof(buffer))) > 0) {
624         outFile.write(buffer, len);
625     }
626     return outFileName;
627 }
628
629 /*!
630   Finds the largest unicode digit in \a value in the range
631   1..7 and returns it.
632  */
633 int Config::numParams(const QString& value)
634 {
635     int max = 0;
636     for (int i = 0; i != value.length(); i++) {
637         uint c = value[i].unicode();
638         if (c > 0 && c < 8)
639             max = qMax(max, (int)c);
640     }
641     return max;
642 }
643
644 /*!
645   Removes everything from \a dir. This function is recursive.
646   It doesn't remove \a dir itself, but if it was called
647   recursively, then the caller will remove \a dir.
648  */
649 bool Config::removeDirContents(const QString& dir)
650 {
651     QDir dirInfo(dir);
652     QFileInfoList entries = dirInfo.entryInfoList();
653
654     bool ok = true;
655
656     QFileInfoList::Iterator it = entries.begin();
657     while (it != entries.end()) {
658         if ((*it).isFile()) {
659             if (!dirInfo.remove((*it).fileName()))
660                 ok = false;
661         }
662         else if ((*it).isDir()) {
663             if ((*it).fileName() != QLatin1String(".") && (*it).fileName() != QLatin1String("..")) {
664                 if (removeDirContents((*it).absoluteFilePath())) {
665                     if (!dirInfo.rmdir((*it).fileName()))
666                         ok = false;
667                 }
668                 else {
669                     ok = false;
670                 }
671             }
672         }
673         ++it;
674     }
675     return ok;
676 }
677
678 /*!
679   Returns true if \a ch is a letter, number, '_', '.',
680   '{', '}', or ','.
681  */
682 bool Config::isMetaKeyChar(QChar ch)
683 {
684     return ch.isLetterOrNumber()
685             || ch == QLatin1Char('_')
686             || ch == QLatin1Char('.')
687             || ch == QLatin1Char('{')
688             || ch == QLatin1Char('}')
689             || ch == QLatin1Char(',');
690 }
691
692 /*!
693   Load, parse, and process a qdoc configuration file. This
694   function is only called by the other load() function, but
695   this one is recursive, i.e., it calls itself when it sees
696   an \c{include} statement in the qdoc configuration file.
697  */
698 void Config::load(Location location, const QString& fileName)
699 {
700     QRegExp keySyntax(QLatin1String("\\w+(?:\\.\\w+)*"));
701
702 #define SKIP_CHAR() \
703     do { \
704     location.advance(c); \
705     ++i; \
706     c = text.at(i); \
707     cc = c.unicode(); \
708 } while (0)
709
710 #define SKIP_SPACES() \
711     while (c.isSpace() && cc != '\n') \
712     SKIP_CHAR()
713
714 #define PUT_CHAR() \
715     word += c; \
716     SKIP_CHAR();
717
718     if (location.depth() > 16)
719         location.fatal(tr("Too many nested includes"));
720
721     QFile fin(fileName);
722     if (!fin.open(QFile::ReadOnly | QFile::Text)) {
723         if (!Config::installDir.isEmpty()) {
724             int prefix = location.filePath().length() - location.fileName().length();
725             fin.setFileName(Config::installDir + "/" + fileName.right(fileName.length() - prefix));
726         }
727         if (!fin.open(QFile::ReadOnly | QFile::Text))
728             location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString()));
729     }
730
731     QTextStream stream(&fin);
732     stream.setCodec("UTF-8");
733     QString text = stream.readAll();
734     text += QLatin1String("\n\n");
735     text += QLatin1Char('\0');
736     fin.close();
737
738     location.push(fileName);
739     location.start();
740
741     int i = 0;
742     QChar c = text.at(0);
743     uint cc = c.unicode();
744     while (i < (int) text.length()) {
745         if (cc == 0)
746             ++i;
747         else if (c.isSpace()) {
748             SKIP_CHAR();
749         }
750         else if (cc == '#') {
751             do {
752                 SKIP_CHAR();
753             } while (cc != '\n');
754         }
755         else if (isMetaKeyChar(c)) {
756             Location keyLoc = location;
757             bool plus = false;
758             QString stringValue;
759             QStringList stringListValue;
760             QString word;
761             bool inQuote = false;
762             bool prevWordQuoted = true;
763             bool metWord = false;
764
765             MetaStack stack;
766             do {
767                 stack.process(c, location);
768                 SKIP_CHAR();
769             } while (isMetaKeyChar(c));
770
771             QStringList keys = stack.getExpanded(location);
772             SKIP_SPACES();
773
774             if (keys.count() == 1 && keys.first() == QLatin1String("include")) {
775                 QString includeFile;
776
777                 if (cc != '(')
778                     location.fatal(tr("Bad include syntax"));
779                 SKIP_CHAR();
780                 SKIP_SPACES();
781
782                 while (!c.isSpace() && cc != '#' && cc != ')') {
783
784                     if (cc == '$') {
785                         QString var;
786                         SKIP_CHAR();
787                         while (c.isLetterOrNumber() || cc == '_') {
788                             var += c;
789                             SKIP_CHAR();
790                         }
791                         if (!var.isEmpty()) {
792                             char *val = getenv(var.toLatin1().data());
793                             if (val == 0) {
794                                 location.fatal(tr("Environment variable '%1' undefined").arg(var));
795                             }
796                             else {
797                                 includeFile += QString::fromLatin1(val);
798                             }
799                         }
800                     } else {
801                         includeFile += c;
802                         SKIP_CHAR();
803                     }
804                 }
805                 SKIP_SPACES();
806                 if (cc != ')')
807                     location.fatal(tr("Bad include syntax"));
808                 SKIP_CHAR();
809                 SKIP_SPACES();
810                 if (cc != '#' && cc != '\n')
811                     location.fatal(tr("Trailing garbage"));
812
813                 /*
814                   Here is the recursive call.
815                  */
816                 load(location,
817                      QFileInfo(QFileInfo(fileName).dir(), includeFile)
818                      .filePath());
819             }
820             else {
821                 /*
822                   It wasn't an include statement, so it's something else.
823                  */
824                 if (cc == '+') {
825                     plus = true;
826                     SKIP_CHAR();
827                 }
828                 if (cc != '=')
829                     location.fatal(tr("Expected '=' or '+=' after key"));
830                 SKIP_CHAR();
831                 SKIP_SPACES();
832
833                 for (;;) {
834                     if (cc == '\\') {
835                         int metaCharPos;
836
837                         SKIP_CHAR();
838                         if (cc == '\n') {
839                             SKIP_CHAR();
840                         }
841                         else if (cc > '0' && cc < '8') {
842                             word += QChar(c.digitValue());
843                             SKIP_CHAR();
844                         }
845                         else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) {
846                             word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]);
847                             SKIP_CHAR();
848                         }
849                         else {
850                             PUT_CHAR();
851                         }
852                     }
853                     else if (c.isSpace() || cc == '#') {
854                         if (inQuote) {
855                             if (cc == '\n')
856                                 location.fatal(tr("Unterminated string"));
857                             PUT_CHAR();
858                         }
859                         else {
860                             if (!word.isEmpty()) {
861                                 if (metWord)
862                                     stringValue += QLatin1Char(' ');
863                                 stringValue += word;
864                                 stringListValue << word;
865                                 metWord = true;
866                                 word.clear();
867                                 prevWordQuoted = false;
868                             }
869                             if (cc == '\n' || cc == '#')
870                                 break;
871                             SKIP_SPACES();
872                         }
873                     }
874                     else if (cc == '"') {
875                         if (inQuote) {
876                             if (!prevWordQuoted)
877                                 stringValue += QLatin1Char(' ');
878                             stringValue += word;
879                             if (!word.isEmpty())
880                                 stringListValue << word;
881                             metWord = true;
882                             word.clear();
883                             prevWordQuoted = true;
884                         }
885                         inQuote = !inQuote;
886                         SKIP_CHAR();
887                     }
888                     else if (cc == '$') {
889                         QString var;
890                         SKIP_CHAR();
891                         while (c.isLetterOrNumber() || cc == '_') {
892                             var += c;
893                             SKIP_CHAR();
894                         }
895                         if (!var.isEmpty()) {
896                             char *val = getenv(var.toLatin1().data());
897                             if (val == 0) {
898                                 location.fatal(tr("Environment variable '%1' undefined").arg(var));
899                             }
900                             else {
901                                 word += QString::fromLatin1(val);
902                             }
903                         }
904                     }
905                     else {
906                         if (!inQuote && cc == '=')
907                             location.fatal(tr("Unexpected '='"));
908                         PUT_CHAR();
909                     }
910                 }
911
912                 QStringList::ConstIterator key = keys.constBegin();
913                 while (key != keys.constEnd()) {
914                     if (!keySyntax.exactMatch(*key))
915                         keyLoc.fatal(tr("Invalid key '%1'").arg(*key));
916
917                     if (plus) {
918                         if (locMap[*key].isEmpty()) {
919                             locMap[*key] = keyLoc;
920                         }
921                         else {
922                             locMap[*key].setEtc(true);
923                         }
924                         if (stringValueMap[*key].isEmpty()) {
925                             stringValueMap[*key] = stringValue;
926                         }
927                         else {
928                             stringValueMap[*key] +=
929                                     QLatin1Char(' ') + stringValue;
930                         }
931                         stringListValueMap[*key] += stringListValue;
932                     }
933                     else {
934                         locMap[*key] = keyLoc;
935                         stringValueMap[*key] = stringValue;
936                         stringListValueMap[*key] = stringListValue;
937                     }
938                     ++key;
939                 }
940             }
941         }
942         else {
943             location.fatal(tr("Unexpected character '%1' at beginning of line")
944                            .arg(c));
945         }
946     }
947 }
948
949 QStringList Config::getFilesHere(const QString& uncleanDir,
950                                  const QString& nameFilter,
951                                  const QSet<QString> &excludedDirs,
952                                  const QSet<QString> &excludedFiles)
953 {
954     QString dir = QDir::cleanPath(uncleanDir);
955     QStringList result;
956     if (excludedDirs.contains(dir))
957         return result;
958
959     QDir dirInfo(dir);
960     QStringList fileNames;
961     QStringList::const_iterator fn;
962
963     dirInfo.setNameFilters(nameFilter.split(QLatin1Char(' ')));
964     dirInfo.setSorting(QDir::Name);
965     dirInfo.setFilter(QDir::Files);
966     fileNames = dirInfo.entryList();
967     fn = fileNames.constBegin();
968     while (fn != fileNames.constEnd()) {
969         if (!fn->startsWith(QLatin1Char('~'))) {
970             QString s = dirInfo.filePath(*fn);
971             QString c = QDir::cleanPath(s);
972             if (!excludedFiles.contains(c)) {
973                 result.append(c);
974             }
975         }
976         ++fn;
977     }
978
979     dirInfo.setNameFilters(QStringList(QLatin1String("*")));
980     dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot);
981     fileNames = dirInfo.entryList();
982     fn = fileNames.constBegin();
983     while (fn != fileNames.constEnd()) {
984         result += getFilesHere(dirInfo.filePath(*fn), nameFilter, excludedDirs, excludedFiles);
985         ++fn;
986     }
987     return result;
988 }
989
990 QT_END_NAMESPACE