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