util/tools/qdoc3/config.cpp
changeset 7 f7bc934e204c
equal deleted inserted replaced
3:41300fa6a67c 7:f7bc934e204c
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2010 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   \a fileName is the path of the file to find.
       
   403 
       
   404   \a files and \a dirs are the lists where we must find the
       
   405   components of \a fileName.
       
   406   
       
   407   \a location is used for obtaining the file and line numbers
       
   408   for report qdoc errors.
       
   409  */
       
   410 QString Config::findFile(const Location& location,
       
   411                          const QStringList& files,
       
   412                          const QStringList& dirs,
       
   413                          const QString& fileName,
       
   414                          QString& userFriendlyFilePath)
       
   415 {
       
   416     if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
       
   417         userFriendlyFilePath = fileName;
       
   418         return fileName;
       
   419     }
       
   420 
       
   421     QFileInfo fileInfo;
       
   422     QStringList components = fileName.split(QLatin1Char('?'));
       
   423     QString firstComponent = components.first();
       
   424 
       
   425     QStringList::ConstIterator f = files.begin();
       
   426     while (f != files.end()) {
       
   427 	if (*f == firstComponent ||
       
   428             (*f).endsWith(QLatin1Char('/') + firstComponent)) {
       
   429 	    fileInfo.setFile(*f);
       
   430 	    if (!fileInfo.exists())
       
   431 		location.fatal(tr("File '%1' does not exist").arg(*f));
       
   432 	    break;
       
   433 	}
       
   434 	++f;
       
   435     }
       
   436 
       
   437     if (fileInfo.fileName().isEmpty()) {
       
   438 	QStringList::ConstIterator d = dirs.begin();
       
   439 	while (d != dirs.end()) {
       
   440 	    fileInfo.setFile(QDir(*d), firstComponent);
       
   441 	    if (fileInfo.exists()) {
       
   442 		break;
       
   443             }
       
   444 	    ++d;
       
   445 	}
       
   446     }
       
   447 
       
   448     userFriendlyFilePath = QString();
       
   449     if (!fileInfo.exists())
       
   450 	    return QString();
       
   451 
       
   452     QStringList::ConstIterator c = components.begin();
       
   453     for (;;) {
       
   454 	bool isArchive = (c != components.end() - 1);
       
   455 	ArchiveExtractor *extractor = 0;
       
   456 	QString userFriendly = *c;
       
   457 
       
   458 	if (isArchive) {
       
   459 	    extractor = ArchiveExtractor::extractorForFileName(userFriendly);
       
   460         }
       
   461 
       
   462 	if (extractor == 0) {
       
   463 	    Uncompressor *uncompressor =
       
   464 		    Uncompressor::uncompressorForFileName(userFriendly);
       
   465 	    if (uncompressor != 0) {
       
   466 		QString fileNameWithCorrectExtension =
       
   467 			uncompressor->uncompressedFilePath(
       
   468 				fileInfo.filePath());
       
   469 		QString uncompressed = uncompressedFiles[fileInfo.filePath()];
       
   470 		if (uncompressed.isEmpty()) {
       
   471 		    uncompressed =
       
   472                         QTemporaryFile(fileInfo.filePath()).fileName();
       
   473 		    uncompressor->uncompressFile(location,
       
   474                                                  fileInfo.filePath(),
       
   475                                                  uncompressed);
       
   476 		    uncompressedFiles[fileInfo.filePath()] = uncompressed;
       
   477 		}
       
   478 		fileInfo.setFile(uncompressed);
       
   479 
       
   480 		if (isArchive) {
       
   481 		    extractor = ArchiveExtractor::extractorForFileName(
       
   482 					fileNameWithCorrectExtension);
       
   483 		}
       
   484                 else {
       
   485 		    userFriendly = fileNameWithCorrectExtension;
       
   486 		}
       
   487 	    }
       
   488 	}
       
   489 	userFriendlyFilePath += userFriendly;
       
   490 
       
   491 	if (isArchive) {
       
   492 	    if (extractor == 0)
       
   493 		location.fatal(tr("Unknown archive type '%1'")
       
   494 				.arg(userFriendlyFilePath));
       
   495 	    QString extracted = extractedDirs[fileInfo.filePath()];
       
   496 	    if (extracted.isEmpty()) {
       
   497 		extracted = QTemporaryFile(fileInfo.filePath()).fileName();
       
   498 		if (!QDir().mkdir(extracted))
       
   499 		    location.fatal(tr("Cannot create temporary directory '%1'")
       
   500 				    .arg(extracted));
       
   501 		extractor->extractArchive(location, fileInfo.filePath(),
       
   502 					   extracted);
       
   503 		extractedDirs[fileInfo.filePath()] = extracted;
       
   504 	    }
       
   505 	    ++c;
       
   506 	    fileInfo.setFile(QDir(extracted), *c);
       
   507 	}
       
   508         else {
       
   509 	    break;
       
   510 	}
       
   511 	userFriendlyFilePath += "?";
       
   512     }
       
   513     return fileInfo.filePath();
       
   514 }
       
   515 
       
   516 /*!
       
   517  */
       
   518 QString Config::findFile(const Location& location,
       
   519                          const QStringList& files,
       
   520                          const QStringList& dirs,
       
   521                          const QString& fileBase,
       
   522                          const QStringList& fileExtensions,
       
   523                          QString& userFriendlyFilePath)
       
   524 {
       
   525     QStringList::ConstIterator e = fileExtensions.begin();
       
   526     while (e != fileExtensions.end()) {
       
   527 	QString filePath = findFile(location, files, dirs, fileBase + "." + *e,
       
   528 				     userFriendlyFilePath);
       
   529 	if (!filePath.isEmpty())
       
   530 	    return filePath;
       
   531 	++e;
       
   532     }
       
   533     return findFile(location, files, dirs, fileBase, userFriendlyFilePath);
       
   534 }
       
   535 
       
   536 /*!
       
   537   Copies the \a sourceFilePath to the file name constructed by
       
   538   concatenating \a targetDirPath and \a userFriendlySourceFilePath.
       
   539   \a location is for identifying the file and line number where
       
   540   a qdoc error occurred. The constructed output file name is
       
   541   returned.
       
   542  */
       
   543 QString Config::copyFile(const Location& location,
       
   544                          const QString& sourceFilePath,
       
   545                          const QString& userFriendlySourceFilePath,
       
   546                          const QString& targetDirPath)
       
   547 {
       
   548     QFile inFile(sourceFilePath);
       
   549     if (!inFile.open(QFile::ReadOnly)) {
       
   550 	location.fatal(tr("Cannot open input file '%1': %2")
       
   551 			.arg(inFile.fileName()).arg(inFile.errorString()));
       
   552 	return "";
       
   553     }
       
   554 
       
   555     QString outFileName = userFriendlySourceFilePath;
       
   556     int slash = outFileName.lastIndexOf("/");
       
   557     if (slash != -1)
       
   558 	outFileName = outFileName.mid(slash);
       
   559 
       
   560     QFile outFile(targetDirPath + "/" + outFileName);
       
   561     if (!outFile.open(QFile::WriteOnly)) {
       
   562 	location.fatal(tr("Cannot open output file '%1': %2")
       
   563 			.arg(outFile.fileName()).arg(outFile.errorString()));
       
   564 	return "";
       
   565     }
       
   566 
       
   567     char buffer[1024];
       
   568     int len;
       
   569     while ((len = inFile.read(buffer, sizeof(buffer))) > 0) {
       
   570 	outFile.write(buffer, len);
       
   571     }
       
   572     return outFileName;
       
   573 }
       
   574 
       
   575 /*!
       
   576   Finds the largest unicode digit in \a value in the range
       
   577   1..7 and returns it.
       
   578  */
       
   579 int Config::numParams(const QString& value)
       
   580 {
       
   581     int max = 0;
       
   582     for (int i = 0; i != value.length(); i++) {
       
   583         uint c = value[i].unicode();
       
   584         if (c > 0 && c < 8)
       
   585             max = qMax(max, (int)c);
       
   586     }
       
   587     return max;
       
   588 }
       
   589 
       
   590 /*!
       
   591   Removes everything from \a dir. This function is recursive.
       
   592   It doesn't remove \a dir itself, but if it was called
       
   593   recursively, then the caller will remove \a dir.
       
   594  */
       
   595 bool Config::removeDirContents(const QString& dir)
       
   596 {
       
   597     QDir dirInfo(dir);
       
   598     QFileInfoList entries = dirInfo.entryInfoList();
       
   599 
       
   600     bool ok = true;
       
   601 
       
   602     QFileInfoList::Iterator it = entries.begin();
       
   603     while (it != entries.end()) {
       
   604 	if ((*it).isFile()) {
       
   605 	    if (!dirInfo.remove((*it).fileName()))
       
   606 		ok = false;
       
   607 	}
       
   608         else if ((*it).isDir()) {
       
   609 	    if ((*it).fileName() != "." && (*it).fileName() != "..") {
       
   610 		if (removeDirContents((*it).absoluteFilePath())) {
       
   611 		    if (!dirInfo.rmdir((*it).fileName()))
       
   612 			ok = false;
       
   613 		}
       
   614                 else {
       
   615 		    ok = false;
       
   616 		}
       
   617 	    }
       
   618 	}
       
   619 	++it;
       
   620     }
       
   621     return ok;
       
   622 }
       
   623 
       
   624 /*!
       
   625   Returns true if \a ch is a letter, number, '_', '.',
       
   626   '{', '}', or ','.
       
   627  */
       
   628 bool Config::isMetaKeyChar(QChar ch)
       
   629 {
       
   630     return ch.isLetterOrNumber()
       
   631         || ch == QLatin1Char('_')
       
   632         || ch == QLatin1Char('.')
       
   633         || ch == QLatin1Char('{')
       
   634         || ch == QLatin1Char('}')
       
   635         || ch == QLatin1Char(',');
       
   636 }
       
   637 
       
   638 /*!
       
   639   Load, parse, and process a qdoc configuration file. This
       
   640   function is only called by the other load() function, but
       
   641   this one is recursive, i.e., it calls itself when it sees
       
   642   an \c{include} statement in the qdog configuration file.
       
   643  */
       
   644 void Config::load(Location location, const QString& fileName)
       
   645 {
       
   646     QRegExp keySyntax("\\w+(?:\\.\\w+)*");
       
   647 
       
   648 #define SKIP_CHAR() \
       
   649     do { \
       
   650         location.advance(c); \
       
   651         ++i; \
       
   652         c = text.at(i); \
       
   653         cc = c.unicode(); \
       
   654     } while (0)
       
   655 
       
   656 #define SKIP_SPACES() \
       
   657     while (c.isSpace() && cc != '\n') \
       
   658         SKIP_CHAR()
       
   659 
       
   660 #define PUT_CHAR() \
       
   661     word += c; \
       
   662     SKIP_CHAR();
       
   663 
       
   664     if (location.depth() > 16)
       
   665         location.fatal(tr("Too many nested includes"));
       
   666 
       
   667     QFile fin(fileName);
       
   668     if (!fin.open(QFile::ReadOnly | QFile::Text)) {
       
   669         fin.setFileName(fileName + ".qdoc");
       
   670         if (!fin.open(QFile::ReadOnly | QFile::Text))
       
   671             location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString()));
       
   672     }
       
   673 
       
   674     QString text = fin.readAll();
       
   675     text += QLatin1String("\n\n");
       
   676     text += QChar('\0');
       
   677     fin.close();
       
   678 
       
   679     location.push(fileName);
       
   680     location.start();
       
   681 
       
   682     int i = 0;
       
   683     QChar c = text.at(0);
       
   684     uint cc = c.unicode();
       
   685     while (i < (int) text.length()) {
       
   686         if (cc == 0)
       
   687             ++i;
       
   688         else if (c.isSpace()) {
       
   689             SKIP_CHAR();
       
   690         }
       
   691         else if (cc == '#') {
       
   692             do {
       
   693                 SKIP_CHAR();
       
   694             } while (cc != '\n');
       
   695         }
       
   696         else if (isMetaKeyChar(c)) {
       
   697             Location keyLoc = location;
       
   698             bool plus = false;
       
   699             QString stringValue;
       
   700             QStringList stringListValue;
       
   701             QString word;
       
   702             bool inQuote = false;
       
   703             bool prevWordQuoted = true;
       
   704             bool metWord = false;
       
   705 
       
   706             MetaStack stack;
       
   707             do {
       
   708                 stack.process(c, location);
       
   709                 SKIP_CHAR();
       
   710             } while (isMetaKeyChar(c));
       
   711 
       
   712             QStringList keys = stack.getExpanded(location);
       
   713             SKIP_SPACES();
       
   714 
       
   715             if (keys.count() == 1 && keys.first() == "include") {
       
   716                 QString includeFile;
       
   717 
       
   718                 if (cc != '(')
       
   719                     location.fatal(tr("Bad include syntax"));
       
   720                 SKIP_CHAR();
       
   721                 SKIP_SPACES();
       
   722                 while (!c.isSpace() && cc != '#' && cc != ')') {
       
   723                     includeFile += c;
       
   724                     SKIP_CHAR();
       
   725                 }
       
   726                 SKIP_SPACES();
       
   727                 if (cc != ')')
       
   728                     location.fatal(tr("Bad include syntax"));
       
   729                 SKIP_CHAR();
       
   730                 SKIP_SPACES();
       
   731                 if (cc != '#' && cc != '\n')
       
   732                     location.fatal(tr("Trailing garbage"));
       
   733 
       
   734                 /*
       
   735                   Here is the recursive call.
       
   736                  */
       
   737                 load(location,
       
   738                       QFileInfo(QFileInfo(fileName).dir(), includeFile)
       
   739                       .filePath());
       
   740             }
       
   741             else {
       
   742                 /*
       
   743                   It wasn't an include statement, so it;s something else.
       
   744                  */
       
   745                 if (cc == '+') {
       
   746                     plus = true;
       
   747                     SKIP_CHAR();
       
   748                 }
       
   749                 if (cc != '=')
       
   750                     location.fatal(tr("Expected '=' or '+=' after key"));
       
   751                 SKIP_CHAR();
       
   752                 SKIP_SPACES();
       
   753 
       
   754                 for (;;) {
       
   755                     if (cc == '\\') {
       
   756                         int metaCharPos;
       
   757 
       
   758                         SKIP_CHAR();
       
   759                         if (cc == '\n') {
       
   760                             SKIP_CHAR();
       
   761                         }
       
   762                         else if (cc > '0' && cc < '8') {
       
   763                             word += QChar(c.digitValue());
       
   764                             SKIP_CHAR();
       
   765                         }
       
   766                         else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) {
       
   767                             word += "\a\b\f\n\r\t\v"[metaCharPos];
       
   768                             SKIP_CHAR();
       
   769                         }
       
   770                         else {
       
   771                             PUT_CHAR();
       
   772                         }
       
   773                     }
       
   774                     else if (c.isSpace() || cc == '#') {
       
   775                         if (inQuote) {
       
   776                             if (cc == '\n')
       
   777                                 location.fatal(tr("Unterminated string"));
       
   778                             PUT_CHAR();
       
   779                         }
       
   780                         else {
       
   781                             if (!word.isEmpty()) {
       
   782                                 if (metWord)
       
   783                                     stringValue += QLatin1Char(' ');
       
   784                                 stringValue += word;
       
   785                                 stringListValue << word;
       
   786                                 metWord = true;
       
   787                                 word.clear();
       
   788                                 prevWordQuoted = false;
       
   789                             }
       
   790                             if (cc == '\n' || cc == '#')
       
   791                                 break;
       
   792                             SKIP_SPACES();
       
   793                         }
       
   794                     }
       
   795                     else if (cc == '"') {
       
   796                         if (inQuote) {
       
   797                             if (!prevWordQuoted)
       
   798                                 stringValue += QLatin1Char(' ');
       
   799                             stringValue += word;
       
   800                             if (!word.isEmpty())
       
   801                                 stringListValue << word;
       
   802                             metWord = true;
       
   803                             word.clear();
       
   804                             prevWordQuoted = true;
       
   805                         }
       
   806                         inQuote = !inQuote;
       
   807                         SKIP_CHAR();
       
   808                     }
       
   809                     else if (cc == '$') {
       
   810                         QString var;
       
   811                         SKIP_CHAR();
       
   812                         while (c.isLetterOrNumber() || cc == '_') {
       
   813                             var += c;
       
   814                             SKIP_CHAR();
       
   815                         }
       
   816                         if (!var.isEmpty()) {
       
   817                             char *val = getenv(var.toLatin1().data());
       
   818                             if (val == 0) {
       
   819                                 location.fatal(tr("Environment variable '%1' undefined").arg(var));
       
   820                             }
       
   821                             else {
       
   822                                 word += QString(val);
       
   823                             }
       
   824                         }
       
   825                     }
       
   826                     else {
       
   827                         if (!inQuote && cc == '=')
       
   828                             location.fatal(tr("Unexpected '='"));
       
   829                         PUT_CHAR();
       
   830                     }
       
   831                 }
       
   832 
       
   833                 QStringList::ConstIterator key = keys.begin();
       
   834                 while (key != keys.end()) {
       
   835                     if (!keySyntax.exactMatch(*key))
       
   836                         keyLoc.fatal(tr("Invalid key '%1'").arg(*key));
       
   837 
       
   838                     if (plus) {
       
   839                         if (locMap[*key].isEmpty()) {
       
   840                             locMap[*key] = keyLoc;
       
   841                         }
       
   842                         else {
       
   843                             locMap[*key].setEtc(true);
       
   844                         }
       
   845                         if (stringValueMap[*key].isEmpty()) {
       
   846                             stringValueMap[*key] = stringValue;
       
   847                         }
       
   848                         else {
       
   849                             stringValueMap[*key] +=
       
   850                                 QLatin1Char(' ') + stringValue;
       
   851                         }
       
   852                         stringListValueMap[*key] += stringListValue;
       
   853                     }
       
   854                     else {
       
   855                         locMap[*key] = keyLoc;
       
   856                         stringValueMap[*key] = stringValue;
       
   857                         stringListValueMap[*key] = stringListValue;
       
   858                     }
       
   859                     ++key;
       
   860                 }
       
   861             }
       
   862         }
       
   863         else {
       
   864             location.fatal(tr("Unexpected character '%1' at beginning of line")
       
   865                             .arg(c));
       
   866         }
       
   867     }
       
   868 }
       
   869 
       
   870 QStringList Config::getFilesHere(const QString& dir,
       
   871                                  const QString& nameFilter,
       
   872                                  const QSet<QString> &excludedDirs)
       
   873 {
       
   874     QStringList result;
       
   875     if (excludedDirs.contains(dir))
       
   876         return result;
       
   877 
       
   878     QDir dirInfo(dir);
       
   879     QStringList fileNames;
       
   880     QStringList::const_iterator fn;
       
   881 
       
   882     dirInfo.setNameFilters(nameFilter.split(' '));
       
   883     dirInfo.setSorting(QDir::Name);
       
   884     dirInfo.setFilter(QDir::Files);
       
   885     fileNames = dirInfo.entryList();
       
   886     fn = fileNames.constBegin();
       
   887     while (fn != fileNames.constEnd()) {
       
   888         if (!fn->startsWith(QLatin1Char('~')))
       
   889             result.append(dirInfo.filePath(*fn));
       
   890 	++fn;
       
   891     }    
       
   892     
       
   893     dirInfo.setNameFilters(QStringList("*"));
       
   894     dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot);
       
   895     fileNames = dirInfo.entryList();
       
   896     fn = fileNames.constBegin();
       
   897     while (fn != fileNames.constEnd()) {
       
   898         result += getFilesHere(dirInfo.filePath(*fn), nameFilter, excludedDirs);
       
   899 	++fn;
       
   900     }
       
   901     return result;
       
   902 }
       
   903 
       
   904 QT_END_NAMESPACE