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.begin();
119 while (pre != prefixes.end()) {
120 QStringList::ConstIterator suf = suffixes.begin();
121 while (suf != suffixes.end()) {
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 QString Config::overrideOutputDir;
152 QSet<QString> Config::overrideOutputFormats;
153 QMap<QString, QString> Config::extractedDirs;
154 int Config::numInstances;
158 \brief The Config class contains the configuration variables
159 for controlling how qdoc produces documentation.
161 Its load() function, reads, parses, and processes a qdocconf file.
165 The constructor sets the \a programName and initializes all
166 internal state variables to empty values.
168 Config::Config(const QString& programName)
171 loc = Location::null;
172 lastLoc = Location::null;
174 stringValueMap.clear();
175 stringListValueMap.clear();
180 The destructor has nothing special to do.
187 Loads and parses the qdoc configuration file \a fileName.
188 This function calls the other load() function, which does
189 the loading, parsing, and processing of the configuration
192 Intializes the location variables returned by location()
195 void Config::load(const QString& fileName)
197 load(Location::null, fileName);
199 loc = Location(fileName);
204 lastLoc = Location::null;
208 Writes the qdoc configuration data to the named file.
209 The previous contents of the file are overwritten.
211 void Config::unload(const QString& fileName)
213 QStringMultiMap::ConstIterator v = stringValueMap.begin();
214 while (v != stringValueMap.end()) {
215 qDebug() << v.key() << " = " << v.value();
217 if (v.key().startsWith(varDot)) {
218 QString subVar = v.key().mid(varDot.length());
219 int dot = subVar.indexOf(QLatin1Char('.'));
221 subVar.truncate(dot);
222 t.insert(subVar,v.value());
227 qDebug() << "fileName:" << fileName;
231 Joins all the strings in \a values into a single string with the
232 individual \a values separated by ' '. Then it inserts the result
233 into the string list map with \a var as the key.
235 It also inserts the \a values string list into a separate map,
236 also with \a var as the key.
238 void Config::setStringList(const QString& var, const QStringList& values)
240 stringValueMap[var] = values.join(QLatin1String(" "));
241 stringListValueMap[var] = values;
245 Looks up the configuarion variable \a var in the string
246 map and returns the boolean value.
248 bool Config::getBool(const QString& var) const
250 return QVariant(getString(var)).toBool();
254 Looks up the configuration variable \a var in the string list
255 map. Iterates through the string list found, interpreting each
256 string in the list as an integer and adding it to a total sum.
259 int Config::getInt(const QString& var) const
261 QStringList strs = getStringList(var);
262 QStringList::ConstIterator s = strs.begin();
265 while (s != strs.end()) {
273 Function to return the correct outputdir.
274 outputdir can be set using the qdocconf or the command-line
277 QString Config::getOutputDir() const
279 if (overrideOutputDir.isNull())
280 return getString(QLatin1String(CONFIG_OUTPUTDIR));
282 return overrideOutputDir;
286 Function to return the correct outputformats.
287 outputformats can be set using the qdocconf or the command-line
288 variable -outputformat.
290 QSet<QString> Config::getOutputFormats() const
292 if (overrideOutputFormats.isEmpty())
293 return getStringSet(QLatin1String(CONFIG_OUTPUTFORMATS));
295 return overrideOutputFormats;
299 First, this function looks up the configuration variable \a var
300 in the location map and, if found, sets the internal variable
301 \c{lastLoc} to the Location that \a var maps to.
303 Then it looks up the configuration variable \a var in the string
304 map, and returns the string that \a var maps to.
306 QString Config::getString(const QString& var) const
308 if (!locMap[var].isEmpty())
309 (Location&) lastLoc = locMap[var];
310 return stringValueMap[var];
314 Looks up the configuration variable \a var in the string
315 list map, converts the string list it maps to into a set
316 of strings, and returns the set.
318 QSet<QString> Config::getStringSet(const QString& var) const
320 return QSet<QString>::fromList(getStringList(var));
324 First, this function looks up the configuration variable \a var
325 in the location map and, if found, sets the internal variable
326 \c{lastLoc} the Location that \a var maps to.
328 Then it looks up the configuration variable \a var in the string
329 list map, and returns the string list that \a var maps to.
331 QStringList Config::getStringList(const QString& var) const
333 if (!locMap[var].isEmpty())
334 (Location&) lastLoc = locMap[var];
335 return stringListValueMap[var];
339 This function should only be called when the configuration
340 variable \a var maps to a string list that contains file paths.
341 It cleans the paths with QDir::cleanPath() before returning
344 First, this function looks up the configuration variable \a var
345 in the location map and, if found, sets the internal variable
346 \c{lastLoc} the Location that \a var maps to.
348 Then it looks up the configuration variable \a var in the string
349 list map, which maps to a string list that contains file paths.
350 These paths might not be clean, so QDir::cleanPath() is called
351 for each one. The string list returned contains cleaned paths.
353 QStringList Config::getCleanPathList(const QString& var) const
355 if (!locMap[var].isEmpty())
356 (Location&) lastLoc = locMap[var];
358 QMap<QString,QStringList>::const_iterator it = stringListValueMap.find(var);
359 if (it != stringListValueMap.end()) {
360 const QStringList& sl = it.value();
362 t.reserve(sl.size());
363 for (int i=0; i<sl.size(); ++i) {
364 t.append(QDir::cleanPath(sl[i]));
372 Calls getRegExpList() with the control variable \a var and
373 iterates through the resulting list of regular expressions,
374 concatening them with some extras characters to form a single
375 QRegExp, which is returned/
379 QRegExp Config::getRegExp(const QString& var) const
382 QList<QRegExp> subRegExps = getRegExpList(var);
383 QList<QRegExp>::ConstIterator s = subRegExps.begin();
385 while (s != subRegExps.end()) {
388 if (!pattern.isEmpty())
389 pattern += QLatin1Char('|');
390 pattern += QLatin1String("(?:") + (*s).pattern() + QLatin1Char(')');
393 if (pattern.isEmpty())
394 pattern = QLatin1String("$x"); // cannot match
395 return QRegExp(pattern);
399 Looks up the configuration variable \a var in the string list
400 map, converts the string list to a list of regular expressions,
403 QList<QRegExp> Config::getRegExpList(const QString& var) const
405 QStringList strs = getStringList(var);
406 QStringList::ConstIterator s = strs.begin();
407 QList<QRegExp> regExps;
409 while (s != strs.end()) {
410 regExps += QRegExp(*s);
417 This function is slower than it could be. What it does is
418 find all the keys that begin with \a var + dot and return
419 the matching keys in a set, stripped of the matching prefix
422 QSet<QString> Config::subVars(const QString& var) const
424 QSet<QString> result;
425 QString varDot = var + QLatin1Char('.');
426 QStringMultiMap::ConstIterator v = stringValueMap.begin();
427 while (v != stringValueMap.end()) {
428 if (v.key().startsWith(varDot)) {
429 QString subVar = v.key().mid(varDot.length());
430 int dot = subVar.indexOf(QLatin1Char('.'));
432 subVar.truncate(dot);
433 result.insert(subVar);
441 Same as subVars(), but in this case we return a string map
442 with the matching keys (stripped of the prefix \a var and
443 mapped to their values. The pairs are inserted into \a t
445 void Config::subVarsAndValues(const QString& var, QStringMultiMap& t) const
447 QString varDot = var + QLatin1Char('.');
448 QStringMultiMap::ConstIterator v = stringValueMap.begin();
449 while (v != stringValueMap.end()) {
450 if (v.key().startsWith(varDot)) {
451 QString subVar = v.key().mid(varDot.length());
452 int dot = subVar.indexOf(QLatin1Char('.'));
454 subVar.truncate(dot);
455 t.insert(subVar,v.value());
462 Builds and returns a list of file pathnames for the file
463 type specified by \a filesVar (e.g. "headers" or "sources").
464 The files are found in the directories specified by
465 \a dirsVar, and they are filtered by \a defaultNameFilter
466 if a better filter can't be constructed from \a filesVar.
467 The directories in \a excludedDirs are avoided. The files
468 in \a excludedFiles are not included in the return list.
470 QStringList Config::getAllFiles(const QString &filesVar,
471 const QString &dirsVar,
472 const QSet<QString> &excludedDirs,
473 const QSet<QString> &excludedFiles)
475 QStringList result = getStringList(filesVar);
476 QStringList dirs = getStringList(dirsVar);
478 QString nameFilter = getString(filesVar + dot + QLatin1String(CONFIG_FILEEXTENSIONS));
480 QStringList::ConstIterator d = dirs.begin();
481 while (d != dirs.end()) {
482 result += getFilesHere(*d, nameFilter, excludedDirs, excludedFiles);
489 \a fileName is the path of the file to find.
491 \a files and \a dirs are the lists where we must find the
492 components of \a fileName.
494 \a location is used for obtaining the file and line numbers
495 for report qdoc errors.
497 QString Config::findFile(const Location& location,
498 const QStringList& files,
499 const QStringList& dirs,
500 const QString& fileName,
501 QString& userFriendlyFilePath)
503 if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
504 userFriendlyFilePath = fileName;
509 QStringList components = fileName.split(QLatin1Char('?'));
510 QString firstComponent = components.first();
512 QStringList::ConstIterator f = files.begin();
513 while (f != files.end()) {
514 if (*f == firstComponent ||
515 (*f).endsWith(QLatin1Char('/') + firstComponent)) {
516 fileInfo.setFile(*f);
517 if (!fileInfo.exists())
518 location.fatal(tr("File '%1' does not exist").arg(*f));
524 if (fileInfo.fileName().isEmpty()) {
525 QStringList::ConstIterator d = dirs.begin();
526 while (d != dirs.end()) {
527 fileInfo.setFile(QDir(*d), firstComponent);
528 if (fileInfo.exists()) {
535 userFriendlyFilePath = QString();
536 if (!fileInfo.exists())
539 QStringList::ConstIterator c = components.begin();
541 bool isArchive = (c != components.end() - 1);
542 QString userFriendly = *c;
544 userFriendlyFilePath += userFriendly;
547 QString extracted = extractedDirs[fileInfo.filePath()];
549 fileInfo.setFile(QDir(extracted), *c);
554 userFriendlyFilePath += QLatin1Char('?');
556 return fileInfo.filePath();
561 QString Config::findFile(const Location& location,
562 const QStringList& files,
563 const QStringList& dirs,
564 const QString& fileBase,
565 const QStringList& fileExtensions,
566 QString& userFriendlyFilePath)
568 QStringList::ConstIterator e = fileExtensions.begin();
569 while (e != fileExtensions.end()) {
570 QString filePath = findFile(location,
573 fileBase + QLatin1Char('.') + *e,
574 userFriendlyFilePath);
575 if (!filePath.isEmpty())
579 return findFile(location, files, dirs, fileBase, userFriendlyFilePath);
583 Copies the \a sourceFilePath to the file name constructed by
584 concatenating \a targetDirPath and \a userFriendlySourceFilePath.
585 \a location is for identifying the file and line number where
586 a qdoc error occurred. The constructed output file name is
589 QString Config::copyFile(const Location& location,
590 const QString& sourceFilePath,
591 const QString& userFriendlySourceFilePath,
592 const QString& targetDirPath)
594 QFile inFile(sourceFilePath);
595 if (!inFile.open(QFile::ReadOnly)) {
596 location.fatal(tr("Cannot open input file '%1': %2")
597 .arg(sourceFilePath).arg(inFile.errorString()));
601 QString outFileName = userFriendlySourceFilePath;
602 int slash = outFileName.lastIndexOf(QLatin1Char('/'));
604 outFileName = outFileName.mid(slash);
606 QFile outFile(targetDirPath + QLatin1Char('/') + outFileName);
607 if (!outFile.open(QFile::WriteOnly)) {
608 location.fatal(tr("Cannot open output file '%1': %2")
609 .arg(outFile.fileName()).arg(outFile.errorString()));
615 while ((len = inFile.read(buffer, sizeof(buffer))) > 0) {
616 outFile.write(buffer, len);
622 Finds the largest unicode digit in \a value in the range
625 int Config::numParams(const QString& value)
628 for (int i = 0; i != value.length(); i++) {
629 uint c = value[i].unicode();
631 max = qMax(max, (int)c);
637 Removes everything from \a dir. This function is recursive.
638 It doesn't remove \a dir itself, but if it was called
639 recursively, then the caller will remove \a dir.
641 bool Config::removeDirContents(const QString& dir)
644 QFileInfoList entries = dirInfo.entryInfoList();
648 QFileInfoList::Iterator it = entries.begin();
649 while (it != entries.end()) {
650 if ((*it).isFile()) {
651 if (!dirInfo.remove((*it).fileName()))
654 else if ((*it).isDir()) {
655 if ((*it).fileName() != QLatin1String(".") && (*it).fileName() != QLatin1String("..")) {
656 if (removeDirContents((*it).absoluteFilePath())) {
657 if (!dirInfo.rmdir((*it).fileName()))
671 Returns true if \a ch is a letter, number, '_', '.',
674 bool Config::isMetaKeyChar(QChar ch)
676 return ch.isLetterOrNumber()
677 || ch == QLatin1Char('_')
678 || ch == QLatin1Char('.')
679 || ch == QLatin1Char('{')
680 || ch == QLatin1Char('}')
681 || ch == QLatin1Char(',');
685 Load, parse, and process a qdoc configuration file. This
686 function is only called by the other load() function, but
687 this one is recursive, i.e., it calls itself when it sees
688 an \c{include} statement in the qdoc configuration file.
690 void Config::load(Location location, const QString& fileName)
692 QRegExp keySyntax(QLatin1String("\\w+(?:\\.\\w+)*"));
694 #define SKIP_CHAR() \
696 location.advance(c); \
702 #define SKIP_SPACES() \
703 while (c.isSpace() && cc != '\n') \
710 if (location.depth() > 16)
711 location.fatal(tr("Too many nested includes"));
714 if (!fin.open(QFile::ReadOnly | QFile::Text)) {
715 fin.setFileName(fileName + QLatin1String(".qdoc"));
716 if (!fin.open(QFile::ReadOnly | QFile::Text))
717 location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString()));
720 QTextStream stream(&fin);
721 stream.setCodec("UTF-8");
722 QString text = stream.readAll();
723 text += QLatin1String("\n\n");
724 text += QLatin1Char('\0');
727 location.push(fileName);
731 QChar c = text.at(0);
732 uint cc = c.unicode();
733 while (i < (int) text.length()) {
736 else if (c.isSpace()) {
739 else if (cc == '#') {
742 } while (cc != '\n');
744 else if (isMetaKeyChar(c)) {
745 Location keyLoc = location;
748 QStringList stringListValue;
750 bool inQuote = false;
751 bool prevWordQuoted = true;
752 bool metWord = false;
756 stack.process(c, location);
758 } while (isMetaKeyChar(c));
760 QStringList keys = stack.getExpanded(location);
763 if (keys.count() == 1 && keys.first() == QLatin1String("include")) {
767 location.fatal(tr("Bad include syntax"));
771 while (!c.isSpace() && cc != '#' && cc != ')') {
776 while (c.isLetterOrNumber() || cc == '_') {
780 if (!var.isEmpty()) {
781 char *val = getenv(var.toLatin1().data());
783 location.fatal(tr("Environment variable '%1' undefined").arg(var));
786 includeFile += QString::fromLatin1(val);
796 location.fatal(tr("Bad include syntax"));
799 if (cc != '#' && cc != '\n')
800 location.fatal(tr("Trailing garbage"));
803 Here is the recursive call.
806 QFileInfo(QFileInfo(fileName).dir(), includeFile)
811 It wasn't an include statement, so it's something else.
818 location.fatal(tr("Expected '=' or '+=' after key"));
830 else if (cc > '0' && cc < '8') {
831 word += QChar(c.digitValue());
834 else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) {
835 word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]);
842 else if (c.isSpace() || cc == '#') {
845 location.fatal(tr("Unterminated string"));
849 if (!word.isEmpty()) {
851 stringValue += QLatin1Char(' ');
853 stringListValue << word;
856 prevWordQuoted = false;
858 if (cc == '\n' || cc == '#')
863 else if (cc == '"') {
866 stringValue += QLatin1Char(' ');
869 stringListValue << word;
872 prevWordQuoted = true;
877 else if (cc == '$') {
880 while (c.isLetterOrNumber() || cc == '_') {
884 if (!var.isEmpty()) {
885 char *val = getenv(var.toLatin1().data());
887 location.fatal(tr("Environment variable '%1' undefined").arg(var));
890 word += QString::fromLatin1(val);
895 if (!inQuote && cc == '=')
896 location.fatal(tr("Unexpected '='"));
901 QStringList::ConstIterator key = keys.begin();
902 while (key != keys.end()) {
903 if (!keySyntax.exactMatch(*key))
904 keyLoc.fatal(tr("Invalid key '%1'").arg(*key));
907 if (locMap[*key].isEmpty()) {
908 locMap[*key] = keyLoc;
911 locMap[*key].setEtc(true);
913 if (stringValueMap[*key].isEmpty()) {
914 stringValueMap[*key] = stringValue;
917 stringValueMap[*key] +=
918 QLatin1Char(' ') + stringValue;
920 stringListValueMap[*key] += stringListValue;
923 locMap[*key] = keyLoc;
924 stringValueMap[*key] = stringValue;
925 stringListValueMap[*key] = stringListValue;
932 location.fatal(tr("Unexpected character '%1' at beginning of line")
938 QStringList Config::getFilesHere(const QString& dir,
939 const QString& nameFilter,
940 const QSet<QString> &excludedDirs,
941 const QSet<QString> &excludedFiles)
944 if (excludedDirs.contains(dir))
948 QStringList fileNames;
949 QStringList::const_iterator fn;
951 dirInfo.setNameFilters(nameFilter.split(QLatin1Char(' ')));
952 dirInfo.setSorting(QDir::Name);
953 dirInfo.setFilter(QDir::Files);
954 fileNames = dirInfo.entryList();
955 fn = fileNames.constBegin();
956 while (fn != fileNames.constEnd()) {
957 if (!fn->startsWith(QLatin1Char('~'))) {
958 QString s = dirInfo.filePath(*fn);
959 QString c = QDir::cleanPath(s);
960 if (!excludedFiles.contains(c)) {
967 dirInfo.setNameFilters(QStringList(QLatin1String("*")));
968 dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot);
969 fileNames = dirInfo.entryList();
970 fn = fileNames.constBegin();
971 while (fn != fileNames.constEnd()) {
972 result += getFilesHere(dirInfo.filePath(*fn), nameFilter, excludedDirs, excludedFiles);