Merge branch 'master' of scm.dev.nokia.troll.no:qt/oslo-staging-1 into master-integration
[qt-netbsd.git] / tools / qdoc3 / config.cpp
blobf62ec241dacaa725b16a70fe7a810ffb35d2ab0f
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the tools applications of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
38 ** $QT_END_LICENSE$
40 ****************************************************************************/
43 config.cpp
46 #include <QtCore>
48 #include "archiveextractor.h"
49 #include "config.h"
50 #include "uncompressor.h"
51 #include <stdlib.h>
53 QT_BEGIN_NAMESPACE
56 An entry on the MetaStack.
58 class MetaStackEntry
60 public:
61 void open();
62 void close();
64 QStringList accum;
65 QStringList next;
70 void MetaStackEntry::open()
72 next.append(QString());
77 void MetaStackEntry::close()
79 accum += next;
80 next.clear();
84 ###
86 class MetaStack : private QStack<MetaStackEntry>
88 public:
89 MetaStack();
91 void process(QChar ch, const Location& location);
92 QStringList getExpanded(const Location& location);
95 MetaStack::MetaStack()
97 push(MetaStackEntry());
98 top().open();
101 void MetaStack::process(QChar ch, const Location& location)
103 if (ch == QLatin1Char('{')) {
104 push(MetaStackEntry());
105 top().open();
107 else if (ch == QLatin1Char('}')) {
108 if (count() == 1)
109 location.fatal(tr("Unexpected '}'"));
111 top().close();
112 QStringList suffixes = pop().accum;
113 QStringList prefixes = top().next;
115 top().next.clear();
116 QStringList::ConstIterator pre = prefixes.begin();
117 while (pre != prefixes.end()) {
118 QStringList::ConstIterator suf = suffixes.begin();
119 while (suf != suffixes.end()) {
120 top().next << (*pre + *suf);
121 ++suf;
123 ++pre;
126 else if (ch == QLatin1Char(',') && count() > 1) {
127 top().close();
128 top().open();
130 else {
131 QStringList::Iterator pre = top().next.begin();
132 while (pre != top().next.end()) {
133 *pre += ch;
134 ++pre;
139 QStringList MetaStack::getExpanded(const Location& location)
141 if (count() > 1)
142 location.fatal(tr("Missing '}'"));
144 top().close();
145 return top().accum;
148 QT_STATIC_CONST_IMPL QString Config::dot = QLatin1String(".");
149 QMap<QString, QString> Config::uncompressedFiles;
150 QMap<QString, QString> Config::extractedDirs;
151 int Config::numInstances;
154 \class Config
155 \brief The Config class contains the configuration variables
156 for controlling how qdoc produces documentation.
158 Its load() function, reads, parses, and processes a qdocconf file.
162 The constructor sets the \a programName and initializes all
163 internal state variables to empty values.
165 Config::Config(const QString& programName)
166 : prog(programName)
168 loc = Location::null;
169 lastLoc = Location::null;
170 locMap.clear();
171 stringValueMap.clear();
172 stringListValueMap.clear();
173 numInstances++;
177 The destructor deletes all the temporary files and
178 directories it built.
180 Config::~Config()
182 if (--numInstances == 0) {
183 QMap<QString, QString>::ConstIterator f = uncompressedFiles.begin();
184 while (f != uncompressedFiles.end()) {
185 QDir().remove(*f);
186 ++f;
188 uncompressedFiles.clear();
190 QMap<QString, QString>::ConstIterator d = extractedDirs.begin();
191 while (d != extractedDirs.end()) {
192 removeDirContents(*d);
193 QDir dir(*d);
194 QString name = dir.dirName();
195 dir.cdUp();
196 dir.rmdir(name);
197 ++d;
199 extractedDirs.clear();
204 Loads and parses the qdoc configuration file \a fileName.
205 This function calls the other load() function, which does
206 the loading, parsing, and processing of the configuration
207 file.
209 Intializes the location variables returned by location()
210 and lastLocation().
212 void Config::load(const QString& fileName)
214 load(Location::null, fileName);
215 if (loc.isEmpty()) {
216 loc = Location(fileName);
218 else {
219 loc.setEtc(true);
221 lastLoc = Location::null;
225 Joins all the strings in \a values into a single string with the
226 individual \a values separated by ' '. Then it inserts the result
227 into the string list map with \a var as the key.
229 It also inserts the \a values string list into a separate map,
230 also with \a var as the key.
232 void Config::setStringList(const QString& var, const QStringList& values)
234 stringValueMap[var] = values.join(QLatin1String(" "));
235 stringListValueMap[var] = values;
239 Looks up the configuarion variable \a var in the string
240 map and returns the boolean value.
242 bool Config::getBool(const QString& var) const
244 return QVariant(getString(var)).toBool();
248 Looks up the configuration variable \a var in the string list
249 map. Iterates through the string list found, interpreting each
250 string in the list as an integer and adding it to a total sum.
251 Returns the sum.
253 int Config::getInt(const QString& var) const
255 QStringList strs = getStringList(var);
256 QStringList::ConstIterator s = strs.begin();
257 int sum = 0;
259 while (s != strs.end()) {
260 sum += (*s).toInt();
261 ++s;
263 return sum;
267 First, this function looks up the configuration variable \a var
268 in the location map and, if found, sets the internal variable
269 \c{lastLoc} to the Location that \a var maps to.
271 Then it looks up the configuration variable \a var in the string
272 map, and returns the string that \a var maps to.
274 QString Config::getString(const QString& var) const
276 if (!locMap[var].isEmpty())
277 (Location&) lastLoc = locMap[var];
278 return stringValueMap[var];
282 Looks up the configuration variable \a var in the string
283 list map, converts the string list it maps to into a set
284 of strings, and returns the set.
286 QSet<QString> Config::getStringSet(const QString& var) const
288 return QSet<QString>::fromList(getStringList(var));
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} the Location that \a var maps to.
296 Then it looks up the configuration variable \a var in the string
297 list map, and returns the string list that \a var maps to.
299 QStringList Config::getStringList(const QString& var) const
301 if (!locMap[var].isEmpty())
302 (Location&) lastLoc = locMap[var];
303 return stringListValueMap[var];
307 Calls getRegExpList() with the control variable \a var and
308 iterates through the resulting list of regular expressions,
309 concatening them with some extras characters to form a single
310 QRegExp, which is returned/
312 \sa getRegExpList()
314 QRegExp Config::getRegExp(const QString& var) const
316 QString pattern;
317 QList<QRegExp> subRegExps = getRegExpList(var);
318 QList<QRegExp>::ConstIterator s = subRegExps.begin();
320 while (s != subRegExps.end()) {
321 if (!(*s).isValid())
322 return *s;
323 if (!pattern.isEmpty())
324 pattern += QLatin1Char('|');
325 pattern += QLatin1String("(?:") + (*s).pattern() + QLatin1Char(')');
326 ++s;
328 if (pattern.isEmpty())
329 pattern = QLatin1String("$x"); // cannot match
330 return QRegExp(pattern);
334 Looks up the configuration variable \a var in the string list
335 map, converts the string list to a list of regular expressions,
336 and returns it.
338 QList<QRegExp> Config::getRegExpList(const QString& var) const
340 QStringList strs = getStringList(var);
341 QStringList::ConstIterator s = strs.begin();
342 QList<QRegExp> regExps;
344 while (s != strs.end()) {
345 regExps += QRegExp(*s);
346 ++s;
348 return regExps;
352 This function is slower than it could be.
354 QSet<QString> Config::subVars(const QString& var) const
356 QSet<QString> result;
357 QString varDot = var + QLatin1Char('.');
358 QMap<QString, QString>::ConstIterator v = stringValueMap.begin();
359 while (v != stringValueMap.end()) {
360 if (v.key().startsWith(varDot)) {
361 QString subVar = v.key().mid(varDot.length());
362 int dot = subVar.indexOf(QLatin1Char('.'));
363 if (dot != -1)
364 subVar.truncate(dot);
365 result.insert(subVar);
367 ++v;
369 return result;
373 Builds and returns a list of file pathnames for the file
374 type specified by \a filesVar (e.g. "headers" or "sources").
375 The files are found in the directories specified by
376 \a dirsVar, and they are filtered by \a defaultNameFilter
377 if a better filter can't be constructed from \a filesVar.
378 The directories in \a excludedDirs are avoided.
380 QStringList Config::getAllFiles(const QString &filesVar,
381 const QString &dirsVar,
382 const QString &defaultNameFilter,
383 const QSet<QString> &excludedDirs)
385 QStringList result = getStringList(filesVar);
386 QStringList dirs = getStringList(dirsVar);
388 QString nameFilter = getString(filesVar + dot +
389 QLatin1String(CONFIG_FILEEXTENSIONS));
390 if (nameFilter.isEmpty())
391 nameFilter = defaultNameFilter;
393 QStringList::ConstIterator d = dirs.begin();
394 while (d != dirs.end()) {
395 result += getFilesHere(*d, nameFilter, excludedDirs);
396 ++d;
398 return result;
402 \a fileName is the path of the file to find.
404 \a files and \a dirs are the lists where we must find the
405 components of \a fileName.
407 \a location is used for obtaining the file and line numbers
408 for report qdoc errors.
410 QString Config::findFile(const Location& location,
411 const QStringList& files,
412 const QStringList& dirs,
413 const QString& fileName,
414 QString& userFriendlyFilePath)
416 if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) {
417 userFriendlyFilePath = fileName;
418 return fileName;
421 QFileInfo fileInfo;
422 QStringList components = fileName.split(QLatin1Char('?'));
423 QString firstComponent = components.first();
425 QStringList::ConstIterator f = files.begin();
426 while (f != files.end()) {
427 if (*f == firstComponent ||
428 (*f).endsWith(QLatin1Char('/') + firstComponent)) {
429 fileInfo.setFile(*f);
430 if (!fileInfo.exists())
431 location.fatal(tr("File '%1' does not exist").arg(*f));
432 break;
434 ++f;
437 if (fileInfo.fileName().isEmpty()) {
438 QStringList::ConstIterator d = dirs.begin();
439 while (d != dirs.end()) {
440 fileInfo.setFile(QDir(*d), firstComponent);
441 if (fileInfo.exists()) {
442 break;
444 ++d;
448 userFriendlyFilePath = QString();
449 if (!fileInfo.exists())
450 return QString();
452 QStringList::ConstIterator c = components.begin();
453 for (;;) {
454 bool isArchive = (c != components.end() - 1);
455 ArchiveExtractor *extractor = 0;
456 QString userFriendly = *c;
458 if (isArchive) {
459 extractor = ArchiveExtractor::extractorForFileName(userFriendly);
462 if (extractor == 0) {
463 Uncompressor *uncompressor =
464 Uncompressor::uncompressorForFileName(userFriendly);
465 if (uncompressor != 0) {
466 QString fileNameWithCorrectExtension =
467 uncompressor->uncompressedFilePath(
468 fileInfo.filePath());
469 QString uncompressed = uncompressedFiles[fileInfo.filePath()];
470 if (uncompressed.isEmpty()) {
471 uncompressed =
472 QTemporaryFile(fileInfo.filePath()).fileName();
473 uncompressor->uncompressFile(location,
474 fileInfo.filePath(),
475 uncompressed);
476 uncompressedFiles[fileInfo.filePath()] = uncompressed;
478 fileInfo.setFile(uncompressed);
480 if (isArchive) {
481 extractor = ArchiveExtractor::extractorForFileName(
482 fileNameWithCorrectExtension);
484 else {
485 userFriendly = fileNameWithCorrectExtension;
489 userFriendlyFilePath += userFriendly;
491 if (isArchive) {
492 if (extractor == 0)
493 location.fatal(tr("Unknown archive type '%1'")
494 .arg(userFriendlyFilePath));
495 QString extracted = extractedDirs[fileInfo.filePath()];
496 if (extracted.isEmpty()) {
497 extracted = QTemporaryFile(fileInfo.filePath()).fileName();
498 if (!QDir().mkdir(extracted))
499 location.fatal(tr("Cannot create temporary directory '%1'")
500 .arg(extracted));
501 extractor->extractArchive(location, fileInfo.filePath(),
502 extracted);
503 extractedDirs[fileInfo.filePath()] = extracted;
505 ++c;
506 fileInfo.setFile(QDir(extracted), *c);
508 else {
509 break;
511 userFriendlyFilePath += "?";
513 return fileInfo.filePath();
518 QString Config::findFile(const Location& location,
519 const QStringList& files,
520 const QStringList& dirs,
521 const QString& fileBase,
522 const QStringList& fileExtensions,
523 QString& userFriendlyFilePath)
525 QStringList::ConstIterator e = fileExtensions.begin();
526 while (e != fileExtensions.end()) {
527 QString filePath = findFile(location, files, dirs, fileBase + "." + *e,
528 userFriendlyFilePath);
529 if (!filePath.isEmpty())
530 return filePath;
531 ++e;
533 return findFile(location, files, dirs, fileBase, userFriendlyFilePath);
537 Copies the \a sourceFilePath to the file name constructed by
538 concatenating \a targetDirPath and \a userFriendlySourceFilePath.
539 \a location is for identifying the file and line number where
540 a qdoc error occurred. The constructed output file name is
541 returned.
543 QString Config::copyFile(const Location& location,
544 const QString& sourceFilePath,
545 const QString& userFriendlySourceFilePath,
546 const QString& targetDirPath)
548 QFile inFile(sourceFilePath);
549 if (!inFile.open(QFile::ReadOnly)) {
550 location.fatal(tr("Cannot open input file '%1': %2")
551 .arg(inFile.fileName()).arg(inFile.errorString()));
552 return "";
555 QString outFileName = userFriendlySourceFilePath;
556 int slash = outFileName.lastIndexOf("/");
557 if (slash != -1)
558 outFileName = outFileName.mid(slash);
560 QFile outFile(targetDirPath + "/" + outFileName);
561 if (!outFile.open(QFile::WriteOnly)) {
562 location.fatal(tr("Cannot open output file '%1': %2")
563 .arg(outFile.fileName()).arg(outFile.errorString()));
564 return "";
567 char buffer[1024];
568 int len;
569 while ((len = inFile.read(buffer, sizeof(buffer))) > 0) {
570 outFile.write(buffer, len);
572 return outFileName;
576 Finds the largest unicode digit in \a value in the range
577 1..7 and returns it.
579 int Config::numParams(const QString& value)
581 int max = 0;
582 for (int i = 0; i != value.length(); i++) {
583 uint c = value[i].unicode();
584 if (c > 0 && c < 8)
585 max = qMax(max, (int)c);
587 return max;
591 Removes everything from \a dir. This function is recursive.
592 It doesn't remove \a dir itself, but if it was called
593 recursively, then the caller will remove \a dir.
595 bool Config::removeDirContents(const QString& dir)
597 QDir dirInfo(dir);
598 QFileInfoList entries = dirInfo.entryInfoList();
600 bool ok = true;
602 QFileInfoList::Iterator it = entries.begin();
603 while (it != entries.end()) {
604 if ((*it).isFile()) {
605 if (!dirInfo.remove((*it).fileName()))
606 ok = false;
608 else if ((*it).isDir()) {
609 if ((*it).fileName() != "." && (*it).fileName() != "..") {
610 if (removeDirContents((*it).absoluteFilePath())) {
611 if (!dirInfo.rmdir((*it).fileName()))
612 ok = false;
614 else {
615 ok = false;
619 ++it;
621 return ok;
625 Returns true if \a ch is a letter, number, '_', '.',
626 '{', '}', or ','.
628 bool Config::isMetaKeyChar(QChar ch)
630 return ch.isLetterOrNumber()
631 || ch == QLatin1Char('_')
632 || ch == QLatin1Char('.')
633 || ch == QLatin1Char('{')
634 || ch == QLatin1Char('}')
635 || ch == QLatin1Char(',');
639 Load, parse, and process a qdoc configuration file. This
640 function is only called by the other load() function, but
641 this one is recursive, i.e., it calls itself when it sees
642 an \c{include} statement in the qdog configuration file.
644 void Config::load(Location location, const QString& fileName)
646 QRegExp keySyntax("\\w+(?:\\.\\w+)*");
648 #define SKIP_CHAR() \
649 do { \
650 location.advance(c); \
651 ++i; \
652 c = text.at(i); \
653 cc = c.unicode(); \
654 } while (0)
656 #define SKIP_SPACES() \
657 while (c.isSpace() && cc != '\n') \
658 SKIP_CHAR()
660 #define PUT_CHAR() \
661 word += c; \
662 SKIP_CHAR();
664 if (location.depth() > 16)
665 location.fatal(tr("Too many nested includes"));
667 QFile fin(fileName);
668 if (!fin.open(QFile::ReadOnly | QFile::Text)) {
669 fin.setFileName(fileName + ".qdoc");
670 if (!fin.open(QFile::ReadOnly | QFile::Text))
671 location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString()));
674 QString text = fin.readAll();
675 text += QLatin1String("\n\n");
676 text += QChar('\0');
677 fin.close();
679 location.push(fileName);
680 location.start();
682 int i = 0;
683 QChar c = text.at(0);
684 uint cc = c.unicode();
685 while (i < (int) text.length()) {
686 if (cc == 0)
687 ++i;
688 else if (c.isSpace()) {
689 SKIP_CHAR();
691 else if (cc == '#') {
692 do {
693 SKIP_CHAR();
694 } while (cc != '\n');
696 else if (isMetaKeyChar(c)) {
697 Location keyLoc = location;
698 bool plus = false;
699 QString stringValue;
700 QStringList stringListValue;
701 QString word;
702 bool inQuote = false;
703 bool prevWordQuoted = true;
704 bool metWord = false;
706 MetaStack stack;
707 do {
708 stack.process(c, location);
709 SKIP_CHAR();
710 } while (isMetaKeyChar(c));
712 QStringList keys = stack.getExpanded(location);
713 SKIP_SPACES();
715 if (keys.count() == 1 && keys.first() == "include") {
716 QString includeFile;
718 if (cc != '(')
719 location.fatal(tr("Bad include syntax"));
720 SKIP_CHAR();
721 SKIP_SPACES();
722 while (!c.isSpace() && cc != '#' && cc != ')') {
723 includeFile += c;
724 SKIP_CHAR();
726 SKIP_SPACES();
727 if (cc != ')')
728 location.fatal(tr("Bad include syntax"));
729 SKIP_CHAR();
730 SKIP_SPACES();
731 if (cc != '#' && cc != '\n')
732 location.fatal(tr("Trailing garbage"));
735 Here is the recursive call.
737 load(location,
738 QFileInfo(QFileInfo(fileName).dir(), includeFile)
739 .filePath());
741 else {
743 It wasn't an include statement, so it;s something else.
745 if (cc == '+') {
746 plus = true;
747 SKIP_CHAR();
749 if (cc != '=')
750 location.fatal(tr("Expected '=' or '+=' after key"));
751 SKIP_CHAR();
752 SKIP_SPACES();
754 for (;;) {
755 if (cc == '\\') {
756 int metaCharPos;
758 SKIP_CHAR();
759 if (cc == '\n') {
760 SKIP_CHAR();
762 else if (cc > '0' && cc < '8') {
763 word += QChar(c.digitValue());
764 SKIP_CHAR();
766 else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) {
767 word += "\a\b\f\n\r\t\v"[metaCharPos];
768 SKIP_CHAR();
770 else {
771 PUT_CHAR();
774 else if (c.isSpace() || cc == '#') {
775 if (inQuote) {
776 if (cc == '\n')
777 location.fatal(tr("Unterminated string"));
778 PUT_CHAR();
780 else {
781 if (!word.isEmpty()) {
782 if (metWord)
783 stringValue += QLatin1Char(' ');
784 stringValue += word;
785 stringListValue << word;
786 metWord = true;
787 word.clear();
788 prevWordQuoted = false;
790 if (cc == '\n' || cc == '#')
791 break;
792 SKIP_SPACES();
795 else if (cc == '"') {
796 if (inQuote) {
797 if (!prevWordQuoted)
798 stringValue += QLatin1Char(' ');
799 stringValue += word;
800 if (!word.isEmpty())
801 stringListValue << word;
802 metWord = true;
803 word.clear();
804 prevWordQuoted = true;
806 inQuote = !inQuote;
807 SKIP_CHAR();
809 else if (cc == '$') {
810 QString var;
811 SKIP_CHAR();
812 while (c.isLetterOrNumber() || cc == '_') {
813 var += c;
814 SKIP_CHAR();
816 if (!var.isEmpty()) {
817 char *val = getenv(var.toLatin1().data());
818 if (val == 0) {
819 location.fatal(tr("Environment variable '%1' undefined").arg(var));
821 else {
822 word += QString(val);
826 else {
827 if (!inQuote && cc == '=')
828 location.fatal(tr("Unexpected '='"));
829 PUT_CHAR();
833 QStringList::ConstIterator key = keys.begin();
834 while (key != keys.end()) {
835 if (!keySyntax.exactMatch(*key))
836 keyLoc.fatal(tr("Invalid key '%1'").arg(*key));
838 if (plus) {
839 if (locMap[*key].isEmpty()) {
840 locMap[*key] = keyLoc;
842 else {
843 locMap[*key].setEtc(true);
845 if (stringValueMap[*key].isEmpty()) {
846 stringValueMap[*key] = stringValue;
848 else {
849 stringValueMap[*key] +=
850 QLatin1Char(' ') + stringValue;
852 stringListValueMap[*key] += stringListValue;
854 else {
855 locMap[*key] = keyLoc;
856 stringValueMap[*key] = stringValue;
857 stringListValueMap[*key] = stringListValue;
859 ++key;
863 else {
864 location.fatal(tr("Unexpected character '%1' at beginning of line")
865 .arg(c));
870 QStringList Config::getFilesHere(const QString& dir,
871 const QString& nameFilter,
872 const QSet<QString> &excludedDirs)
874 QStringList result;
875 if (excludedDirs.contains(dir))
876 return result;
878 QDir dirInfo(dir);
879 QStringList fileNames;
880 QStringList::const_iterator fn;
882 dirInfo.setNameFilters(nameFilter.split(' '));
883 dirInfo.setSorting(QDir::Name);
884 dirInfo.setFilter(QDir::Files);
885 fileNames = dirInfo.entryList();
886 fn = fileNames.constBegin();
887 while (fn != fileNames.constEnd()) {
888 if (!fn->startsWith(QLatin1Char('~')))
889 result.append(dirInfo.filePath(*fn));
890 ++fn;
893 dirInfo.setNameFilters(QStringList("*"));
894 dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot);
895 fileNames = dirInfo.entryList();
896 fn = fileNames.constBegin();
897 while (fn != fileNames.constEnd()) {
898 result += getFilesHere(dirInfo.filePath(*fn), nameFilter, excludedDirs);
899 ++fn;
901 return result;
904 QT_END_NAMESPACE