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;
224 Joins all the strings in \a values into a single string with the
225 individual \a values separated by ' '. Then it inserts the result
226 into the string list map with \a var as the key.
228 It also inserts the \a values string list into a separate map,
229 also with \a var as the key.
231 void Config::setStringList(const QString& var, const QStringList& values)
233 stringValueMap[var] = values.join(QLatin1String(" "));
234 stringListValueMap[var] = values;
238 Looks up the configuarion variable \a var in the string
239 map and returns the boolean value.
241 bool Config::getBool(const QString& var) const
243 return QVariant(getString(var)).toBool();
247 Looks up the configuration variable \a var in the string list
248 map. Iterates through the string list found, interpreting each
249 string in the list as an integer and adding it to a total sum.
252 int Config::getInt(const QString& var) const
254 QStringList strs = getStringList(var);
255 QStringList::ConstIterator s = strs.constBegin();
258 while (s != strs.constEnd()) {
266 Function to return the correct outputdir.
267 outputdir can be set using the qdocconf or the command-line
270 QString Config::getOutputDir() const
272 if (overrideOutputDir.isNull())
273 return getString(QLatin1String(CONFIG_OUTPUTDIR));
275 return overrideOutputDir;
279 Function to return the correct outputformats.
280 outputformats can be set using the qdocconf or the command-line
281 variable -outputformat.
283 QSet<QString> Config::getOutputFormats() const
285 if (overrideOutputFormats.isEmpty())
286 return getStringSet(QLatin1String(CONFIG_OUTPUTFORMATS));
288 return overrideOutputFormats;
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} to the Location that \a var maps to.
296 Then it looks up the configuration variable \a var in the string
297 map, and returns the string that \a var maps to.
299 QString Config::getString(const QString& var) const
301 if (!locMap[var].isEmpty())
302 (Location&) lastLoc = locMap[var];
303 return stringValueMap[var];
307 Looks up the configuration variable \a var in the string
308 list map, converts the string list it maps to into a set
309 of strings, and returns the set.
311 QSet<QString> Config::getStringSet(const QString& var) const
313 return QSet<QString>::fromList(getStringList(var));
317 First, this function looks up the configuration variable \a var
318 in the location map and, if found, sets the internal variable
319 \c{lastLoc} the Location that \a var maps to.
321 Then it looks up the configuration variable \a var in the string
322 list map, and returns the string list that \a var maps to.
324 QStringList Config::getStringList(const QString& var) const
326 if (!locMap[var].isEmpty())
327 (Location&) lastLoc = locMap[var];
328 return stringListValueMap[var];
332 This function should only be called when the configuration
333 variable \a var maps to a string list that contains file paths.
334 It cleans the paths with QDir::cleanPath() before returning
337 First, this function looks up the configuration variable \a var
338 in the location map and, if found, sets the internal variable
339 \c{lastLoc} the Location that \a var maps to.
341 Then it looks up the configuration variable \a var in the string
342 list map, which maps to a string list that contains file paths.
343 These paths might not be clean, so QDir::cleanPath() is called
344 for each one. The string list returned contains cleaned paths.
346 QStringList Config::getCleanPathList(const QString& var) const
348 if (!locMap[var].isEmpty())
349 (Location&) lastLoc = locMap[var];
351 QMap<QString,QStringList>::const_iterator it = stringListValueMap.constFind(var);
352 if (it != stringListValueMap.constEnd()) {
353 const QStringList& sl = it.value();
355 t.reserve(sl.size());
356 for (int i=0; i<sl.size(); ++i) {
357 t.append(QDir::cleanPath(sl[i]));
365 Calls getRegExpList() with the control variable \a var and
366 iterates through the resulting list of regular expressions,
367 concatening them with some extras characters to form a single
368 QRegExp, which is returned/
372 QRegExp Config::getRegExp(const QString& var) const
375 QList<QRegExp> subRegExps = getRegExpList(var);
376 QList<QRegExp>::ConstIterator s = subRegExps.constBegin();
378 while (s != subRegExps.constEnd()) {
381 if (!pattern.isEmpty())
382 pattern += QLatin1Char('|');
383 pattern += QLatin1String("(?:") + (*s).pattern() + QLatin1Char(')');
386 if (pattern.isEmpty())
387 pattern = QLatin1String("$x"); // cannot match
388 return QRegExp(pattern);
392 Looks up the configuration variable \a var in the string list
393 map, converts the string list to a list of regular expressions,
396 QList<QRegExp> Config::getRegExpList(const QString& var) const
398 QStringList strs = getStringList(var);
399 QStringList::ConstIterator s = strs.constBegin();
400 QList<QRegExp> regExps;
402 while (s != strs.constEnd()) {
403 regExps += QRegExp(*s);
410 This function is slower than it could be. What it does is
411 find all the keys that begin with \a var + dot and return
412 the matching keys in a set, stripped of the matching prefix
415 QSet<QString> Config::subVars(const QString& var) const
417 QSet<QString> result;
418 QString varDot = var + QLatin1Char('.');
419 QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
420 while (v != stringValueMap.constEnd()) {
421 if (v.key().startsWith(varDot)) {
422 QString subVar = v.key().mid(varDot.length());
423 int dot = subVar.indexOf(QLatin1Char('.'));
425 subVar.truncate(dot);
426 result.insert(subVar);
434 Same as subVars(), but in this case we return a string map
435 with the matching keys (stripped of the prefix \a var and
436 mapped to their values. The pairs are inserted into \a t
438 void Config::subVarsAndValues(const QString& var, QStringMultiMap& t) const
440 QString varDot = var + QLatin1Char('.');
441 QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
442 while (v != stringValueMap.constEnd()) {
443 if (v.key().startsWith(varDot)) {
444 QString subVar = v.key().mid(varDot.length());
445 int dot = subVar.indexOf(QLatin1Char('.'));
447 subVar.truncate(dot);
448 t.insert(subVar,v.value());
455 Builds and returns a list of file pathnames for the file
456 type specified by \a filesVar (e.g. "headers" or "sources").
457 The files are found in the directories specified by
458 \a dirsVar, and they are filtered by \a defaultNameFilter
459 if a better filter can't be constructed from \a filesVar.
460 The directories in \a excludedDirs are avoided. The files
461 in \a excludedFiles are not included in the return list.
463 QStringList Config::getAllFiles(const QString &filesVar,
464 const QString &dirsVar,
465 const QSet<QString> &excludedDirs,
466 const QSet<QString> &excludedFiles)
468 QStringList result = getStringList(filesVar);
469 QStringList dirs = getStringList(dirsVar);
471 QString nameFilter = getString(filesVar + dot + QLatin1String(CONFIG_FILEEXTENSIONS));
473 QStringList::ConstIterator d = dirs.constBegin();
474 while (d != dirs.constEnd()) {
475 result += getFilesHere(*d, nameFilter, excludedDirs, excludedFiles);
482 \a fileName is the path of the file to find.
484 \a files and \a dirs are the lists where we must find the
485 components of \a fileName.
487 \a location is used for obtaining the file and line numbers
488 for report qdoc errors.
490 QString Config::findFile(const Location& location,
491 const QStringList& files,
492 const QStringList& dirs,
493 const QString& fileName,
494 QString& userFriendlyFilePath)
496 if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
497 userFriendlyFilePath = fileName;
502 QStringList components = fileName.split(QLatin1Char('?'));
503 QString firstComponent = components.first();
505 QStringList::ConstIterator f = files.constBegin();
506 while (f != files.constEnd()) {
507 if (*f == firstComponent ||
508 (*f).endsWith(QLatin1Char('/') + firstComponent)) {
509 fileInfo.setFile(*f);
510 if (!fileInfo.exists())
511 location.fatal(tr("File '%1' does not exist").arg(*f));
517 if (fileInfo.fileName().isEmpty()) {
518 QStringList::ConstIterator d = dirs.constBegin();
519 while (d != dirs.constEnd()) {
520 fileInfo.setFile(QDir(*d), firstComponent);
521 if (fileInfo.exists()) {
528 userFriendlyFilePath = QString();
529 if (!fileInfo.exists())
532 QStringList::ConstIterator c = components.constBegin();
534 bool isArchive = (c != components.constEnd() - 1);
535 QString userFriendly = *c;
537 userFriendlyFilePath += userFriendly;
540 QString extracted = extractedDirs[fileInfo.filePath()];
542 fileInfo.setFile(QDir(extracted), *c);
547 userFriendlyFilePath += QLatin1Char('?');
549 return fileInfo.filePath();
554 QString Config::findFile(const Location& location,
555 const QStringList& files,
556 const QStringList& dirs,
557 const QString& fileBase,
558 const QStringList& fileExtensions,
559 QString& userFriendlyFilePath)
561 QStringList::ConstIterator e = fileExtensions.constBegin();
562 while (e != fileExtensions.constEnd()) {
563 QString filePath = findFile(location,
566 fileBase + QLatin1Char('.') + *e,
567 userFriendlyFilePath);
568 if (!filePath.isEmpty())
572 return findFile(location, files, dirs, fileBase, userFriendlyFilePath);
576 Copies the \a sourceFilePath to the file name constructed by
577 concatenating \a targetDirPath and \a userFriendlySourceFilePath.
578 \a location is for identifying the file and line number where
579 a qdoc error occurred. The constructed output file name is
582 QString Config::copyFile(const Location& location,
583 const QString& sourceFilePath,
584 const QString& userFriendlySourceFilePath,
585 const QString& targetDirPath)
587 QFile inFile(sourceFilePath);
588 if (!inFile.open(QFile::ReadOnly)) {
589 location.fatal(tr("Cannot open input file '%1': %2")
590 .arg(sourceFilePath).arg(inFile.errorString()));
594 QString outFileName = userFriendlySourceFilePath;
595 int slash = outFileName.lastIndexOf(QLatin1Char('/'));
597 outFileName = outFileName.mid(slash);
599 QFile outFile(targetDirPath + QLatin1Char('/') + outFileName);
600 if (!outFile.open(QFile::WriteOnly)) {
601 location.fatal(tr("Cannot open output file '%1': %2")
602 .arg(outFile.fileName()).arg(outFile.errorString()));
608 while ((len = inFile.read(buffer, sizeof(buffer))) > 0) {
609 outFile.write(buffer, len);
615 Finds the largest unicode digit in \a value in the range
618 int Config::numParams(const QString& value)
621 for (int i = 0; i != value.length(); i++) {
622 uint c = value[i].unicode();
624 max = qMax(max, (int)c);
630 Removes everything from \a dir. This function is recursive.
631 It doesn't remove \a dir itself, but if it was called
632 recursively, then the caller will remove \a dir.
634 bool Config::removeDirContents(const QString& dir)
637 QFileInfoList entries = dirInfo.entryInfoList();
641 QFileInfoList::Iterator it = entries.begin();
642 while (it != entries.end()) {
643 if ((*it).isFile()) {
644 if (!dirInfo.remove((*it).fileName()))
647 else if ((*it).isDir()) {
648 if ((*it).fileName() != QLatin1String(".") && (*it).fileName() != QLatin1String("..")) {
649 if (removeDirContents((*it).absoluteFilePath())) {
650 if (!dirInfo.rmdir((*it).fileName()))
664 Returns true if \a ch is a letter, number, '_', '.',
667 bool Config::isMetaKeyChar(QChar ch)
669 return ch.isLetterOrNumber()
670 || ch == QLatin1Char('_')
671 || ch == QLatin1Char('.')
672 || ch == QLatin1Char('{')
673 || ch == QLatin1Char('}')
674 || ch == QLatin1Char(',');
678 Load, parse, and process a qdoc configuration file. This
679 function is only called by the other load() function, but
680 this one is recursive, i.e., it calls itself when it sees
681 an \c{include} statement in the qdoc configuration file.
683 void Config::load(Location location, const QString& fileName)
685 QRegExp keySyntax(QLatin1String("\\w+(?:\\.\\w+)*"));
687 #define SKIP_CHAR() \
689 location.advance(c); \
695 #define SKIP_SPACES() \
696 while (c.isSpace() && cc != '\n') \
703 if (location.depth() > 16)
704 location.fatal(tr("Too many nested includes"));
707 if (!fin.open(QFile::ReadOnly | QFile::Text)) {
708 if (!Config::installDir.isEmpty()) {
709 int prefix = location.filePath().length() - location.fileName().length();
710 fin.setFileName(Config::installDir + "/" + fileName.right(fileName.length() - prefix));
712 if (!fin.open(QFile::ReadOnly | QFile::Text))
713 location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString()));
716 QTextStream stream(&fin);
717 stream.setCodec("UTF-8");
718 QString text = stream.readAll();
719 text += QLatin1String("\n\n");
720 text += QLatin1Char('\0');
723 location.push(fileName);
727 QChar c = text.at(0);
728 uint cc = c.unicode();
729 while (i < (int) text.length()) {
732 else if (c.isSpace()) {
735 else if (cc == '#') {
738 } while (cc != '\n');
740 else if (isMetaKeyChar(c)) {
741 Location keyLoc = location;
744 QStringList stringListValue;
746 bool inQuote = false;
747 bool prevWordQuoted = true;
748 bool metWord = false;
752 stack.process(c, location);
754 } while (isMetaKeyChar(c));
756 QStringList keys = stack.getExpanded(location);
759 if (keys.count() == 1 && keys.first() == QLatin1String("include")) {
763 location.fatal(tr("Bad include syntax"));
767 while (!c.isSpace() && cc != '#' && cc != ')') {
772 while (c.isLetterOrNumber() || cc == '_') {
776 if (!var.isEmpty()) {
777 char *val = getenv(var.toLatin1().data());
779 location.fatal(tr("Environment variable '%1' undefined").arg(var));
782 includeFile += QString::fromLatin1(val);
792 location.fatal(tr("Bad include syntax"));
795 if (cc != '#' && cc != '\n')
796 location.fatal(tr("Trailing garbage"));
799 Here is the recursive call.
802 QFileInfo(QFileInfo(fileName).dir(), includeFile)
807 It wasn't an include statement, so it's something else.
814 location.fatal(tr("Expected '=' or '+=' after key"));
826 else if (cc > '0' && cc < '8') {
827 word += QChar(c.digitValue());
830 else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) {
831 word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]);
838 else if (c.isSpace() || cc == '#') {
841 location.fatal(tr("Unterminated string"));
845 if (!word.isEmpty()) {
847 stringValue += QLatin1Char(' ');
849 stringListValue << word;
852 prevWordQuoted = false;
854 if (cc == '\n' || cc == '#')
859 else if (cc == '"') {
862 stringValue += QLatin1Char(' ');
865 stringListValue << word;
868 prevWordQuoted = true;
873 else if (cc == '$') {
876 while (c.isLetterOrNumber() || cc == '_') {
880 if (!var.isEmpty()) {
881 char *val = getenv(var.toLatin1().data());
883 location.fatal(tr("Environment variable '%1' undefined").arg(var));
886 word += QString::fromLatin1(val);
891 if (!inQuote && cc == '=')
892 location.fatal(tr("Unexpected '='"));
897 QStringList::ConstIterator key = keys.constBegin();
898 while (key != keys.constEnd()) {
899 if (!keySyntax.exactMatch(*key))
900 keyLoc.fatal(tr("Invalid key '%1'").arg(*key));
903 if (locMap[*key].isEmpty()) {
904 locMap[*key] = keyLoc;
907 locMap[*key].setEtc(true);
909 if (stringValueMap[*key].isEmpty()) {
910 stringValueMap[*key] = stringValue;
913 stringValueMap[*key] +=
914 QLatin1Char(' ') + stringValue;
916 stringListValueMap[*key] += stringListValue;
919 locMap[*key] = keyLoc;
920 stringValueMap[*key] = stringValue;
921 stringListValueMap[*key] = stringListValue;
928 location.fatal(tr("Unexpected character '%1' at beginning of line")
934 QStringList Config::getFilesHere(const QString& dir,
935 const QString& nameFilter,
936 const QSet<QString> &excludedDirs,
937 const QSet<QString> &excludedFiles)
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);