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