diff options
Diffstat (limited to 'src/qdoc/config.cpp')
-rw-r--r-- | src/qdoc/config.cpp | 1221 |
1 files changed, 1221 insertions, 0 deletions
diff --git a/src/qdoc/config.cpp b/src/qdoc/config.cpp new file mode 100644 index 000000000..6c47b86d2 --- /dev/null +++ b/src/qdoc/config.cpp @@ -0,0 +1,1221 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + config.cpp +*/ + +#include <qdir.h> +#include <qvariant.h> +#include <qfile.h> +#include <qtemporaryfile.h> +#include <qtextstream.h> +#include <qdebug.h> +#include "config.h" +#include "generator.h" +#include <stdlib.h> + +QT_BEGIN_NAMESPACE + +QString ConfigStrings::ALIAS = QStringLiteral("alias"); +QString ConfigStrings::AUTOLINKERRORS = QStringLiteral("autolinkerrors"); +QString ConfigStrings::BASE = QStringLiteral("base"); +QString ConfigStrings::BASEDIR = QStringLiteral("basedir"); +QString ConfigStrings::BUILDVERSION = QStringLiteral("buildversion"); +QString ConfigStrings::CODEINDENT = QStringLiteral("codeindent"); +QString ConfigStrings::CODEPREFIX = QStringLiteral("codeprefix"); +QString ConfigStrings::CODESUFFIX = QStringLiteral("codesuffix"); +QString ConfigStrings::CPPCLASSESPAGE = QStringLiteral("cppclassespage"); +QString ConfigStrings::DEFINES = QStringLiteral("defines"); +QString ConfigStrings::DEPENDS = QStringLiteral("depends"); +QString ConfigStrings::DESCRIPTION = QStringLiteral("description"); +QString ConfigStrings::EDITION = QStringLiteral("edition"); +QString ConfigStrings::ENDHEADER = QStringLiteral("endheader"); +QString ConfigStrings::EXAMPLEDIRS = QStringLiteral("exampledirs"); +QString ConfigStrings::EXAMPLES = QStringLiteral("examples"); +QString ConfigStrings::EXAMPLESINSTALLPATH = QStringLiteral("examplesinstallpath"); +QString ConfigStrings::EXCLUDEDIRS = QStringLiteral("excludedirs"); +QString ConfigStrings::EXCLUDEFILES = QStringLiteral("excludefiles"); +QString ConfigStrings::EXTRAIMAGES = QStringLiteral("extraimages"); +QString ConfigStrings::FALSEHOODS = QStringLiteral("falsehoods"); +QString ConfigStrings::FORMATTING = QStringLiteral("formatting"); +QString ConfigStrings::GENERATEINDEX = QStringLiteral("generateindex"); +QString ConfigStrings::HEADERDIRS = QStringLiteral("headerdirs"); +QString ConfigStrings::HEADERS = QStringLiteral("headers"); +QString ConfigStrings::HEADERSCRIPTS = QStringLiteral("headerscripts"); +QString ConfigStrings::HEADERSTYLES = QStringLiteral("headerstyles"); +QString ConfigStrings::HOMEPAGE = QStringLiteral("homepage"); +QString ConfigStrings::IGNOREDIRECTIVES = QStringLiteral("ignoredirectives"); +QString ConfigStrings::IGNORETOKENS = QStringLiteral("ignoretokens"); +QString ConfigStrings::IMAGEDIRS = QStringLiteral("imagedirs"); +QString ConfigStrings::IMAGES = QStringLiteral("images"); +QString ConfigStrings::INDEXES = QStringLiteral("indexes"); +QString ConfigStrings::LANDINGPAGE = QStringLiteral("landingpage"); +QString ConfigStrings::LANGUAGE = QStringLiteral("language"); +QString ConfigStrings::MACRO = QStringLiteral("macro"); +QString ConfigStrings::MANIFESTMETA = QStringLiteral("manifestmeta"); +QString ConfigStrings::NATURALLANGUAGE = QStringLiteral("naturallanguage"); +QString ConfigStrings::NAVIGATION = QStringLiteral("navigation"); +QString ConfigStrings::NOLINKERRORS = QStringLiteral("nolinkerrors"); +QString ConfigStrings::OBSOLETELINKS = QStringLiteral("obsoletelinks"); +QString ConfigStrings::OUTPUTDIR = QStringLiteral("outputdir"); +QString ConfigStrings::OUTPUTENCODING = QStringLiteral("outputencoding"); +QString ConfigStrings::OUTPUTLANGUAGE = QStringLiteral("outputlanguage"); +QString ConfigStrings::OUTPUTFORMATS = QStringLiteral("outputformats"); +QString ConfigStrings::OUTPUTPREFIXES = QStringLiteral("outputprefixes"); +QString ConfigStrings::OUTPUTSUFFIXES = QStringLiteral("outputsuffixes"); +QString ConfigStrings::PROJECT = QStringLiteral("project"); +QString ConfigStrings::REDIRECTDOCUMENTATIONTODEVNULL = QStringLiteral("redirectdocumentationtodevnull"); +QString ConfigStrings::QHP = QStringLiteral("qhp"); +QString ConfigStrings::QUOTINGINFORMATION = QStringLiteral("quotinginformation"); +QString ConfigStrings::SCRIPTDIRS = QStringLiteral("scriptdirs"); +QString ConfigStrings::SCRIPTS = QStringLiteral("scripts"); +QString ConfigStrings::SHOWINTERNAL = QStringLiteral("showinternal"); +QString ConfigStrings::SINGLEEXEC = QStringLiteral("singleexec"); +QString ConfigStrings::SOURCEDIRS = QStringLiteral("sourcedirs"); +QString ConfigStrings::SOURCEENCODING = QStringLiteral("sourceencoding"); +QString ConfigStrings::SOURCES = QStringLiteral("sources"); +QString ConfigStrings::SPURIOUS = QStringLiteral("spurious"); +QString ConfigStrings::STYLEDIRS = QStringLiteral("styledirs"); +QString ConfigStrings::STYLE = QStringLiteral("style"); +QString ConfigStrings::STYLES = QStringLiteral("styles"); +QString ConfigStrings::STYLESHEETS = QStringLiteral("stylesheets"); +QString ConfigStrings::SYNTAXHIGHLIGHTING = QStringLiteral("syntaxhighlighting"); +QString ConfigStrings::TEMPLATEDIR = QStringLiteral("templatedir"); +QString ConfigStrings::TABSIZE = QStringLiteral("tabsize"); +QString ConfigStrings::TAGFILE = QStringLiteral("tagfile"); +QString ConfigStrings::TRANSLATORS = QStringLiteral("translators"); +QString ConfigStrings::URL = QStringLiteral("url"); +QString ConfigStrings::VERSION = QStringLiteral("version"); +QString ConfigStrings::VERSIONSYM = QStringLiteral("versionsym"); +QString ConfigStrings::FILEEXTENSIONS = QStringLiteral("fileextensions"); +QString ConfigStrings::IMAGEEXTENSIONS = QStringLiteral("imageextensions"); +QString ConfigStrings::QMLONLY = QStringLiteral("qmlonly"); +QString ConfigStrings::QMLTYPESPAGE = QStringLiteral("qmltypespage"); +QString ConfigStrings::WRITEQAPAGES = QStringLiteral("writeqapages"); + +/*! + An entry in a stack, where each entry is a list + of string values. + */ +class MetaStackEntry +{ +public: + void open(); + void close(); + + QStringList accum; + QStringList next; +}; +Q_DECLARE_TYPEINFO(MetaStackEntry, Q_MOVABLE_TYPE); + +/*! + Start accumulating values in a list by appending an empty + string to the list. + */ +void MetaStackEntry::open() +{ + next.append(QString()); +} + +/*! + Stop accumulating values and append the list of accumulated + values to the complete list of accumulated values. + + */ +void MetaStackEntry::close() +{ + accum += next; + next.clear(); +} + +/*! + \class MetaStack + + This class maintains a stack of values of config file variables. +*/ +class MetaStack : private QStack<MetaStackEntry> +{ + Q_DECLARE_TR_FUNCTIONS(QDoc::MetaStack) + +public: + MetaStack(); + + void process(QChar ch, const Location& location); + QStringList getExpanded(const Location& location); +}; + +/*! + The default constructor pushes a new stack entry and + opens it. + */ +MetaStack::MetaStack() +{ + push(MetaStackEntry()); + top().open(); +} + +/*! + Processes the character \a ch using the \a location. + It really just builds up a name by appending \a ch to + it. + */ +void MetaStack::process(QChar ch, const Location& location) +{ + if (ch == QLatin1Char('{')) { + push(MetaStackEntry()); + top().open(); + } else if (ch == QLatin1Char('}')) { + if (count() == 1) + location.fatal(tr("Unexpected '}'")); + + top().close(); + QStringList suffixes = pop().accum; + QStringList prefixes = top().next; + + top().next.clear(); + QStringList::ConstIterator pre = prefixes.constBegin(); + while (pre != prefixes.constEnd()) { + QStringList::ConstIterator suf = suffixes.constBegin(); + while (suf != suffixes.constEnd()) { + top().next << (*pre + *suf); + ++suf; + } + ++pre; + } + } else if (ch == QLatin1Char(',') && count() > 1) { + top().close(); + top().open(); + } else { + /* + This is where all the processing is done. + */ + QStringList::Iterator pre = top().next.begin(); + while (pre != top().next.end()) { + *pre += ch; + ++pre; + } + } +} + +/*! + Returns the accumulated string values. + */ +QStringList MetaStack::getExpanded(const Location& location) +{ + if (count() > 1) + location.fatal(tr("Missing '}'")); + + top().close(); + return top().accum; +} + +const QString Config::dot = QLatin1String("."); +bool Config::debug_ = false; +bool Config::generateExamples = true; +QString Config::overrideOutputDir; +QString Config::installDir; +QSet<QString> Config::overrideOutputFormats; +QMap<QString, QString> Config::extractedDirs; +int Config::numInstances; +QStack<QString> Config::workingDirs_; +QMap<QString, QStringList> Config::includeFilesMap_; + +/*! + \class Config + \brief The Config class contains the configuration variables + for controlling how qdoc produces documentation. + + Its load() function, reads, parses, and processes a qdocconf file. + */ + +/*! + The constructor sets the \a programName and initializes all + internal state variables to empty values. + */ +Config::Config(const QString& programName) + : prog(programName) +{ + loc = Location::null; + lastLocation_ = Location::null; + configVars_.clear(); + numInstances++; + includeFilesMap_.clear(); +} + +/*! + The destructor has nothing special to do. + */ +Config::~Config() +{ + includeFilesMap_.clear(); +} + +/*! + Loads and parses the qdoc configuration file \a fileName. + This function calls the other load() function, which does + the loading, parsing, and processing of the configuration + file. + + Intializes the location variables returned by location() + and lastLocation(). + */ +void Config::load(const QString& fileName) +{ + load(Location::null, fileName); + if (loc.isEmpty()) + loc = Location(fileName); + else + loc.setEtc(true); + lastLocation_ = Location::null; +} + +/*! + Joins all the strings in \a values into a single string with the + individual \a values separated by ' '. Then it inserts the result + into the string list map with \a var as the key. + + It also inserts the \a values string list into a separate map, + also with \a var as the key. + */ +void Config::setStringList(const QString& var, const QStringList& values) +{ + configVars_.insert(var,ConfigVar(var, values, QDir::currentPath())); +} + +/*! + Looks up the configuarion variable \a var in the string + map and returns the boolean value. + */ +bool Config::getBool(const QString& var) const +{ + return QVariant(getString(var)).toBool(); +} + +/*! + Looks up the configuration variable \a var in the string list + map. Iterates through the string list found, interpreting each + string in the list as an integer and adding it to a total sum. + Returns the sum or \c -1 if \a var is not set. + */ +int Config::getInt(const QString& var) const +{ + QStringList strs = getStringList(var); + if (strs.isEmpty()) + return -1; + + QStringList::ConstIterator s = strs.constBegin(); + int sum = 0; + + while (s != strs.constEnd()) { + sum += (*s).toInt(); + ++s; + } + return sum; +} + +/*! + Function to return the correct outputdir. + outputdir can be set using the qdocconf or the command-line + variable -outputdir. + */ +QString Config::getOutputDir() const +{ + QString t; + if (overrideOutputDir.isNull()) + t = getString(CONFIG_OUTPUTDIR); + else + t = overrideOutputDir; + if (Generator::singleExec()) { + QString project = getString(CONFIG_PROJECT); + t += QLatin1Char('/') + project.toLower(); + } + if (!Generator::useOutputSubdirs()) { + t = t.left(t.lastIndexOf('/')); + QString singleOutputSubdir = getString("HTML.outputsubdir"); + if (singleOutputSubdir.isEmpty()) + singleOutputSubdir = "html"; + t += QLatin1Char('/') + singleOutputSubdir; + } + return t; +} + +/*! + Function to return the correct outputformats. + outputformats can be set using the qdocconf or the command-line + variable -outputformat. + */ +QSet<QString> Config::getOutputFormats() const +{ + if (overrideOutputFormats.isEmpty()) + return getStringSet(CONFIG_OUTPUTFORMATS); + else + return overrideOutputFormats; +} + +/*! + First, this function looks up the configuration variable \a var + in the location map and, if found, sets the internal variable + \c{lastLocation_} to the Location that \a var maps to. + + Then it looks up the configuration variable \a var in the string + map and returns the string that \a var maps to. + */ +QString Config::getString(const QString& var) const +{ + QList<ConfigVar> configVars = configVars_.values(var); + QString value; + if (!configVars.empty()) { + int i = configVars.size() - 1; + while (i >= 0) { + const ConfigVar& cv = configVars[i]; + if (!cv.location_.isEmpty()) + const_cast<Config *>(this)->lastLocation_ = cv.location_; + if (!cv.values_.isEmpty()) { + if (!cv.plus_) + value.clear(); + for (int j=0; j<cv.values_.size(); ++j) { + if (!value.isEmpty() && !value.endsWith(QChar('\n'))) + value.append(QChar(' ')); + value.append(cv.values_[j]); + } + } + --i; + } + } + return value; +} + +/*! + Looks up the configuration variable \a var in the string + list map, converts the string list it maps to into a set + of strings, and returns the set. + */ +QSet<QString> Config::getStringSet(const QString& var) const +{ + return QSet<QString>::fromList(getStringList(var)); +} + +/*! + First, this function looks up the configuration variable \a var + in the location map. If found, it sets the internal variable + \c{lastLocation_} to the Location that \a var maps to. + + Then it looks up the configuration variable \a var in the map of + configuration variable records. If found, it gets a list of all + the records for \a var. Then it appends all the values for \a var + to a list and returns the list. As it appends the values from each + record, if the \a var used '=' instead of '+=' the list is cleared + before the values are appended. \note '+=' should always be used. + The final list is returned. + */ +QStringList Config::getStringList(const QString& var) const +{ + QList<ConfigVar> configVars = configVars_.values(var); + QStringList values; + if (!configVars.empty()) { + int i = configVars.size() - 1; + while (i >= 0) { + if (!configVars[i].location_.isEmpty()) + const_cast<Config *>(this)->lastLocation_ = configVars[i].location_; + if (configVars[i].plus_) + values.append(configVars[i].values_); + else + values = configVars[i].values_; + --i; + } + } + return values; +} + +/*! + Returns the a path list where all paths from the config variable \a var + are canonicalized. If \a validate is true, a warning for invalid paths is + generated. + + First, this function looks up the configuration variable \a var + in the location map and, if found, sets the internal variable + \c{lastLocation_} the Location that \a var maps to. + + Then it looks up the configuration variable \a var in the string + list map, which maps to one or more records that each contains a + list of file paths. + + \sa Location::canonicalRelativePath() + */ +QStringList Config::getCanonicalPathList(const QString& var, bool validate) const +{ + QStringList t; + QList<ConfigVar> configVars = configVars_.values(var); + if (!configVars.empty()) { + int i = configVars.size() - 1; + while (i >= 0) { + const ConfigVar& cv = configVars[i]; + if (!cv.location_.isEmpty()) + const_cast<Config *>(this)->lastLocation_ = cv.location_; + if (!cv.plus_) + t.clear(); + const QString d = cv.currentPath_; + const QStringList& sl = cv.values_; + if (!sl.isEmpty()) { + t.reserve(t.size() + sl.size()); + for (int i=0; i<sl.size(); ++i) { + QDir dir(sl[i].simplified()); + QString path = dir.path(); + if (dir.isRelative()) + dir.setPath(d + QLatin1Char('/') + path); + if (validate && !QFileInfo::exists(dir.path())) + lastLocation_.warning(tr("Cannot find file or directory: %1").arg(path)); + else + t.append(dir.canonicalPath()); + } + } + --i; + } + } + return t; +} + +/*! + Calls getRegExpList() with the control variable \a var and + iterates through the resulting list of regular expressions, + concatening them with some extras characters to form a single + QRegExp, which is returned/ + + \sa getRegExpList() + */ +QRegExp Config::getRegExp(const QString& var) const +{ + QString pattern; + QList<QRegExp> subRegExps = getRegExpList(var); + QList<QRegExp>::ConstIterator s = subRegExps.constBegin(); + + while (s != subRegExps.constEnd()) { + if (!(*s).isValid()) + return *s; + if (!pattern.isEmpty()) + pattern += QLatin1Char('|'); + pattern += QLatin1String("(?:") + (*s).pattern() + QLatin1Char(')'); + ++s; + } + if (pattern.isEmpty()) + pattern = QLatin1String("$x"); // cannot match + return QRegExp(pattern); +} + +/*! + Looks up the configuration variable \a var in the string list + map, converts the string list to a list of regular expressions, + and returns it. + */ +QList<QRegExp> Config::getRegExpList(const QString& var) const +{ + QStringList strs = getStringList(var); + QStringList::ConstIterator s = strs.constBegin(); + QList<QRegExp> regExps; + + while (s != strs.constEnd()) { + regExps += QRegExp(*s); + ++s; + } + return regExps; +} + +/*! + This function is slower than it could be. What it does is + find all the keys that begin with \a var + dot and return + the matching keys in a set, stripped of the matching prefix + and dot. + */ +QSet<QString> Config::subVars(const QString& var) const +{ + QSet<QString> result; + QString varDot = var + QLatin1Char('.'); + ConfigVarMultimap::ConstIterator i = configVars_.constBegin(); + while (i != configVars_.constEnd()) { + if (i.key().startsWith(varDot)) { + QString subVar = i.key().mid(varDot.length()); + int dot = subVar.indexOf(QLatin1Char('.')); + if (dot != -1) + subVar.truncate(dot); + if (!result.contains(subVar)) + result.insert(subVar); + } + ++i; + } + return result; +} + +/*! + Same as subVars(), but in this case we return a config var + multimap with the matching keys (stripped of the prefix \a var + and mapped to their values. The pairs are inserted into \a t + */ +void Config::subVarsAndValues(const QString& var, ConfigVarMultimap& t) const +{ + QString varDot = var + QLatin1Char('.'); + ConfigVarMultimap::ConstIterator v = configVars_.constBegin(); + while (v != configVars_.constEnd()) { + if (v.key().startsWith(varDot)) { + QString subVar = v.key().mid(varDot.length()); + int dot = subVar.indexOf(QLatin1Char('.')); + if (dot != -1) + subVar.truncate(dot); + t.insert(subVar,v.value()); + } + ++v; + } +} + +/*! + Get all .qdocinc files. + */ +QString Config::getIncludeFilePath(const QString& fileName) const +{ + QString ext = fileName.mid(fileName.lastIndexOf('.')); + ext.prepend('*'); + + if (!includeFilesMap_.contains(ext)) { + QSet<QString> t; + QStringList result; + QStringList dirs = getCanonicalPathList(CONFIG_SOURCEDIRS); + QStringList::ConstIterator d = dirs.constBegin(); + while (d != dirs.constEnd()) { + result += getFilesHere(*d, ext, location(), t, t); + ++d; + } + includeFilesMap_.insert(ext, result); + } + const QStringList& paths = (*includeFilesMap_.find(ext)); + for (int i=0; i<paths.size(); ++i) { + if (paths[i].endsWith(fileName)) + return paths[i]; + } + return QString(); +} + +/*! + Builds and returns a list of file pathnames for the file + type specified by \a filesVar (e.g. "headers" or "sources"). + The files are found in the directories specified by + \a dirsVar, and they are filtered by \a defaultNameFilter + if a better filter can't be constructed from \a filesVar. + The directories in \a excludedDirs are avoided. The files + in \a excludedFiles are not included in the return list. + */ +QStringList Config::getAllFiles(const QString &filesVar, + const QString &dirsVar, + const QSet<QString> &excludedDirs, + const QSet<QString> &excludedFiles) +{ + QStringList result = getCanonicalPathList(filesVar); + QStringList dirs = getCanonicalPathList(dirsVar); + + QString nameFilter = getString(filesVar + dot + CONFIG_FILEEXTENSIONS); + + QStringList::ConstIterator d = dirs.constBegin(); + while (d != dirs.constEnd()) { + result += getFilesHere(*d, nameFilter, location(), excludedDirs, excludedFiles); + ++d; + } + return result; +} + +QStringList Config::getExampleQdocFiles(const QSet<QString> &excludedDirs, + const QSet<QString> &excludedFiles) +{ + QStringList result; + QStringList dirs = getCanonicalPathList("exampledirs"); + QString nameFilter = " *.qdoc"; + + QStringList::ConstIterator d = dirs.constBegin(); + while (d != dirs.constEnd()) { + result += getFilesHere(*d, nameFilter, location(), excludedDirs, excludedFiles); + ++d; + } + return result; +} + +QStringList Config::getExampleImageFiles(const QSet<QString> &excludedDirs, + const QSet<QString> &excludedFiles) +{ + QStringList result; + QStringList dirs = getCanonicalPathList("exampledirs"); + QString nameFilter = getString(CONFIG_EXAMPLES + dot + CONFIG_IMAGEEXTENSIONS); + + QStringList::ConstIterator d = dirs.constBegin(); + while (d != dirs.constEnd()) { + result += getFilesHere(*d, nameFilter, location(), excludedDirs, excludedFiles); + ++d; + } + return result; +} + +/*! + \a fileName is the path of the file to find. + + \a files and \a dirs are the lists where we must find the + components of \a fileName. + + \a location is used for obtaining the file and line numbers + for report qdoc errors. + */ +QString Config::findFile(const Location& location, + const QStringList& files, + const QStringList& dirs, + const QString& fileName, + QString& userFriendlyFilePath) +{ + if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) { + userFriendlyFilePath = fileName; + return fileName; + } + + QFileInfo fileInfo; + QStringList components = fileName.split(QLatin1Char('?')); + QString firstComponent = components.first(); + + QStringList::ConstIterator f = files.constBegin(); + while (f != files.constEnd()) { + if (*f == firstComponent || + (*f).endsWith(QLatin1Char('/') + firstComponent)) { + fileInfo.setFile(*f); + if (!fileInfo.exists()) + location.fatal(tr("File '%1' does not exist").arg(*f)); + break; + } + ++f; + } + + if (fileInfo.fileName().isEmpty()) { + QStringList::ConstIterator d = dirs.constBegin(); + while (d != dirs.constEnd()) { + fileInfo.setFile(QDir(*d), firstComponent); + if (fileInfo.exists()) + break; + ++d; + } + } + + userFriendlyFilePath = QString(); + if (!fileInfo.exists()) + return QString(); + + QStringList::ConstIterator c = components.constBegin(); + for (;;) { + bool isArchive = (c != components.constEnd() - 1); + QString userFriendly = *c; + + userFriendlyFilePath += userFriendly; + + if (isArchive) { + QString extracted = extractedDirs[fileInfo.filePath()]; + ++c; + fileInfo.setFile(QDir(extracted), *c); + } else { + break; + } + + userFriendlyFilePath += QLatin1Char('?'); + } + return fileInfo.filePath(); +} + +/*! + */ +QString Config::findFile(const Location& location, + const QStringList& files, + const QStringList& dirs, + const QString& fileBase, + const QStringList& fileExtensions, + QString& userFriendlyFilePath) +{ + QStringList::ConstIterator e = fileExtensions.constBegin(); + while (e != fileExtensions.constEnd()) { + QString filePath = findFile(location, + files, + dirs, + fileBase + QLatin1Char('.') + *e, + userFriendlyFilePath); + if (!filePath.isEmpty()) + return filePath; + ++e; + } + return findFile(location, files, dirs, fileBase, userFriendlyFilePath); +} + +/*! + Copies the \a sourceFilePath to the file name constructed by + concatenating \a targetDirPath and the file name from the + \a userFriendlySourceFilePath. \a location is for identifying + the file and line number where a qdoc error occurred. The + constructed output file name is returned. + */ +QString Config::copyFile(const Location& location, + const QString& sourceFilePath, + const QString& userFriendlySourceFilePath, + const QString& targetDirPath) +{ + QFile inFile(sourceFilePath); + if (!inFile.open(QFile::ReadOnly)) { + location.warning(tr("Cannot open input file for copy: '%1': %2") + .arg(sourceFilePath).arg(inFile.errorString())); + return QString(); + } + + QString outFileName = userFriendlySourceFilePath; + int slash = outFileName.lastIndexOf(QLatin1Char('/')); + if (slash != -1) + outFileName = outFileName.mid(slash); + if ((outFileName.size()) > 0 && (outFileName[0] != '/')) + outFileName = targetDirPath + QLatin1Char('/') + outFileName; + else + outFileName = targetDirPath + outFileName; + QFile outFile(outFileName); + if (!outFile.open(QFile::WriteOnly)) { + location.warning(tr("Cannot open output file for copy: '%1': %2") + .arg(outFileName).arg(outFile.errorString())); + return QString(); + } + + char buffer[1024]; + int len; + while ((len = inFile.read(buffer, sizeof(buffer))) > 0) + outFile.write(buffer, len); + return outFileName; +} + +/*! + Finds the largest unicode digit in \a value in the range + 1..7 and returns it. + */ +int Config::numParams(const QString& value) +{ + int max = 0; + for (int i = 0; i != value.length(); i++) { + uint c = value[i].unicode(); + if (c > 0 && c < 8) + max = qMax(max, (int)c); + } + return max; +} + +/*! + Removes everything from \a dir. This function is recursive. + It doesn't remove \a dir itself, but if it was called + recursively, then the caller will remove \a dir. + */ +bool Config::removeDirContents(const QString& dir) +{ + QDir dirInfo(dir); + QFileInfoList entries = dirInfo.entryInfoList(); + + bool ok = true; + + QFileInfoList::Iterator it = entries.begin(); + while (it != entries.end()) { + if ((*it).isFile()) { + if (!dirInfo.remove((*it).fileName())) + ok = false; + } + else if ((*it).isDir()) { + if ((*it).fileName() != QLatin1String(".") && (*it).fileName() != QLatin1String("..")) { + if (removeDirContents((*it).absoluteFilePath())) { + if (!dirInfo.rmdir((*it).fileName())) + ok = false; + } + else { + ok = false; + } + } + } + ++it; + } + return ok; +} + +/*! + Returns \c true if \a ch is a letter, number, '_', '.', + '{', '}', or ','. + */ +bool Config::isMetaKeyChar(QChar ch) +{ + return ch.isLetterOrNumber() + || ch == QLatin1Char('_') + || ch == QLatin1Char('.') + || ch == QLatin1Char('{') + || ch == QLatin1Char('}') + || ch == QLatin1Char(','); +} + +/*! + \a fileName is a master qdocconf file. It contains a list of + qdocconf files and nothing else. Read the list and return it. + */ +QStringList Config::loadMaster(const QString& fileName) +{ + Location location = Location::null; + QFile fin(fileName); + if (!fin.open(QFile::ReadOnly | QFile::Text)) { + if (!Config::installDir.isEmpty()) { + int prefix = location.filePath().length() - location.fileName().length(); + fin.setFileName(Config::installDir + QLatin1Char('/') + fileName.right(fileName.length() - prefix)); + } + if (!fin.open(QFile::ReadOnly | QFile::Text)) + location.fatal(tr("Cannot open master qdocconf file '%1': %2").arg(fileName).arg(fin.errorString())); + } + QTextStream stream(&fin); +#ifndef QT_NO_TEXTCODEC + stream.setCodec("UTF-8"); +#endif + QStringList qdocFiles; + QString line = stream.readLine(); + while (!line.isNull()) { + qdocFiles.append(line); + line = stream.readLine(); + } + fin.close(); + return qdocFiles; +} + +/*! + Load, parse, and process a qdoc configuration file. This + function is only called by the other load() function, but + this one is recursive, i.e., it calls itself when it sees + an \c{include} statement in the qdoc configuration file. + */ +void Config::load(Location location, const QString& fileName) +{ + QFileInfo fileInfo(fileName); + QString path = fileInfo.canonicalPath(); + pushWorkingDir(path); + QDir::setCurrent(path); + QRegExp keySyntax(QLatin1String("\\w+(?:\\.\\w+)*")); + +#define SKIP_CHAR() \ + do { \ + location.advance(c); \ + ++i; \ + c = text.at(i); \ + cc = c.unicode(); \ +} while (0) + +#define SKIP_SPACES() \ + while (c.isSpace() && cc != '\n') \ + SKIP_CHAR() + +#define PUT_CHAR() \ + word += c; \ + SKIP_CHAR(); + + if (location.depth() > 16) + location.fatal(tr("Too many nested includes")); + + QFile fin(fileInfo.fileName()); + if (!fin.open(QFile::ReadOnly | QFile::Text)) { + if (!Config::installDir.isEmpty()) { + int prefix = location.filePath().length() - location.fileName().length(); + fin.setFileName(Config::installDir + QLatin1Char('/') + fileName.right(fileName.length() - prefix)); + } + if (!fin.open(QFile::ReadOnly | QFile::Text)) + location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString())); + } + + QTextStream stream(&fin); +#ifndef QT_NO_TEXTCODEC + stream.setCodec("UTF-8"); +#endif + QString text = stream.readAll(); + text += QLatin1String("\n\n"); + text += QLatin1Char('\0'); + fin.close(); + + location.push(fileName); + location.start(); + + int i = 0; + QChar c = text.at(0); + uint cc = c.unicode(); + while (i < (int) text.length()) { + if (cc == 0) { + ++i; + } else if (c.isSpace()) { + SKIP_CHAR(); + } else if (cc == '#') { + do { + SKIP_CHAR(); + } while (cc != '\n'); + } else if (isMetaKeyChar(c)) { + Location keyLoc = location; + bool plus = false; + QString stringValue; + QStringList rhsValues; + QString word; + bool inQuote = false; + bool prevWordQuoted = true; + bool metWord = false; + + MetaStack stack; + do { + stack.process(c, location); + SKIP_CHAR(); + } while (isMetaKeyChar(c)); + + QStringList keys = stack.getExpanded(location); + SKIP_SPACES(); + + if (keys.count() == 1 && keys.first() == QLatin1String("include")) { + QString includeFile; + + if (cc != '(') + location.fatal(tr("Bad include syntax")); + SKIP_CHAR(); + SKIP_SPACES(); + + while (!c.isSpace() && cc != '#' && cc != ')') { + + if (cc == '$') { + QString var; + SKIP_CHAR(); + while (c.isLetterOrNumber() || cc == '_') { + var += c; + SKIP_CHAR(); + } + if (!var.isEmpty()) { + const QByteArray val = qgetenv(var.toLatin1().data()); + if (val.isNull()) { + location.fatal(tr("Environment variable '%1' undefined").arg(var)); + } + else { + includeFile += QString::fromLatin1(val); + } + } + } else { + includeFile += c; + SKIP_CHAR(); + } + } + SKIP_SPACES(); + if (cc != ')') + location.fatal(tr("Bad include syntax")); + SKIP_CHAR(); + SKIP_SPACES(); + if (cc != '#' && cc != '\n') + location.fatal(tr("Trailing garbage")); + + /* + Here is the recursive call. + */ + load(location, QFileInfo(QDir(path), includeFile).filePath()); + } + else { + /* + It wasn't an include statement, so it's something else. + We must see either '=' or '+=' next. If not, fatal error. + */ + if (cc == '+') { + plus = true; + SKIP_CHAR(); + } + if (cc != '=') + location.fatal(tr("Expected '=' or '+=' after key")); + SKIP_CHAR(); + SKIP_SPACES(); + + for (;;) { + if (cc == '\\') { + int metaCharPos; + + SKIP_CHAR(); + if (cc == '\n') { + SKIP_CHAR(); + } + else if (cc > '0' && cc < '8') { + word += QChar(c.digitValue()); + SKIP_CHAR(); + } + else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) { + word += QLatin1Char("\a\b\f\n\r\t\v"[metaCharPos]); + SKIP_CHAR(); + } + else { + PUT_CHAR(); + } + } + else if (c.isSpace() || cc == '#') { + if (inQuote) { + if (cc == '\n') + location.fatal(tr("Unterminated string")); + PUT_CHAR(); + } + else { + if (!word.isEmpty()) { + if (metWord) + stringValue += QLatin1Char(' '); + stringValue += word; +#if 0 + if (metWord) + rhsValues << QString(" " + word); + else +#endif + rhsValues << word; + metWord = true; + word.clear(); + prevWordQuoted = false; + } + if (cc == '\n' || cc == '#') + break; + SKIP_SPACES(); + } + } + else if (cc == '"') { + if (inQuote) { + if (!prevWordQuoted) + stringValue += QLatin1Char(' '); + stringValue += word; + if (!word.isEmpty()) + rhsValues << word; + metWord = true; + word.clear(); + prevWordQuoted = true; + } + inQuote = !inQuote; + SKIP_CHAR(); + } + else if (cc == '$') { + QString var; + SKIP_CHAR(); + while (c.isLetterOrNumber() || cc == '_') { + var += c; + SKIP_CHAR(); + } + if (!var.isEmpty()) { + const QByteArray val = qgetenv(var.toLatin1().constData()); + if (val.isNull()) { + location.fatal(tr("Environment variable '%1' undefined").arg(var)); + } + else { + word += QString::fromLatin1(val); + } + } + } + else { + if (!inQuote && cc == '=') + location.fatal(tr("Unexpected '='")); + PUT_CHAR(); + } + } + + QStringList::ConstIterator key = keys.constBegin(); + while (key != keys.constEnd()) { + if (!keySyntax.exactMatch(*key)) + keyLoc.fatal(tr("Invalid key '%1'").arg(*key)); + + ConfigVarMultimap::Iterator i; + i = configVars_.insert(*key, ConfigVar(*key, rhsValues, QDir::currentPath(), keyLoc)); + i.value().plus_ = plus; + ++key; + } + } + } else { + location.fatal(tr("Unexpected character '%1' at beginning of line").arg(c)); + } + } + popWorkingDir(); + if (!workingDirs_.isEmpty()) + QDir::setCurrent(workingDirs_.top()); +} + +QStringList Config::getFilesHere(const QString& uncleanDir, + const QString& nameFilter, + const Location &location, + const QSet<QString> &excludedDirs, + const QSet<QString> &excludedFiles) +{ + QString dir = location.isEmpty() ? QDir::cleanPath(uncleanDir) : QDir(uncleanDir).canonicalPath(); + QStringList result; + if (excludedDirs.contains(dir)) + return result; + + QDir dirInfo(dir); + QStringList fileNames; + QStringList::const_iterator fn; + + dirInfo.setNameFilters(nameFilter.split(QLatin1Char(' '))); + dirInfo.setSorting(QDir::Name); + dirInfo.setFilter(QDir::Files); + fileNames = dirInfo.entryList(); + fn = fileNames.constBegin(); + while (fn != fileNames.constEnd()) { + if (!fn->startsWith(QLatin1Char('~'))) { + QString s = dirInfo.filePath(*fn); + QString c = QDir::cleanPath(s); + if (!excludedFiles.contains(c)) + result.append(c); + } + ++fn; + } + + dirInfo.setNameFilters(QStringList(QLatin1String("*"))); + dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot); + fileNames = dirInfo.entryList(); + fn = fileNames.constBegin(); + while (fn != fileNames.constEnd()) { + result += getFilesHere(dirInfo.filePath(*fn), nameFilter, location, excludedDirs, excludedFiles); + ++fn; + } + return result; +} + +/*! + Push \a dir onto the stack of working directories. + */ +void Config::pushWorkingDir(const QString& dir) +{ + workingDirs_.push(dir); +} + +/*! + If the stack of working directories is not empty, pop the + top entry and return it. Otherwise return an empty string. + */ +QString Config::popWorkingDir() +{ + if (!workingDirs_.isEmpty()) + return workingDirs_.pop(); + + qDebug() << "RETURNED EMPTY WORKING DIR"; + return QString(); +} + +QT_END_NAMESPACE |