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} to 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);
480 QStringList Config::getExampleQdocFiles()
483 QSet<QString> excludedDirs;
484 QSet<QString> excludedFiles;
485 QStringList dirs = getStringList("exampledirs");
486 QString nameFilter = " *.qdoc";
488 QStringList::ConstIterator d = dirs.constBegin();
489 while (d != dirs.constEnd()) {
490 result += getFilesHere(*d, nameFilter, excludedDirs, excludedFiles);
497 \a fileName is the path of the file to find.
499 \a files and \a dirs are the lists where we must find the
500 components of \a fileName.
502 \a location is used for obtaining the file and line numbers
503 for report qdoc errors.
505 QString Config::findFile(const Location& location,
506 const QStringList& files,
507 const QStringList& dirs,
508 const QString& fileName,
509 QString& userFriendlyFilePath)
511 if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
512 userFriendlyFilePath = fileName;
517 QStringList components = fileName.split(QLatin1Char('?'));
518 QString firstComponent = components.first();
520 QStringList::ConstIterator f = files.constBegin();
521 while (f != files.constEnd()) {
522 if (*f == firstComponent ||
523 (*f).endsWith(QLatin1Char('/') + firstComponent)) {
524 fileInfo.setFile(*f);
525 if (!fileInfo.exists())
526 location.fatal(tr("File '%1' does not exist").arg(*f));
532 if (fileInfo.fileName().isEmpty()) {
533 QStringList::ConstIterator d = dirs.constBegin();
534 while (d != dirs.constEnd()) {
535 fileInfo.setFile(QDir(*d), firstComponent);
536 if (fileInfo.exists()) {
543 userFriendlyFilePath = QString();
544 if (!fileInfo.exists())
547 QStringList::ConstIterator c = components.constBegin();
549 bool isArchive = (c != components.constEnd() - 1);
550 QString userFriendly = *c;
552 userFriendlyFilePath += userFriendly;
555 QString extracted = extractedDirs[fileInfo.filePath()];
557 fileInfo.setFile(QDir(extracted), *c);
562 userFriendlyFilePath += QLatin1Char('?');
564 return fileInfo.filePath();
569 QString Config::findFile(const Location& location,
570 const QStringList& files,
571 const QStringList& dirs,
572 const QString& fileBase,
573 const QStringList& fileExtensions,
574 QString& userFriendlyFilePath)
576 QStringList::ConstIterator e = fileExtensions.constBegin();
577 while (e != fileExtensions.constEnd()) {
578 QString filePath = findFile(location,
581 fileBase + QLatin1Char('.') + *e,
582 userFriendlyFilePath);
583 if (!filePath.isEmpty())
587 return findFile(location, files, dirs, fileBase, userFriendlyFilePath);
591 Copies the \a sourceFilePath to the file name constructed by
592 concatenating \a targetDirPath and \a userFriendlySourceFilePath.
593 \a location is for identifying the file and line number where
594 a qdoc error occurred. The constructed output file name is
597 QString Config::copyFile(const Location& location,
598 const QString& sourceFilePath,
599 const QString& userFriendlySourceFilePath,
600 const QString& targetDirPath)
602 QFile inFile(sourceFilePath);
603 if (!inFile.open(QFile::ReadOnly)) {
604 location.fatal(tr("Cannot open input file '%1': %2")
605 .arg(sourceFilePath).arg(inFile.errorString()));
609 QString outFileName = userFriendlySourceFilePath;
610 int slash = outFileName.lastIndexOf(QLatin1Char('/'));
612 outFileName = outFileName.mid(slash);
614 QFile outFile(targetDirPath + QLatin1Char('/') + outFileName);
615 if (!outFile.open(QFile::WriteOnly)) {
616 location.fatal(tr("Cannot open output file '%1': %2")
617 .arg(outFile.fileName()).arg(outFile.errorString()));
623 while ((len = inFile.read(buffer, sizeof(buffer))) > 0) {
624 outFile.write(buffer, len);
630 Finds the largest unicode digit in \a value in the range
633 int Config::numParams(const QString& value)
636 for (int i = 0; i != value.length(); i++) {
637 uint c = value[i].unicode();
639 max = qMax(max, (int)c);
645 Removes everything from \a dir. This function is recursive.
646 It doesn't remove \a dir itself, but if it was called
647 recursively, then the caller will remove \a dir.
649 bool Config::removeDirContents(const QString& dir)
652 QFileInfoList entries = dirInfo.entryInfoList();
656 QFileInfoList::Iterator it = entries.begin();
657 while (it != entries.end()) {
658 if ((*it).isFile()) {
659 if (!dirInfo.remove((*it).fileName()))
662 else if ((*it).isDir()) {
663 if ((*it).fileName() != QLatin1String(".") && (*it).fileName() != QLatin1String("..")) {
664 if (removeDirContents((*it).absoluteFilePath())) {
665 if (!dirInfo.rmdir((*it).fileName()))
679 Returns true if \a ch is a letter, number, '_', '.',
682 bool Config::isMetaKeyChar(QChar ch)
684 return ch.isLetterOrNumber()
685 || ch == QLatin1Char('_')
686 || ch == QLatin1Char('.')
687 || ch == QLatin1Char('{')
688 || ch == QLatin1Char('}')
689 || ch == QLatin1Char(',');
693 Load, parse, and process a qdoc configuration file. This
694 function is only called by the other load() function, but
695 this one is recursive, i.e., it calls itself when it sees
696 an \c{include} statement in the qdoc configuration file.
698 void Config::load(Location location, const QString& fileName)
700 QRegExp keySyntax(QLatin1String("\\w+(?:\\.\\w+)*"));
702 #define SKIP_CHAR() \
704 location.advance(c); \
710 #define SKIP_SPACES() \
711 while (c.isSpace() && cc != '\n') \
718 if (location.depth() > 16)
719 location.fatal(tr("Too many nested includes"));
722 if (!fin.open(QFile::ReadOnly | QFile::Text)) {
723 if (!Config::installDir.isEmpty()) {
724 int prefix = location.filePath().length() - location.fileName().length();
725 fin.setFileName(Config::installDir + "/" + fileName.right(fileName.length() - prefix));
727 if (!fin.open(QFile::ReadOnly | QFile::Text))
728 location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString()));
731 QTextStream stream(&fin);
732 stream.setCodec("UTF-8");
733 QString text = stream.readAll();
734 text += QLatin1String("\n\n");
735 text += QLatin1Char('\0');
738 location.push(fileName);
742 QChar c = text.at(0);
743 uint cc = c.unicode();
744 while (i < (int) text.length()) {
747 else if (c.isSpace()) {
750 else if (cc == '#') {
753 } while (cc != '\n');
755 else if (isMetaKeyChar(c)) {
756 Location keyLoc = location;
759 QStringList stringListValue;
761 bool inQuote = false;
762 bool prevWordQuoted = true;
763 bool metWord = false;
767 stack.process(c, location);
769 } while (isMetaKeyChar(c));
771 QStringList keys = stack.getExpanded(location);
774 if (keys.count() == 1 && keys.first() == QLatin1String("include")) {
778 location.fatal(tr("Bad include syntax"));
782 while (!c.isSpace() && cc != '#' && cc != ')') {
787 while (c.isLetterOrNumber() || cc == '_') {
791 if (!var.isEmpty()) {
792 char *val = getenv(var.toLatin1().data());
794 location.fatal(tr("Environment variable '%1' undefined").arg(var));
797 includeFile += QString::fromLatin1(val);
807 location.fatal(tr("Bad include syntax"));
810 if (cc != '#' && cc != '\n')
811 location.fatal(tr("Trailing garbage"));
814 Here is the recursive call.
817 QFileInfo(QFileInfo(fileName).dir(), includeFile)
822 It wasn't an include statement, so it's something else.
829 location.fatal(tr("Expected '=' or '+=' after key"));
841 else if (cc > '0' && cc < '8') {
842 word += QChar(c.digitValue());
845 else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) {
846 word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]);
853 else if (c.isSpace() || cc == '#') {
856 location.fatal(tr("Unterminated string"));
860 if (!word.isEmpty()) {
862 stringValue += QLatin1Char(' ');
864 stringListValue << word;
867 prevWordQuoted = false;
869 if (cc == '\n' || cc == '#')
874 else if (cc == '"') {
877 stringValue += QLatin1Char(' ');
880 stringListValue << word;
883 prevWordQuoted = true;
888 else if (cc == '$') {
891 while (c.isLetterOrNumber() || cc == '_') {
895 if (!var.isEmpty()) {
896 char *val = getenv(var.toLatin1().data());
898 location.fatal(tr("Environment variable '%1' undefined").arg(var));
901 word += QString::fromLatin1(val);
906 if (!inQuote && cc == '=')
907 location.fatal(tr("Unexpected '='"));
912 QStringList::ConstIterator key = keys.constBegin();
913 while (key != keys.constEnd()) {
914 if (!keySyntax.exactMatch(*key))
915 keyLoc.fatal(tr("Invalid key '%1'").arg(*key));
918 if (locMap[*key].isEmpty()) {
919 locMap[*key] = keyLoc;
922 locMap[*key].setEtc(true);
924 if (stringValueMap[*key].isEmpty()) {
925 stringValueMap[*key] = stringValue;
928 stringValueMap[*key] +=
929 QLatin1Char(' ') + stringValue;
931 stringListValueMap[*key] += stringListValue;
934 locMap[*key] = keyLoc;
935 stringValueMap[*key] = stringValue;
936 stringListValueMap[*key] = stringListValue;
943 location.fatal(tr("Unexpected character '%1' at beginning of line")
949 QStringList Config::getFilesHere(const QString& uncleanDir,
950 const QString& nameFilter,
951 const QSet<QString> &excludedDirs,
952 const QSet<QString> &excludedFiles)
954 QString dir = QDir::cleanPath(uncleanDir);
956 if (excludedDirs.contains(dir))
960 QStringList fileNames;
961 QStringList::const_iterator fn;
963 dirInfo.setNameFilters(nameFilter.split(QLatin1Char(' ')));
964 dirInfo.setSorting(QDir::Name);
965 dirInfo.setFilter(QDir::Files);
966 fileNames = dirInfo.entryList();
967 fn = fileNames.constBegin();
968 while (fn != fileNames.constEnd()) {
969 if (!fn->startsWith(QLatin1Char('~'))) {
970 QString s = dirInfo.filePath(*fn);
971 QString c = QDir::cleanPath(s);
972 if (!excludedFiles.contains(c)) {
979 dirInfo.setNameFilters(QStringList(QLatin1String("*")));
980 dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot);
981 fileNames = dirInfo.entryList();
982 fn = fileNames.constBegin();
983 while (fn != fileNames.constEnd()) {
984 result += getFilesHere(dirInfo.filePath(*fn), nameFilter, excludedDirs, excludedFiles);