1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the tools applications of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
49 #include <QTemporaryFile>
50 #include <QTextStream>
58 An entry on the MetaStack.
72 void MetaStackEntry::open()
74 next.append(QString());
79 void MetaStackEntry::close()
88 class MetaStack : private QStack<MetaStackEntry>
93 void process(QChar ch, const Location& location);
94 QStringList getExpanded(const Location& location);
97 MetaStack::MetaStack()
99 push(MetaStackEntry());
103 void MetaStack::process(QChar ch, const Location& location)
105 if (ch == QLatin1Char('{')) {
106 push(MetaStackEntry());
109 else if (ch == QLatin1Char('}')) {
111 location.fatal(tr("Unexpected '}'"));
114 QStringList suffixes = pop().accum;
115 QStringList prefixes = top().next;
118 QStringList::ConstIterator pre = prefixes.constBegin();
119 while (pre != prefixes.constEnd()) {
120 QStringList::ConstIterator suf = suffixes.constBegin();
121 while (suf != suffixes.constEnd()) {
122 top().next << (*pre + *suf);
128 else if (ch == QLatin1Char(',') && count() > 1) {
133 QStringList::Iterator pre = top().next.begin();
134 while (pre != top().next.end()) {
141 QStringList MetaStack::getExpanded(const Location& location)
144 location.fatal(tr("Missing '}'"));
150 QT_STATIC_CONST_IMPL QString Config::dot = QLatin1String(".");
151 bool Config::generateExamples = true;
152 QString Config::overrideOutputDir;
153 QString Config::installDir;
154 QSet<QString> Config::overrideOutputFormats;
155 QMap<QString, QString> Config::extractedDirs;
156 int Config::numInstances;
160 \brief The Config class contains the configuration variables
161 for controlling how qdoc produces documentation.
163 Its load() function, reads, parses, and processes a qdocconf file.
167 The constructor sets the \a programName and initializes all
168 internal state variables to empty values.
170 Config::Config(const QString& programName)
173 loc = Location::null;
174 lastLoc = Location::null;
176 stringValueMap.clear();
177 stringListValueMap.clear();
182 The destructor has nothing special to do.
189 Loads and parses the qdoc configuration file \a fileName.
190 This function calls the other load() function, which does
191 the loading, parsing, and processing of the configuration
194 Intializes the location variables returned by location()
197 void Config::load(const QString& fileName)
199 load(Location::null, fileName);
201 loc = Location(fileName);
206 lastLoc = Location::null;
210 Writes the qdoc configuration data to the named file.
211 The previous contents of the file are overwritten.
213 void Config::unload(const QString& fileName)
215 QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
216 while (v != stringValueMap.constEnd()) {
217 qDebug() << v.key() << " = " << v.value();
220 qDebug() << "fileName:" << fileName;
223 Joins all the strings in \a values into a single string with the
224 individual \a values separated by ' '. Then it inserts the result
225 into the string list map with \a var as the key.
227 It also inserts the \a values string list into a separate map,
228 also with \a var as the key.
230 void Config::setStringList(const QString& var, const QStringList& values)
232 stringValueMap[var] = values.join(QLatin1String(" "));
233 stringListValueMap[var] = values;
237 Looks up the configuarion variable \a var in the string
238 map and returns the boolean value.
240 bool Config::getBool(const QString& var) const
242 return QVariant(getString(var)).toBool();
246 Looks up the configuration variable \a var in the string list
247 map. Iterates through the string list found, interpreting each
248 string in the list as an integer and adding it to a total sum.
251 int Config::getInt(const QString& var) const
253 QStringList strs = getStringList(var);
254 QStringList::ConstIterator s = strs.constBegin();
257 while (s != strs.constEnd()) {
265 Function to return the correct outputdir.
266 outputdir can be set using the qdocconf or the command-line
269 QString Config::getOutputDir() const
271 if (overrideOutputDir.isNull())
272 return getString(QLatin1String(CONFIG_OUTPUTDIR));
274 return overrideOutputDir;
278 Function to return the correct outputformats.
279 outputformats can be set using the qdocconf or the command-line
280 variable -outputformat.
282 QSet<QString> Config::getOutputFormats() const
284 if (overrideOutputFormats.isEmpty())
285 return getStringSet(QLatin1String(CONFIG_OUTPUTFORMATS));
287 return overrideOutputFormats;
291 First, this function looks up the configuration variable \a var
292 in the location map and, if found, sets the internal variable
293 \c{lastLoc} to the Location that \a var maps to.
295 Then it looks up the configuration variable \a var in the string
296 map, and returns the string that \a var maps to.
298 QString Config::getString(const QString& var) const
300 if (!locMap[var].isEmpty())
301 (Location&) lastLoc = locMap[var];
302 return stringValueMap[var];
306 Looks up the configuration variable \a var in the string
307 list map, converts the string list it maps to into a set
308 of strings, and returns the set.
310 QSet<QString> Config::getStringSet(const QString& var) const
312 return QSet<QString>::fromList(getStringList(var));
316 First, this function looks up the configuration variable \a var
317 in the location map and, if found, sets the internal variable
318 \c{lastLoc} the Location that \a var maps to.
320 Then it looks up the configuration variable \a var in the string
321 list map, and returns the string list that \a var maps to.
323 QStringList Config::getStringList(const QString& var) const
325 if (!locMap[var].isEmpty())
326 (Location&) lastLoc = locMap[var];
327 return stringListValueMap[var];
331 This function should only be called when the configuration
332 variable \a var maps to a string list that contains file paths.
333 It cleans the paths with QDir::cleanPath() before returning
336 First, this function looks up the configuration variable \a var
337 in the location map and, if found, sets the internal variable
338 \c{lastLoc} the Location that \a var maps to.
340 Then it looks up the configuration variable \a var in the string
341 list map, which maps to a string list that contains file paths.
342 These paths might not be clean, so QDir::cleanPath() is called
343 for each one. The string list returned contains cleaned paths.
345 QStringList Config::getCleanPathList(const QString& var) const
347 if (!locMap[var].isEmpty())
348 (Location&) lastLoc = locMap[var];
350 QMap<QString,QStringList>::const_iterator it = stringListValueMap.constFind(var);
351 if (it != stringListValueMap.constEnd()) {
352 const QStringList& sl = it.value();
354 t.reserve(sl.size());
355 for (int i=0; i<sl.size(); ++i) {
356 t.append(QDir::cleanPath(sl[i]));
364 Calls getRegExpList() with the control variable \a var and
365 iterates through the resulting list of regular expressions,
366 concatening them with some extras characters to form a single
367 QRegExp, which is returned/
371 QRegExp Config::getRegExp(const QString& var) const
374 QList<QRegExp> subRegExps = getRegExpList(var);
375 QList<QRegExp>::ConstIterator s = subRegExps.constBegin();
377 while (s != subRegExps.constEnd()) {
380 if (!pattern.isEmpty())
381 pattern += QLatin1Char('|');
382 pattern += QLatin1String("(?:") + (*s).pattern() + QLatin1Char(')');
385 if (pattern.isEmpty())
386 pattern = QLatin1String("$x"); // cannot match
387 return QRegExp(pattern);
391 Looks up the configuration variable \a var in the string list
392 map, converts the string list to a list of regular expressions,
395 QList<QRegExp> Config::getRegExpList(const QString& var) const
397 QStringList strs = getStringList(var);
398 QStringList::ConstIterator s = strs.constBegin();
399 QList<QRegExp> regExps;
401 while (s != strs.constEnd()) {
402 regExps += QRegExp(*s);
409 This function is slower than it could be. What it does is
410 find all the keys that begin with \a var + dot and return
411 the matching keys in a set, stripped of the matching prefix
414 QSet<QString> Config::subVars(const QString& var) const
416 QSet<QString> result;
417 QString varDot = var + QLatin1Char('.');
418 QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
419 while (v != stringValueMap.constEnd()) {
420 if (v.key().startsWith(varDot)) {
421 QString subVar = v.key().mid(varDot.length());
422 int dot = subVar.indexOf(QLatin1Char('.'));
424 subVar.truncate(dot);
425 result.insert(subVar);
433 Same as subVars(), but in this case we return a string map
434 with the matching keys (stripped of the prefix \a var and
435 mapped to their values. The pairs are inserted into \a t
437 void Config::subVarsAndValues(const QString& var, QStringMultiMap& t) const
439 QString varDot = var + QLatin1Char('.');
440 QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
441 while (v != stringValueMap.constEnd()) {
442 if (v.key().startsWith(varDot)) {
443 QString subVar = v.key().mid(varDot.length());
444 int dot = subVar.indexOf(QLatin1Char('.'));
446 subVar.truncate(dot);
447 t.insert(subVar,v.value());
454 Builds and returns a list of file pathnames for the file
455 type specified by \a filesVar (e.g. "headers" or "sources").
456 The files are found in the directories specified by
457 \a dirsVar, and they are filtered by \a defaultNameFilter
458 if a better filter can't be constructed from \a filesVar.
459 The directories in \a excludedDirs are avoided. The files
460 in \a excludedFiles are not included in the return list.
462 QStringList Config::getAllFiles(const QString &filesVar,
463 const QString &dirsVar,
464 const QSet<QString> &excludedDirs,
465 const QSet<QString> &excludedFiles)
467 QStringList result = getStringList(filesVar);
468 QStringList dirs = getStringList(dirsVar);
470 QString nameFilter = getString(filesVar + dot + QLatin1String(CONFIG_FILEEXTENSIONS));
472 QStringList::ConstIterator d = dirs.constBegin();
473 while (d != dirs.constEnd()) {
474 result += getFilesHere(*d, nameFilter, excludedDirs, excludedFiles);
481 \a fileName is the path of the file to find.
483 \a files and \a dirs are the lists where we must find the
484 components of \a fileName.
486 \a location is used for obtaining the file and line numbers
487 for report qdoc errors.
489 QString Config::findFile(const Location& location,
490 const QStringList& files,
491 const QStringList& dirs,
492 const QString& fileName,
493 QString& userFriendlyFilePath)
495 if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
496 userFriendlyFilePath = fileName;
501 QStringList components = fileName.split(QLatin1Char('?'));
502 QString firstComponent = components.first();
504 QStringList::ConstIterator f = files.constBegin();
505 while (f != files.constEnd()) {
506 if (*f == firstComponent ||
507 (*f).endsWith(QLatin1Char('/') + firstComponent)) {
508 fileInfo.setFile(*f);
509 if (!fileInfo.exists())
510 location.fatal(tr("File '%1' does not exist").arg(*f));
516 if (fileInfo.fileName().isEmpty()) {
517 QStringList::ConstIterator d = dirs.constBegin();
518 while (d != dirs.constEnd()) {
519 fileInfo.setFile(QDir(*d), firstComponent);
520 if (fileInfo.exists()) {
527 userFriendlyFilePath = QString();
528 if (!fileInfo.exists())
531 QStringList::ConstIterator c = components.constBegin();
533 bool isArchive = (c != components.constEnd() - 1);
534 QString userFriendly = *c;
536 userFriendlyFilePath += userFriendly;
539 QString extracted = extractedDirs[fileInfo.filePath()];
541 fileInfo.setFile(QDir(extracted), *c);
546 userFriendlyFilePath += QLatin1Char('?');
548 return fileInfo.filePath();
553 QString Config::findFile(const Location& location,
554 const QStringList& files,
555 const QStringList& dirs,
556 const QString& fileBase,
557 const QStringList& fileExtensions,
558 QString& userFriendlyFilePath)
560 QStringList::ConstIterator e = fileExtensions.constBegin();
561 while (e != fileExtensions.constEnd()) {
562 QString filePath = findFile(location,
565 fileBase + QLatin1Char('.') + *e,
566 userFriendlyFilePath);
567 if (!filePath.isEmpty())
571 return findFile(location, files, dirs, fileBase, userFriendlyFilePath);
575 Copies the \a sourceFilePath to the file name constructed by
576 concatenating \a targetDirPath and \a userFriendlySourceFilePath.
577 \a location is for identifying the file and line number where
578 a qdoc error occurred. The constructed output file name is
581 QString Config::copyFile(const Location& location,
582 const QString& sourceFilePath,
583 const QString& userFriendlySourceFilePath,
584 const QString& targetDirPath)
586 QFile inFile(sourceFilePath);
587 if (!inFile.open(QFile::ReadOnly)) {
588 location.fatal(tr("Cannot open input file '%1': %2")
589 .arg(sourceFilePath).arg(inFile.errorString()));
593 QString outFileName = userFriendlySourceFilePath;
594 int slash = outFileName.lastIndexOf(QLatin1Char('/'));
596 outFileName = outFileName.mid(slash);
598 QFile outFile(targetDirPath + QLatin1Char('/') + outFileName);
599 if (!outFile.open(QFile::WriteOnly)) {
600 location.fatal(tr("Cannot open output file '%1': %2")
601 .arg(outFile.fileName()).arg(outFile.errorString()));
607 while ((len = inFile.read(buffer, sizeof(buffer))) > 0) {
608 outFile.write(buffer, len);
614 Finds the largest unicode digit in \a value in the range
617 int Config::numParams(const QString& value)
620 for (int i = 0; i != value.length(); i++) {
621 uint c = value[i].unicode();
623 max = qMax(max, (int)c);
629 Removes everything from \a dir. This function is recursive.
630 It doesn't remove \a dir itself, but if it was called
631 recursively, then the caller will remove \a dir.
633 bool Config::removeDirContents(const QString& dir)
636 QFileInfoList entries = dirInfo.entryInfoList();
640 QFileInfoList::Iterator it = entries.begin();
641 while (it != entries.end()) {
642 if ((*it).isFile()) {
643 if (!dirInfo.remove((*it).fileName()))
646 else if ((*it).isDir()) {
647 if ((*it).fileName() != QLatin1String(".") && (*it).fileName() != QLatin1String("..")) {
648 if (removeDirContents((*it).absoluteFilePath())) {
649 if (!dirInfo.rmdir((*it).fileName()))
663 Returns true if \a ch is a letter, number, '_', '.',
666 bool Config::isMetaKeyChar(QChar ch)
668 return ch.isLetterOrNumber()
669 || ch == QLatin1Char('_')
670 || ch == QLatin1Char('.')
671 || ch == QLatin1Char('{')
672 || ch == QLatin1Char('}')
673 || ch == QLatin1Char(',');
677 Load, parse, and process a qdoc configuration file. This
678 function is only called by the other load() function, but
679 this one is recursive, i.e., it calls itself when it sees
680 an \c{include} statement in the qdoc configuration file.
682 void Config::load(Location location, const QString& fileName)
684 QRegExp keySyntax(QLatin1String("\\w+(?:\\.\\w+)*"));
686 #define SKIP_CHAR() \
688 location.advance(c); \
694 #define SKIP_SPACES() \
695 while (c.isSpace() && cc != '\n') \
702 if (location.depth() > 16)
703 location.fatal(tr("Too many nested includes"));
706 if (!fin.open(QFile::ReadOnly | QFile::Text)) {
707 if (!Config::installDir.isEmpty()) {
708 int prefix = location.filePath().length() - location.fileName().length();
709 fin.setFileName(Config::installDir + "/" + fileName.right(fileName.length() - prefix));
711 if (!fin.open(QFile::ReadOnly | QFile::Text))
712 location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString()));
715 QTextStream stream(&fin);
716 stream.setCodec("UTF-8");
717 QString text = stream.readAll();
718 text += QLatin1String("\n\n");
719 text += QLatin1Char('\0');
722 location.push(fileName);
726 QChar c = text.at(0);
727 uint cc = c.unicode();
728 while (i < (int) text.length()) {
731 else if (c.isSpace()) {
734 else if (cc == '#') {
737 } while (cc != '\n');
739 else if (isMetaKeyChar(c)) {
740 Location keyLoc = location;
743 QStringList stringListValue;
745 bool inQuote = false;
746 bool prevWordQuoted = true;
747 bool metWord = false;
751 stack.process(c, location);
753 } while (isMetaKeyChar(c));
755 QStringList keys = stack.getExpanded(location);
758 if (keys.count() == 1 && keys.first() == QLatin1String("include")) {
762 location.fatal(tr("Bad include syntax"));
766 while (!c.isSpace() && cc != '#' && cc != ')') {
771 while (c.isLetterOrNumber() || cc == '_') {
775 if (!var.isEmpty()) {
776 char *val = getenv(var.toLatin1().data());
778 location.fatal(tr("Environment variable '%1' undefined").arg(var));
781 includeFile += QString::fromLatin1(val);
791 location.fatal(tr("Bad include syntax"));
794 if (cc != '#' && cc != '\n')
795 location.fatal(tr("Trailing garbage"));
798 Here is the recursive call.
801 QFileInfo(QFileInfo(fileName).dir(), includeFile)
806 It wasn't an include statement, so it's something else.
813 location.fatal(tr("Expected '=' or '+=' after key"));
825 else if (cc > '0' && cc < '8') {
826 word += QChar(c.digitValue());
829 else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) {
830 word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]);
837 else if (c.isSpace() || cc == '#') {
840 location.fatal(tr("Unterminated string"));
844 if (!word.isEmpty()) {
846 stringValue += QLatin1Char(' ');
848 stringListValue << word;
851 prevWordQuoted = false;
853 if (cc == '\n' || cc == '#')
858 else if (cc == '"') {
861 stringValue += QLatin1Char(' ');
864 stringListValue << word;
867 prevWordQuoted = true;
872 else if (cc == '$') {
875 while (c.isLetterOrNumber() || cc == '_') {
879 if (!var.isEmpty()) {
880 char *val = getenv(var.toLatin1().data());
882 location.fatal(tr("Environment variable '%1' undefined").arg(var));
885 word += QString::fromLatin1(val);
890 if (!inQuote && cc == '=')
891 location.fatal(tr("Unexpected '='"));
896 QStringList::ConstIterator key = keys.constBegin();
897 while (key != keys.constEnd()) {
898 if (!keySyntax.exactMatch(*key))
899 keyLoc.fatal(tr("Invalid key '%1'").arg(*key));
902 if (locMap[*key].isEmpty()) {
903 locMap[*key] = keyLoc;
906 locMap[*key].setEtc(true);
908 if (stringValueMap[*key].isEmpty()) {
909 stringValueMap[*key] = stringValue;
912 stringValueMap[*key] +=
913 QLatin1Char(' ') + stringValue;
915 stringListValueMap[*key] += stringListValue;
918 locMap[*key] = keyLoc;
919 stringValueMap[*key] = stringValue;
920 stringListValueMap[*key] = stringListValue;
927 location.fatal(tr("Unexpected character '%1' at beginning of line")
933 QStringList Config::getFilesHere(const QString& uncleanDir,
934 const QString& nameFilter,
935 const QSet<QString> &excludedDirs,
936 const QSet<QString> &excludedFiles)
938 QString dir = QDir::cleanPath(uncleanDir);
940 if (excludedDirs.contains(dir))
944 QStringList fileNames;
945 QStringList::const_iterator fn;
947 dirInfo.setNameFilters(nameFilter.split(QLatin1Char(' ')));
948 dirInfo.setSorting(QDir::Name);
949 dirInfo.setFilter(QDir::Files);
950 fileNames = dirInfo.entryList();
951 fn = fileNames.constBegin();
952 while (fn != fileNames.constEnd()) {
953 if (!fn->startsWith(QLatin1Char('~'))) {
954 QString s = dirInfo.filePath(*fn);
955 QString c = QDir::cleanPath(s);
956 if (!excludedFiles.contains(c)) {
963 dirInfo.setNameFilters(QStringList(QLatin1String("*")));
964 dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot);
965 fileNames = dirInfo.entryList();
966 fn = fileNames.constBegin();
967 while (fn != fileNames.constEnd()) {
968 result += getFilesHere(dirInfo.filePath(*fn), nameFilter, excludedDirs, excludedFiles);