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