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