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();
219 if (v.key().startsWith(varDot)) {
220 QString subVar = v.key().mid(varDot.length());
221 int dot = subVar.indexOf(QLatin1Char('.'));
223 subVar.truncate(dot);
224 t.insert(subVar,v.value());
229 qDebug() << "fileName:" << fileName;
233 Joins all the strings in \a values into a single string with the
234 individual \a values separated by ' '. Then it inserts the result
235 into the string list map with \a var as the key.
237 It also inserts the \a values string list into a separate map,
238 also with \a var as the key.
240 void Config::setStringList(const QString& var, const QStringList& values)
242 stringValueMap[var] = values.join(QLatin1String(" "));
243 stringListValueMap[var] = values;
247 Looks up the configuarion variable \a var in the string
248 map and returns the boolean value.
250 bool Config::getBool(const QString& var) const
252 return QVariant(getString(var)).toBool();
256 Looks up the configuration variable \a var in the string list
257 map. Iterates through the string list found, interpreting each
258 string in the list as an integer and adding it to a total sum.
261 int Config::getInt(const QString& var) const
263 QStringList strs = getStringList(var);
264 QStringList::ConstIterator s = strs.constBegin();
267 while (s != strs.constEnd()) {
275 Function to return the correct outputdir.
276 outputdir can be set using the qdocconf or the command-line
279 QString Config::getOutputDir() const
281 if (overrideOutputDir.isNull())
282 return getString(QLatin1String(CONFIG_OUTPUTDIR));
284 return overrideOutputDir;
288 Function to return the correct outputformats.
289 outputformats can be set using the qdocconf or the command-line
290 variable -outputformat.
292 QSet<QString> Config::getOutputFormats() const
294 if (overrideOutputFormats.isEmpty())
295 return getStringSet(QLatin1String(CONFIG_OUTPUTFORMATS));
297 return overrideOutputFormats;
301 First, this function looks up the configuration variable \a var
302 in the location map and, if found, sets the internal variable
303 \c{lastLoc} to the Location that \a var maps to.
305 Then it looks up the configuration variable \a var in the string
306 map, and returns the string that \a var maps to.
308 QString Config::getString(const QString& var) const
310 if (!locMap[var].isEmpty())
311 (Location&) lastLoc = locMap[var];
312 return stringValueMap[var];
316 Looks up the configuration variable \a var in the string
317 list map, converts the string list it maps to into a set
318 of strings, and returns the set.
320 QSet<QString> Config::getStringSet(const QString& var) const
322 return QSet<QString>::fromList(getStringList(var));
326 First, this function looks up the configuration variable \a var
327 in the location map and, if found, sets the internal variable
328 \c{lastLoc} the Location that \a var maps to.
330 Then it looks up the configuration variable \a var in the string
331 list map, and returns the string list that \a var maps to.
333 QStringList Config::getStringList(const QString& var) const
335 if (!locMap[var].isEmpty())
336 (Location&) lastLoc = locMap[var];
337 return stringListValueMap[var];
341 This function should only be called when the configuration
342 variable \a var maps to a string list that contains file paths.
343 It cleans the paths with QDir::cleanPath() before returning
346 First, this function looks up the configuration variable \a var
347 in the location map and, if found, sets the internal variable
348 \c{lastLoc} the Location that \a var maps to.
350 Then it looks up the configuration variable \a var in the string
351 list map, which maps to a string list that contains file paths.
352 These paths might not be clean, so QDir::cleanPath() is called
353 for each one. The string list returned contains cleaned paths.
355 QStringList Config::getCleanPathList(const QString& var) const
357 if (!locMap[var].isEmpty())
358 (Location&) lastLoc = locMap[var];
360 QMap<QString,QStringList>::const_iterator it = stringListValueMap.constFind(var);
361 if (it != stringListValueMap.constEnd()) {
362 const QStringList& sl = it.value();
364 t.reserve(sl.size());
365 for (int i=0; i<sl.size(); ++i) {
366 t.append(QDir::cleanPath(sl[i]));
374 Calls getRegExpList() with the control variable \a var and
375 iterates through the resulting list of regular expressions,
376 concatening them with some extras characters to form a single
377 QRegExp, which is returned/
381 QRegExp Config::getRegExp(const QString& var) const
384 QList<QRegExp> subRegExps = getRegExpList(var);
385 QList<QRegExp>::ConstIterator s = subRegExps.constBegin();
387 while (s != subRegExps.constEnd()) {
390 if (!pattern.isEmpty())
391 pattern += QLatin1Char('|');
392 pattern += QLatin1String("(?:") + (*s).pattern() + QLatin1Char(')');
395 if (pattern.isEmpty())
396 pattern = QLatin1String("$x"); // cannot match
397 return QRegExp(pattern);
401 Looks up the configuration variable \a var in the string list
402 map, converts the string list to a list of regular expressions,
405 QList<QRegExp> Config::getRegExpList(const QString& var) const
407 QStringList strs = getStringList(var);
408 QStringList::ConstIterator s = strs.constBegin();
409 QList<QRegExp> regExps;
411 while (s != strs.constEnd()) {
412 regExps += QRegExp(*s);
419 This function is slower than it could be. What it does is
420 find all the keys that begin with \a var + dot and return
421 the matching keys in a set, stripped of the matching prefix
424 QSet<QString> Config::subVars(const QString& var) const
426 QSet<QString> result;
427 QString varDot = var + QLatin1Char('.');
428 QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
429 while (v != stringValueMap.constEnd()) {
430 if (v.key().startsWith(varDot)) {
431 QString subVar = v.key().mid(varDot.length());
432 int dot = subVar.indexOf(QLatin1Char('.'));
434 subVar.truncate(dot);
435 result.insert(subVar);
443 Same as subVars(), but in this case we return a string map
444 with the matching keys (stripped of the prefix \a var and
445 mapped to their values. The pairs are inserted into \a t
447 void Config::subVarsAndValues(const QString& var, QStringMultiMap& t) const
449 QString varDot = var + QLatin1Char('.');
450 QStringMultiMap::ConstIterator v = stringValueMap.constBegin();
451 while (v != stringValueMap.constEnd()) {
452 if (v.key().startsWith(varDot)) {
453 QString subVar = v.key().mid(varDot.length());
454 int dot = subVar.indexOf(QLatin1Char('.'));
456 subVar.truncate(dot);
457 t.insert(subVar,v.value());
464 Builds and returns a list of file pathnames for the file
465 type specified by \a filesVar (e.g. "headers" or "sources").
466 The files are found in the directories specified by
467 \a dirsVar, and they are filtered by \a defaultNameFilter
468 if a better filter can't be constructed from \a filesVar.
469 The directories in \a excludedDirs are avoided. The files
470 in \a excludedFiles are not included in the return list.
472 QStringList Config::getAllFiles(const QString &filesVar,
473 const QString &dirsVar,
474 const QSet<QString> &excludedDirs,
475 const QSet<QString> &excludedFiles)
477 QStringList result = getStringList(filesVar);
478 QStringList dirs = getStringList(dirsVar);
480 QString nameFilter = getString(filesVar + dot + QLatin1String(CONFIG_FILEEXTENSIONS));
482 QStringList::ConstIterator d = dirs.constBegin();
483 while (d != dirs.constEnd()) {
484 result += getFilesHere(*d, nameFilter, excludedDirs, excludedFiles);
491 \a fileName is the path of the file to find.
493 \a files and \a dirs are the lists where we must find the
494 components of \a fileName.
496 \a location is used for obtaining the file and line numbers
497 for report qdoc errors.
499 QString Config::findFile(const Location& location,
500 const QStringList& files,
501 const QStringList& dirs,
502 const QString& fileName,
503 QString& userFriendlyFilePath)
505 if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
506 userFriendlyFilePath = fileName;
511 QStringList components = fileName.split(QLatin1Char('?'));
512 QString firstComponent = components.first();
514 QStringList::ConstIterator f = files.constBegin();
515 while (f != files.constEnd()) {
516 if (*f == firstComponent ||
517 (*f).endsWith(QLatin1Char('/') + firstComponent)) {
518 fileInfo.setFile(*f);
519 if (!fileInfo.exists())
520 location.fatal(tr("File '%1' does not exist").arg(*f));
526 if (fileInfo.fileName().isEmpty()) {
527 QStringList::ConstIterator d = dirs.constBegin();
528 while (d != dirs.constEnd()) {
529 fileInfo.setFile(QDir(*d), firstComponent);
530 if (fileInfo.exists()) {
537 userFriendlyFilePath = QString();
538 if (!fileInfo.exists())
541 QStringList::ConstIterator c = components.constBegin();
543 bool isArchive = (c != components.constEnd() - 1);
544 QString userFriendly = *c;
546 userFriendlyFilePath += userFriendly;
549 QString extracted = extractedDirs[fileInfo.filePath()];
551 fileInfo.setFile(QDir(extracted), *c);
556 userFriendlyFilePath += QLatin1Char('?');
558 return fileInfo.filePath();
563 QString Config::findFile(const Location& location,
564 const QStringList& files,
565 const QStringList& dirs,
566 const QString& fileBase,
567 const QStringList& fileExtensions,
568 QString& userFriendlyFilePath)
570 QStringList::ConstIterator e = fileExtensions.constBegin();
571 while (e != fileExtensions.constEnd()) {
572 QString filePath = findFile(location,
575 fileBase + QLatin1Char('.') + *e,
576 userFriendlyFilePath);
577 if (!filePath.isEmpty())
581 return findFile(location, files, dirs, fileBase, userFriendlyFilePath);
585 Copies the \a sourceFilePath to the file name constructed by
586 concatenating \a targetDirPath and \a userFriendlySourceFilePath.
587 \a location is for identifying the file and line number where
588 a qdoc error occurred. The constructed output file name is
591 QString Config::copyFile(const Location& location,
592 const QString& sourceFilePath,
593 const QString& userFriendlySourceFilePath,
594 const QString& targetDirPath)
596 QFile inFile(sourceFilePath);
597 if (!inFile.open(QFile::ReadOnly)) {
598 location.fatal(tr("Cannot open input file '%1': %2")
599 .arg(sourceFilePath).arg(inFile.errorString()));
603 QString outFileName = userFriendlySourceFilePath;
604 int slash = outFileName.lastIndexOf(QLatin1Char('/'));
606 outFileName = outFileName.mid(slash);
608 QFile outFile(targetDirPath + QLatin1Char('/') + outFileName);
609 if (!outFile.open(QFile::WriteOnly)) {
610 location.fatal(tr("Cannot open output file '%1': %2")
611 .arg(outFile.fileName()).arg(outFile.errorString()));
617 while ((len = inFile.read(buffer, sizeof(buffer))) > 0) {
618 outFile.write(buffer, len);
624 Finds the largest unicode digit in \a value in the range
627 int Config::numParams(const QString& value)
630 for (int i = 0; i != value.length(); i++) {
631 uint c = value[i].unicode();
633 max = qMax(max, (int)c);
639 Removes everything from \a dir. This function is recursive.
640 It doesn't remove \a dir itself, but if it was called
641 recursively, then the caller will remove \a dir.
643 bool Config::removeDirContents(const QString& dir)
646 QFileInfoList entries = dirInfo.entryInfoList();
650 QFileInfoList::Iterator it = entries.begin();
651 while (it != entries.end()) {
652 if ((*it).isFile()) {
653 if (!dirInfo.remove((*it).fileName()))
656 else if ((*it).isDir()) {
657 if ((*it).fileName() != QLatin1String(".") && (*it).fileName() != QLatin1String("..")) {
658 if (removeDirContents((*it).absoluteFilePath())) {
659 if (!dirInfo.rmdir((*it).fileName()))
673 Returns true if \a ch is a letter, number, '_', '.',
676 bool Config::isMetaKeyChar(QChar ch)
678 return ch.isLetterOrNumber()
679 || ch == QLatin1Char('_')
680 || ch == QLatin1Char('.')
681 || ch == QLatin1Char('{')
682 || ch == QLatin1Char('}')
683 || ch == QLatin1Char(',');
687 Load, parse, and process a qdoc configuration file. This
688 function is only called by the other load() function, but
689 this one is recursive, i.e., it calls itself when it sees
690 an \c{include} statement in the qdoc configuration file.
692 void Config::load(Location location, const QString& fileName)
694 QRegExp keySyntax(QLatin1String("\\w+(?:\\.\\w+)*"));
696 #define SKIP_CHAR() \
698 location.advance(c); \
704 #define SKIP_SPACES() \
705 while (c.isSpace() && cc != '\n') \
712 if (location.depth() > 16)
713 location.fatal(tr("Too many nested includes"));
716 if (!fin.open(QFile::ReadOnly | QFile::Text)) {
717 fin.setFileName(fileName + QLatin1String(".qdoc"));
718 if (!fin.open(QFile::ReadOnly | QFile::Text))
719 location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString()));
722 QTextStream stream(&fin);
723 stream.setCodec("UTF-8");
724 QString text = stream.readAll();
725 text += QLatin1String("\n\n");
726 text += QLatin1Char('\0');
729 location.push(fileName);
733 QChar c = text.at(0);
734 uint cc = c.unicode();
735 while (i < (int) text.length()) {
738 else if (c.isSpace()) {
741 else if (cc == '#') {
744 } while (cc != '\n');
746 else if (isMetaKeyChar(c)) {
747 Location keyLoc = location;
750 QStringList stringListValue;
752 bool inQuote = false;
753 bool prevWordQuoted = true;
754 bool metWord = false;
758 stack.process(c, location);
760 } while (isMetaKeyChar(c));
762 QStringList keys = stack.getExpanded(location);
765 if (keys.count() == 1 && keys.first() == QLatin1String("include")) {
769 location.fatal(tr("Bad include syntax"));
773 while (!c.isSpace() && cc != '#' && cc != ')') {
778 while (c.isLetterOrNumber() || cc == '_') {
782 if (!var.isEmpty()) {
783 char *val = getenv(var.toLatin1().data());
785 location.fatal(tr("Environment variable '%1' undefined").arg(var));
788 includeFile += QString::fromLatin1(val);
798 location.fatal(tr("Bad include syntax"));
801 if (cc != '#' && cc != '\n')
802 location.fatal(tr("Trailing garbage"));
805 Here is the recursive call.
808 QFileInfo(QFileInfo(fileName).dir(), includeFile)
813 It wasn't an include statement, so it's something else.
820 location.fatal(tr("Expected '=' or '+=' after key"));
832 else if (cc > '0' && cc < '8') {
833 word += QChar(c.digitValue());
836 else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) {
837 word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]);
844 else if (c.isSpace() || cc == '#') {
847 location.fatal(tr("Unterminated string"));
851 if (!word.isEmpty()) {
853 stringValue += QLatin1Char(' ');
855 stringListValue << word;
858 prevWordQuoted = false;
860 if (cc == '\n' || cc == '#')
865 else if (cc == '"') {
868 stringValue += QLatin1Char(' ');
871 stringListValue << word;
874 prevWordQuoted = true;
879 else if (cc == '$') {
882 while (c.isLetterOrNumber() || cc == '_') {
886 if (!var.isEmpty()) {
887 char *val = getenv(var.toLatin1().data());
889 location.fatal(tr("Environment variable '%1' undefined").arg(var));
892 word += QString::fromLatin1(val);
897 if (!inQuote && cc == '=')
898 location.fatal(tr("Unexpected '='"));
903 QStringList::ConstIterator key = keys.constBegin();
904 while (key != keys.constEnd()) {
905 if (!keySyntax.exactMatch(*key))
906 keyLoc.fatal(tr("Invalid key '%1'").arg(*key));
909 if (locMap[*key].isEmpty()) {
910 locMap[*key] = keyLoc;
913 locMap[*key].setEtc(true);
915 if (stringValueMap[*key].isEmpty()) {
916 stringValueMap[*key] = stringValue;
919 stringValueMap[*key] +=
920 QLatin1Char(' ') + stringValue;
922 stringListValueMap[*key] += stringListValue;
925 locMap[*key] = keyLoc;
926 stringValueMap[*key] = stringValue;
927 stringListValueMap[*key] = stringListValue;
934 location.fatal(tr("Unexpected character '%1' at beginning of line")
940 QStringList Config::getFilesHere(const QString& dir,
941 const QString& nameFilter,
942 const QSet<QString> &excludedDirs,
943 const QSet<QString> &excludedFiles)
946 if (excludedDirs.contains(dir))
950 QStringList fileNames;
951 QStringList::const_iterator fn;
953 dirInfo.setNameFilters(nameFilter.split(QLatin1Char(' ')));
954 dirInfo.setSorting(QDir::Name);
955 dirInfo.setFilter(QDir::Files);
956 fileNames = dirInfo.entryList();
957 fn = fileNames.constBegin();
958 while (fn != fileNames.constEnd()) {
959 if (!fn->startsWith(QLatin1Char('~'))) {
960 QString s = dirInfo.filePath(*fn);
961 QString c = QDir::cleanPath(s);
962 if (!excludedFiles.contains(c)) {
969 dirInfo.setNameFilters(QStringList(QLatin1String("*")));
970 dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot);
971 fileNames = dirInfo.entryList();
972 fn = fileNames.constBegin();
973 while (fn != fileNames.constEnd()) {
974 result += getFilesHere(dirInfo.filePath(*fn), nameFilter, excludedDirs, excludedFiles);