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