1 /****************************************************************************
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the tools applications of the Qt Toolkit.
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
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.
40 ****************************************************************************/
48 #include "archiveextractor.h"
50 #include "uncompressor.h"
56 An entry on the MetaStack.
70 void MetaStackEntry::open()
72 next
.append(QString());
77 void MetaStackEntry::close()
86 class MetaStack
: private QStack
<MetaStackEntry
>
91 void process(QChar ch
, const Location
& location
);
92 QStringList
getExpanded(const Location
& location
);
95 MetaStack::MetaStack()
97 push(MetaStackEntry());
101 void MetaStack::process(QChar ch
, const Location
& location
)
103 if (ch
== QLatin1Char('{')) {
104 push(MetaStackEntry());
107 else if (ch
== QLatin1Char('}')) {
109 location
.fatal(tr("Unexpected '}'"));
112 QStringList suffixes
= pop().accum
;
113 QStringList prefixes
= top().next
;
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
);
126 else if (ch
== QLatin1Char(',') && count() > 1) {
131 QStringList::Iterator pre
= top().next
.begin();
132 while (pre
!= top().next
.end()) {
139 QStringList
MetaStack::getExpanded(const Location
& location
)
142 location
.fatal(tr("Missing '}'"));
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
;
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
)
168 loc
= Location::null
;
169 lastLoc
= Location::null
;
171 stringValueMap
.clear();
172 stringListValueMap
.clear();
177 The destructor deletes all the temporary files and
178 directories it built.
182 if (--numInstances
== 0) {
183 QMap
<QString
, QString
>::ConstIterator f
= uncompressedFiles
.begin();
184 while (f
!= uncompressedFiles
.end()) {
188 uncompressedFiles
.clear();
190 QMap
<QString
, QString
>::ConstIterator d
= extractedDirs
.begin();
191 while (d
!= extractedDirs
.end()) {
192 removeDirContents(*d
);
194 QString name
= dir
.dirName();
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
209 Intializes the location variables returned by location()
212 void Config::load(const QString
& fileName
)
214 load(Location::null
, fileName
);
216 loc
= Location(fileName
);
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.
253 int Config::getInt(const QString
& var
) const
255 QStringList strs
= getStringList(var
);
256 QStringList::ConstIterator s
= strs
.begin();
259 while (s
!= strs
.end()) {
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/
314 QRegExp
Config::getRegExp(const QString
& var
) const
317 QList
<QRegExp
> subRegExps
= getRegExpList(var
);
318 QList
<QRegExp
>::ConstIterator s
= subRegExps
.begin();
320 while (s
!= subRegExps
.end()) {
323 if (!pattern
.isEmpty())
324 pattern
+= QLatin1Char('|');
325 pattern
+= QLatin1String("(?:") + (*s
).pattern() + QLatin1Char(')');
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,
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
);
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('.'));
364 subVar
.truncate(dot
);
365 result
.insert(subVar
);
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
);
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
;
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
));
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()) {
448 userFriendlyFilePath
= QString();
449 if (!fileInfo
.exists())
452 QStringList::ConstIterator c
= components
.begin();
454 bool isArchive
= (c
!= components
.end() - 1);
455 ArchiveExtractor
*extractor
= 0;
456 QString userFriendly
= *c
;
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()) {
472 QTemporaryFile(fileInfo
.filePath()).fileName();
473 uncompressor
->uncompressFile(location
,
476 uncompressedFiles
[fileInfo
.filePath()] = uncompressed
;
478 fileInfo
.setFile(uncompressed
);
481 extractor
= ArchiveExtractor::extractorForFileName(
482 fileNameWithCorrectExtension
);
485 userFriendly
= fileNameWithCorrectExtension
;
489 userFriendlyFilePath
+= userFriendly
;
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'")
501 extractor
->extractArchive(location
, fileInfo
.filePath(),
503 extractedDirs
[fileInfo
.filePath()] = extracted
;
506 fileInfo
.setFile(QDir(extracted
), *c
);
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())
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
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()));
555 QString outFileName
= userFriendlySourceFilePath
;
556 int slash
= outFileName
.lastIndexOf("/");
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()));
569 while ((len
= inFile
.read(buffer
, sizeof(buffer
))) > 0) {
570 outFile
.write(buffer
, len
);
576 Finds the largest unicode digit in \a value in the range
579 int Config::numParams(const QString
& value
)
582 for (int i
= 0; i
!= value
.length(); i
++) {
583 uint c
= value
[i
].unicode();
585 max
= qMax(max
, (int)c
);
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
)
598 QFileInfoList entries
= dirInfo
.entryInfoList();
602 QFileInfoList::Iterator it
= entries
.begin();
603 while (it
!= entries
.end()) {
604 if ((*it
).isFile()) {
605 if (!dirInfo
.remove((*it
).fileName()))
608 else if ((*it
).isDir()) {
609 if ((*it
).fileName() != "." && (*it
).fileName() != "..") {
610 if (removeDirContents((*it
).absoluteFilePath())) {
611 if (!dirInfo
.rmdir((*it
).fileName()))
625 Returns true if \a ch is a letter, number, '_', '.',
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() \
650 location.advance(c); \
656 #define SKIP_SPACES() \
657 while (c.isSpace() && cc != '\n') \
664 if (location
.depth() > 16)
665 location
.fatal(tr("Too many nested includes"));
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");
679 location
.push(fileName
);
683 QChar c
= text
.at(0);
684 uint cc
= c
.unicode();
685 while (i
< (int) text
.length()) {
688 else if (c
.isSpace()) {
691 else if (cc
== '#') {
694 } while (cc
!= '\n');
696 else if (isMetaKeyChar(c
)) {
697 Location keyLoc
= location
;
700 QStringList stringListValue
;
702 bool inQuote
= false;
703 bool prevWordQuoted
= true;
704 bool metWord
= false;
708 stack
.process(c
, location
);
710 } while (isMetaKeyChar(c
));
712 QStringList keys
= stack
.getExpanded(location
);
715 if (keys
.count() == 1 && keys
.first() == "include") {
719 location
.fatal(tr("Bad include syntax"));
722 while (!c
.isSpace() && cc
!= '#' && cc
!= ')') {
728 location
.fatal(tr("Bad include syntax"));
731 if (cc
!= '#' && cc
!= '\n')
732 location
.fatal(tr("Trailing garbage"));
735 Here is the recursive call.
738 QFileInfo(QFileInfo(fileName
).dir(), includeFile
)
743 It wasn't an include statement, so it;s something else.
750 location
.fatal(tr("Expected '=' or '+=' after key"));
762 else if (cc
> '0' && cc
< '8') {
763 word
+= QChar(c
.digitValue());
766 else if ((metaCharPos
= QString::fromLatin1("abfnrtv").indexOf(c
)) != -1) {
767 word
+= "\a\b\f\n\r\t\v"[metaCharPos
];
774 else if (c
.isSpace() || cc
== '#') {
777 location
.fatal(tr("Unterminated string"));
781 if (!word
.isEmpty()) {
783 stringValue
+= QLatin1Char(' ');
785 stringListValue
<< word
;
788 prevWordQuoted
= false;
790 if (cc
== '\n' || cc
== '#')
795 else if (cc
== '"') {
798 stringValue
+= QLatin1Char(' ');
801 stringListValue
<< word
;
804 prevWordQuoted
= true;
809 else if (cc
== '$') {
812 while (c
.isLetterOrNumber() || cc
== '_') {
816 if (!var
.isEmpty()) {
817 char *val
= getenv(var
.toLatin1().data());
819 location
.fatal(tr("Environment variable '%1' undefined").arg(var
));
822 word
+= QString(val
);
827 if (!inQuote
&& cc
== '=')
828 location
.fatal(tr("Unexpected '='"));
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
));
839 if (locMap
[*key
].isEmpty()) {
840 locMap
[*key
] = keyLoc
;
843 locMap
[*key
].setEtc(true);
845 if (stringValueMap
[*key
].isEmpty()) {
846 stringValueMap
[*key
] = stringValue
;
849 stringValueMap
[*key
] +=
850 QLatin1Char(' ') + stringValue
;
852 stringListValueMap
[*key
] += stringListValue
;
855 locMap
[*key
] = keyLoc
;
856 stringValueMap
[*key
] = stringValue
;
857 stringListValueMap
[*key
] = stringListValue
;
864 location
.fatal(tr("Unexpected character '%1' at beginning of line")
870 QStringList
Config::getFilesHere(const QString
& dir
,
871 const QString
& nameFilter
,
872 const QSet
<QString
> &excludedDirs
)
875 if (excludedDirs
.contains(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
));
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
);