aboutsummaryrefslogtreecommitdiffstats
path: root/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp')
-rw-r--r--sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp2429
1 files changed, 2429 insertions, 0 deletions
diff --git a/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp
new file mode 100644
index 000000000..53b403022
--- /dev/null
+++ b/sources/shiboken6/generator/qtdoc/qtdocgenerator.cpp
@@ -0,0 +1,2429 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt for Python.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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 https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtdocgenerator.h"
+#include "ctypenames.h"
+#include <abstractmetalang.h>
+#include <messages.h>
+#include <propertyspec.h>
+#include <reporthandler.h>
+#include <typesystem.h>
+#include <qtdocparser.h>
+#include <doxygenparser.h>
+#include <typedatabase.h>
+#include <algorithm>
+#include <QtCore/QStack>
+#include <QtCore/QRegularExpression>
+#include <QtCore/QTextStream>
+#include <QtCore/QXmlStreamReader>
+#include <QtCore/QFile>
+#include <QtCore/QDir>
+#include <fileout.h>
+#include <limits>
+
+static Indentor INDENT;
+
+static inline QString additionalDocumentationOption() { return QStringLiteral("additional-documentation"); }
+
+static inline QString nameAttribute() { return QStringLiteral("name"); }
+static inline QString titleAttribute() { return QStringLiteral("title"); }
+static inline QString fullTitleAttribute() { return QStringLiteral("fulltitle"); }
+static inline QString briefAttribute() { return QStringLiteral("brief"); }
+static inline QString briefStartElement() { return QStringLiteral("<brief>"); }
+static inline QString briefEndElement() { return QStringLiteral("</brief>"); }
+
+static inline QString none() { return QStringLiteral("None"); }
+
+static void stripPythonQualifiers(QString *s)
+{
+ const int lastSep = s->lastIndexOf(QLatin1Char('.'));
+ if (lastSep != -1)
+ s->remove(0, lastSep + 1);
+}
+
+static bool shouldSkip(const AbstractMetaFunction* func)
+{
+ // Constructors go to separate section
+ if (DocParser::skipForQuery(func) || func->isConstructor())
+ return true;
+
+ // Search a const clone (QImage::bits() vs QImage::bits() const)
+ if (func->isConstant())
+ return false;
+
+ const AbstractMetaArgumentList funcArgs = func->arguments();
+ const AbstractMetaFunctionList &ownerFunctions = func->ownerClass()->functions();
+ for (AbstractMetaFunction *f : ownerFunctions) {
+ if (f != func
+ && f->isConstant()
+ && f->name() == func->name()
+ && f->arguments().count() == funcArgs.count()) {
+ // Compare each argument
+ bool cloneFound = true;
+
+ const AbstractMetaArgumentList fargs = f->arguments();
+ for (int i = 0, max = funcArgs.count(); i < max; ++i) {
+ if (funcArgs.at(i).type().typeEntry() != fargs.at(i).type().typeEntry()) {
+ cloneFound = false;
+ break;
+ }
+ }
+ if (cloneFound)
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool functionSort(const AbstractMetaFunction* func1, const AbstractMetaFunction* func2)
+{
+ return func1->name() < func2->name();
+}
+
+class Pad
+{
+public:
+ explicit Pad(char c, int count) : m_char(c), m_count(count) {}
+
+ void write(QTextStream &str) const
+ {
+ for (int i = 0; i < m_count; ++i)
+ str << m_char;
+ }
+
+private:
+ const char m_char;
+ const int m_count;
+};
+
+inline QTextStream &operator<<(QTextStream &str, const Pad &pad)
+{
+ pad.write(str);
+ return str;
+}
+
+template <class String>
+static int writeEscapedRstText(QTextStream &str, const String &s)
+{
+ int escaped = 0;
+ for (const QChar &c : s) {
+ switch (c.unicode()) {
+ case '*':
+ case '`':
+ case '_':
+ case '\\':
+ str << '\\';
+ ++escaped;
+ break;
+ }
+ str << c;
+ }
+ return s.size() + escaped;
+}
+
+class escape
+{
+public:
+ explicit escape(QStringView s) : m_string(s) {}
+
+ void write(QTextStream &str) const { writeEscapedRstText(str, m_string); }
+
+private:
+ const QStringView m_string;
+};
+
+inline QTextStream &operator<<(QTextStream &str, const escape &e)
+{
+ e.write(str);
+ return str;
+}
+
+// Return last character of a QString-buffered stream.
+static QChar lastChar(const QTextStream &str)
+{
+ const QString *string = str.string();
+ Q_ASSERT(string);
+ return string->isEmpty() ? QChar() : *(string->crbegin());
+}
+
+static QTextStream &ensureEndl(QTextStream &s)
+{
+ if (lastChar(s) != QLatin1Char('\n'))
+ s << Qt::endl;
+ return s;
+}
+
+static inline QVersionNumber versionOf(const TypeEntry *te)
+{
+ if (te) {
+ const auto version = te->version();
+ if (!version.isNull() && version > QVersionNumber(0, 0))
+ return version;
+ }
+ return QVersionNumber();
+}
+
+struct rstVersionAdded
+{
+ explicit rstVersionAdded(const QVersionNumber &v) : m_version(v) {}
+
+ const QVersionNumber m_version;
+};
+
+static QTextStream &operator<<(QTextStream &s, const rstVersionAdded &v)
+{
+ s << ".. versionadded:: "<< v.m_version.toString() << "\n\n";
+ return s;
+}
+
+static QByteArray rstDeprecationNote(const char *what)
+{
+ return QByteArrayLiteral(".. note:: This ")
+ + what + QByteArrayLiteral(" is deprecated.\n\n");
+}
+
+// RST anchor string: Anything else but letters, numbers, '_' or '.' replaced by '-'
+static inline bool isValidRstLabelChar(QChar c)
+{
+ return c.isLetterOrNumber() || c == QLatin1Char('_') || c == QLatin1Char('.');
+}
+
+static QString toRstLabel(QString s)
+{
+ for (int i = 0, size = s.size(); i < size; ++i) {
+ if (!isValidRstLabelChar(s.at(i)))
+ s[i] = QLatin1Char('-');
+ }
+ return s;
+}
+
+class rstLabel
+{
+public:
+ explicit rstLabel(const QString &l) : m_label(l) {}
+
+ friend QTextStream &operator<<(QTextStream &str, const rstLabel &a)
+ {
+ str << ".. _" << toRstLabel(a.m_label) << ":\n\n";
+ return str;
+ }
+
+private:
+ const QString &m_label;
+};
+
+struct QtXmlToSphinx::LinkContext
+{
+ enum Type
+ {
+ Method = 0x1, Function = 0x2,
+ FunctionMask = Method | Function,
+ Class = 0x4, Attribute = 0x8, Module = 0x10,
+ Reference = 0x20, External= 0x40
+ };
+
+ enum Flags { InsideBold = 0x1, InsideItalic = 0x2 };
+
+ explicit LinkContext(const QString &ref) : linkRef(ref) {}
+
+ QString linkRef;
+ QString linkText;
+ Type type = Reference;
+ int flags = 0;
+};
+
+static const char *linkKeyWord(QtXmlToSphinx::LinkContext::Type type)
+{
+ switch (type) {
+ case QtXmlToSphinx::LinkContext::Method:
+ return ":meth:";
+ case QtXmlToSphinx::LinkContext::Function:
+ return ":func:";
+ case QtXmlToSphinx::LinkContext::Class:
+ return ":class:";
+ case QtXmlToSphinx::LinkContext::Attribute:
+ return ":attr:";
+ case QtXmlToSphinx::LinkContext::Module:
+ return ":mod:";
+ case QtXmlToSphinx::LinkContext::Reference:
+ return ":ref:";
+ case QtXmlToSphinx::LinkContext::External:
+ break;
+ case QtXmlToSphinx::LinkContext::FunctionMask:
+ break;
+ }
+ return "";
+}
+
+QTextStream &operator<<(QTextStream &str, const QtXmlToSphinx::LinkContext &linkContext)
+{
+ // Temporarily turn off bold/italic since links do not work within
+ if (linkContext.flags & QtXmlToSphinx::LinkContext::InsideBold)
+ str << "**";
+ else if (linkContext.flags & QtXmlToSphinx::LinkContext::InsideItalic)
+ str << '*';
+ str << ' ' << linkKeyWord(linkContext.type) << '`';
+ const bool isExternal = linkContext.type == QtXmlToSphinx::LinkContext::External;
+ if (!linkContext.linkText.isEmpty()) {
+ writeEscapedRstText(str, linkContext.linkText);
+ if (isExternal && !linkContext.linkText.endsWith(QLatin1Char(' ')))
+ str << ' ';
+ str << '<';
+ }
+ // Convert page titles to RST labels
+ str << (linkContext.type == QtXmlToSphinx::LinkContext::Reference
+ ? toRstLabel(linkContext.linkRef) : linkContext.linkRef);
+ if (!linkContext.linkText.isEmpty())
+ str << '>';
+ str << '`';
+ if (isExternal)
+ str << '_';
+ str << ' ';
+ if (linkContext.flags & QtXmlToSphinx::LinkContext::InsideBold)
+ str << "**";
+ else if (linkContext.flags & QtXmlToSphinx::LinkContext::InsideItalic)
+ str << '*';
+ return str;
+}
+
+QtXmlToSphinx::QtXmlToSphinx(QtDocGenerator* generator, const QString& doc, const QString& context)
+ : m_tableHasHeader(false), m_context(context), m_generator(generator), m_insideBold(false), m_insideItalic(false)
+{
+ m_handlerMap.insert(QLatin1String("heading"), &QtXmlToSphinx::handleHeadingTag);
+ m_handlerMap.insert(QLatin1String("brief"), &QtXmlToSphinx::handleParaTag);
+ m_handlerMap.insert(QLatin1String("para"), &QtXmlToSphinx::handleParaTag);
+ m_handlerMap.insert(QLatin1String("italic"), &QtXmlToSphinx::handleItalicTag);
+ m_handlerMap.insert(QLatin1String("bold"), &QtXmlToSphinx::handleBoldTag);
+ m_handlerMap.insert(QLatin1String("see-also"), &QtXmlToSphinx::handleSeeAlsoTag);
+ m_handlerMap.insert(QLatin1String("snippet"), &QtXmlToSphinx::handleSnippetTag);
+ m_handlerMap.insert(QLatin1String("dots"), &QtXmlToSphinx::handleDotsTag);
+ m_handlerMap.insert(QLatin1String("codeline"), &QtXmlToSphinx::handleDotsTag);
+ m_handlerMap.insert(QLatin1String("table"), &QtXmlToSphinx::handleTableTag);
+ m_handlerMap.insert(QLatin1String("header"), &QtXmlToSphinx::handleRowTag);
+ m_handlerMap.insert(QLatin1String("row"), &QtXmlToSphinx::handleRowTag);
+ m_handlerMap.insert(QLatin1String("item"), &QtXmlToSphinx::handleItemTag);
+ m_handlerMap.insert(QLatin1String("argument"), &QtXmlToSphinx::handleArgumentTag);
+ m_handlerMap.insert(QLatin1String("teletype"), &QtXmlToSphinx::handleArgumentTag);
+ m_handlerMap.insert(QLatin1String("link"), &QtXmlToSphinx::handleLinkTag);
+ m_handlerMap.insert(QLatin1String("inlineimage"), &QtXmlToSphinx::handleInlineImageTag);
+ m_handlerMap.insert(QLatin1String("image"), &QtXmlToSphinx::handleImageTag);
+ m_handlerMap.insert(QLatin1String("list"), &QtXmlToSphinx::handleListTag);
+ m_handlerMap.insert(QLatin1String("term"), &QtXmlToSphinx::handleTermTag);
+ m_handlerMap.insert(QLatin1String("raw"), &QtXmlToSphinx::handleRawTag);
+ m_handlerMap.insert(QLatin1String("underline"), &QtXmlToSphinx::handleItalicTag);
+ m_handlerMap.insert(QLatin1String("superscript"), &QtXmlToSphinx::handleSuperScriptTag);
+ m_handlerMap.insert(QLatin1String("code"), &QtXmlToSphinx::handleCodeTag);
+ m_handlerMap.insert(QLatin1String("badcode"), &QtXmlToSphinx::handleCodeTag);
+ m_handlerMap.insert(QLatin1String("legalese"), &QtXmlToSphinx::handleCodeTag);
+ m_handlerMap.insert(QLatin1String("rst"), &QtXmlToSphinx::handleRstPassTroughTag);
+ m_handlerMap.insert(QLatin1String("section"), &QtXmlToSphinx::handleAnchorTag);
+ m_handlerMap.insert(QLatin1String("quotefile"), &QtXmlToSphinx::handleQuoteFileTag);
+
+ // ignored tags
+ m_handlerMap.insert(QLatin1String("generatedlist"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("tableofcontents"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("quotefromfile"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("skipto"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("target"), &QtXmlToSphinx::handleTargetTag);
+ m_handlerMap.insert(QLatin1String("page"), &QtXmlToSphinx::handlePageTag);
+ m_handlerMap.insert(QLatin1String("group"), &QtXmlToSphinx::handlePageTag);
+
+ // useless tags
+ m_handlerMap.insert(QLatin1String("description"), &QtXmlToSphinx::handleUselessTag);
+ m_handlerMap.insert(QLatin1String("definition"), &QtXmlToSphinx::handleUselessTag);
+ m_handlerMap.insert(QLatin1String("printuntil"), &QtXmlToSphinx::handleUselessTag);
+ m_handlerMap.insert(QLatin1String("relation"), &QtXmlToSphinx::handleUselessTag);
+
+ // Doxygen tags
+ m_handlerMap.insert(QLatin1String("title"), &QtXmlToSphinx::handleHeadingTag);
+ m_handlerMap.insert(QLatin1String("ref"), &QtXmlToSphinx::handleParaTag);
+ m_handlerMap.insert(QLatin1String("computeroutput"), &QtXmlToSphinx::handleParaTag);
+ m_handlerMap.insert(QLatin1String("detaileddescription"), &QtXmlToSphinx::handleParaTag);
+ m_handlerMap.insert(QLatin1String("name"), &QtXmlToSphinx::handleParaTag);
+ m_handlerMap.insert(QLatin1String("listitem"), &QtXmlToSphinx::handleItemTag);
+ m_handlerMap.insert(QLatin1String("parametername"), &QtXmlToSphinx::handleItemTag);
+ m_handlerMap.insert(QLatin1String("parameteritem"), &QtXmlToSphinx::handleItemTag);
+ m_handlerMap.insert(QLatin1String("ulink"), &QtXmlToSphinx::handleLinkTag);
+ m_handlerMap.insert(QLatin1String("itemizedlist"), &QtXmlToSphinx::handleListTag);
+ m_handlerMap.insert(QLatin1String("parameternamelist"), &QtXmlToSphinx::handleListTag);
+ m_handlerMap.insert(QLatin1String("parameterlist"), &QtXmlToSphinx::handleListTag);
+
+ // Doxygen ignored tags
+ m_handlerMap.insert(QLatin1String("highlight"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("linebreak"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("programlisting"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("xreftitle"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("sp"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("entry"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("simplesect"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("verbatim"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("xrefsect"), &QtXmlToSphinx::handleIgnoredTag);
+ m_handlerMap.insert(QLatin1String("xrefdescription"), &QtXmlToSphinx::handleIgnoredTag);
+
+ m_result = transform(doc);
+}
+
+void QtXmlToSphinx::pushOutputBuffer()
+{
+ auto *buffer = new QString();
+ m_buffers << buffer;
+ m_output.setString(buffer);
+}
+
+QString QtXmlToSphinx::popOutputBuffer()
+{
+ Q_ASSERT(!m_buffers.isEmpty());
+ QString* str = m_buffers.pop();
+ QString strcpy(*str);
+ delete str;
+ m_output.setString(m_buffers.isEmpty() ? 0 : m_buffers.top());
+ return strcpy;
+}
+
+QString QtXmlToSphinx::expandFunction(const QString& function) const
+{
+ const int firstDot = function.indexOf(QLatin1Char('.'));
+ const AbstractMetaClass *metaClass = nullptr;
+ if (firstDot != -1) {
+ const auto className = QStringView{function}.left(firstDot);
+ for (const AbstractMetaClass *cls : m_generator->classes()) {
+ if (cls->name() == className) {
+ metaClass = cls;
+ break;
+ }
+ }
+ }
+
+ return metaClass
+ ? metaClass->typeEntry()->qualifiedTargetLangName()
+ + function.right(function.size() - firstDot)
+ : function;
+}
+
+QString QtXmlToSphinx::resolveContextForMethod(const QString& methodName) const
+{
+ const auto currentClass = QStringView{m_context}.split(QLatin1Char('.')).constLast();
+
+ const AbstractMetaClass *metaClass = nullptr;
+ for (const AbstractMetaClass *cls : m_generator->classes()) {
+ if (cls->name() == currentClass) {
+ metaClass = cls;
+ break;
+ }
+ }
+
+ if (metaClass) {
+ AbstractMetaFunctionList funcList;
+ const AbstractMetaFunctionList &methods = metaClass->queryFunctionsByName(methodName);
+ for (AbstractMetaFunction *func : methods) {
+ if (methodName == func->name())
+ funcList.append(func);
+ }
+
+ const AbstractMetaClass *implementingClass = nullptr;
+ for (AbstractMetaFunction *func : qAsConst(funcList)) {
+ implementingClass = func->implementingClass();
+ if (implementingClass->name() == currentClass)
+ break;
+ }
+
+ if (implementingClass)
+ return implementingClass->typeEntry()->qualifiedTargetLangName();
+ }
+
+ return QLatin1Char('~') + m_context;
+}
+
+QString QtXmlToSphinx::transform(const QString& doc)
+{
+ Q_ASSERT(m_buffers.isEmpty());
+ Indentation indentation(INDENT);
+ if (doc.trimmed().isEmpty())
+ return doc;
+
+ pushOutputBuffer();
+
+ QXmlStreamReader reader(doc);
+
+ while (!reader.atEnd()) {
+ QXmlStreamReader::TokenType token = reader.readNext();
+ if (reader.hasError()) {
+ QString message;
+ QTextStream(&message) << "XML Error "
+ << reader.errorString() << " at " << reader.lineNumber()
+ << ':' << reader.columnNumber() << '\n' << doc;
+ m_output << INDENT << message;
+ qCWarning(lcShibokenDoc).noquote().nospace() << message;
+ break;
+ }
+
+ if (token == QXmlStreamReader::StartElement) {
+ const auto tagName = reader.name();
+ TagHandler handler = m_handlerMap.value(tagName.toString(), &QtXmlToSphinx::handleUnknownTag);
+ if (!m_handlers.isEmpty() && ( (m_handlers.top() == &QtXmlToSphinx::handleIgnoredTag) ||
+ (m_handlers.top() == &QtXmlToSphinx::handleRawTag)) )
+ handler = &QtXmlToSphinx::handleIgnoredTag;
+
+ m_handlers.push(handler);
+ }
+ if (!m_handlers.isEmpty())
+ (this->*(m_handlers.top()))(reader);
+
+ if (token == QXmlStreamReader::EndElement) {
+ m_handlers.pop();
+ m_lastTagName = reader.name().toString();
+ }
+ }
+
+ if (!m_inlineImages.isEmpty()) {
+ // Write out inline image definitions stored in handleInlineImageTag().
+ m_output << Qt::endl;
+ for (const InlineImage &img : qAsConst(m_inlineImages))
+ m_output << ".. |" << img.tag << "| image:: " << img.href << Qt::endl;
+ m_output << Qt::endl;
+ m_inlineImages.clear();
+ }
+
+ m_output.flush();
+ QString retval = popOutputBuffer();
+ Q_ASSERT(m_buffers.isEmpty());
+ return retval;
+}
+
+static QString resolveFile(const QStringList &locations, const QString &path)
+{
+ for (QString location : locations) {
+ location.append(QLatin1Char('/'));
+ location.append(path);
+ if (QFileInfo::exists(location))
+ return location;
+ }
+ return QString();
+}
+
+QString QtXmlToSphinx::readFromLocations(const QStringList &locations, const QString &path,
+ const QString &identifier, QString *errorMessage)
+{
+ QString resolvedPath;
+ if (path.endsWith(QLatin1String(".cpp"))) {
+ const QString pySnippet = path.left(path.size() - 3) + QLatin1String("py");
+ resolvedPath = resolveFile(locations, pySnippet);
+ }
+ if (resolvedPath.isEmpty())
+ resolvedPath = resolveFile(locations, path);
+ if (resolvedPath.isEmpty()) {
+ QTextStream(errorMessage) << "Could not resolve \"" << path << "\" in \""
+ << locations.join(QLatin1String("\", \""));
+ return QString(); // null
+ }
+ qCDebug(lcShibokenDoc).noquote().nospace() << "snippet file " << path
+ << " [" << identifier << ']' << " resolved to " << resolvedPath;
+ return readFromLocation(resolvedPath, identifier, errorMessage);
+}
+
+QString QtXmlToSphinx::readFromLocation(const QString &location, const QString &identifier,
+ QString *errorMessage)
+{
+ QFile inputFile;
+ inputFile.setFileName(location);
+ if (!inputFile.open(QIODevice::ReadOnly)) {
+ QTextStream(errorMessage) << "Could not read code snippet file: "
+ << QDir::toNativeSeparators(inputFile.fileName())
+ << ": " << inputFile.errorString();
+ return QString(); // null
+ }
+
+ QString code = QLatin1String(""); // non-null
+ if (identifier.isEmpty()) {
+ while (!inputFile.atEnd())
+ code += QString::fromUtf8(inputFile.readLine());
+ return code;
+ }
+
+ const QRegularExpression searchString(QLatin1String("//!\\s*\\[")
+ + identifier + QLatin1String("\\]"));
+ Q_ASSERT(searchString.isValid());
+ static const QRegularExpression codeSnippetCode(QLatin1String("//!\\s*\\[[\\w\\d\\s]+\\]"));
+ Q_ASSERT(codeSnippetCode.isValid());
+
+ bool getCode = false;
+
+ while (!inputFile.atEnd()) {
+ QString line = QString::fromUtf8(inputFile.readLine());
+ if (getCode && !line.contains(searchString)) {
+ line.remove(codeSnippetCode);
+ code += line;
+ } else if (line.contains(searchString)) {
+ if (getCode)
+ break;
+ getCode = true;
+ }
+ }
+
+ if (!getCode) {
+ QTextStream(errorMessage) << "Code snippet file found ("
+ << QDir::toNativeSeparators(location) << "), but snippet ["
+ << identifier << "] not found.";
+ return QString(); // null
+ }
+
+ return code;
+}
+
+void QtXmlToSphinx::handleHeadingTag(QXmlStreamReader& reader)
+{
+ static int headingSize = 0;
+ static char type;
+ static char types[] = { '-', '^' };
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ uint typeIdx = reader.attributes().value(QLatin1String("level")).toUInt();
+ if (typeIdx >= sizeof(types))
+ type = types[sizeof(types)-1];
+ else
+ type = types[typeIdx];
+ } else if (token == QXmlStreamReader::EndElement) {
+ m_output << Pad(type, headingSize) << Qt::endl << Qt::endl;
+ } else if (token == QXmlStreamReader::Characters) {
+ m_output << Qt::endl << Qt::endl;
+ headingSize = writeEscapedRstText(m_output, reader.text().trimmed());
+ m_output << Qt::endl;
+ }
+}
+
+void QtXmlToSphinx::handleParaTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ pushOutputBuffer();
+ } else if (token == QXmlStreamReader::EndElement) {
+ QString result = popOutputBuffer().simplified();
+ if (result.startsWith(QLatin1String("**Warning:**")))
+ result.replace(0, 12, QLatin1String(".. warning:: "));
+ else if (result.startsWith(QLatin1String("**Note:**")))
+ result.replace(0, 9, QLatin1String(".. note:: "));
+
+ m_output << INDENT << result << Qt::endl << Qt::endl;
+ } else if (token == QXmlStreamReader::Characters) {
+ const auto text = reader.text();
+ const QChar end = lastChar(m_output);
+ if (!text.isEmpty() && INDENT.indent == 0 && !end.isNull()) {
+ QChar start = text[0];
+ if ((end == QLatin1Char('*') || end == QLatin1Char('`')) && start != QLatin1Char(' ') && !start.isPunct())
+ m_output << '\\';
+ }
+ m_output << INDENT << escape(text);
+ }
+}
+
+void QtXmlToSphinx::handleItalicTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement || token == QXmlStreamReader::EndElement) {
+ m_insideItalic = !m_insideItalic;
+ m_output << '*';
+ } else if (token == QXmlStreamReader::Characters) {
+ m_output << escape(reader.text().trimmed());
+ }
+}
+
+void QtXmlToSphinx::handleBoldTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement || token == QXmlStreamReader::EndElement) {
+ m_insideBold = !m_insideBold;
+ m_output << "**";
+ } else if (token == QXmlStreamReader::Characters) {
+ m_output << escape(reader.text().trimmed());
+ }
+}
+
+void QtXmlToSphinx::handleArgumentTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement || token == QXmlStreamReader::EndElement)
+ m_output << "``";
+ else if (token == QXmlStreamReader::Characters)
+ m_output << reader.text().trimmed();
+}
+
+static inline QString functionLinkType() { return QStringLiteral("function"); }
+static inline QString classLinkType() { return QStringLiteral("class"); }
+
+static inline QString fixLinkType(QStringView type)
+{
+ // TODO: create a flag PROPERTY-AS-FUNCTION to ask if the properties
+ // are recognized as such or not in the binding
+ if (type == QLatin1String("property"))
+ return functionLinkType();
+ if (type == QLatin1String("typedef"))
+ return classLinkType();
+ return type.toString();
+}
+
+static inline QString linkSourceAttribute(const QString &type)
+{
+ if (type == functionLinkType() || type == classLinkType())
+ return QLatin1String("raw");
+ return type == QLatin1String("enum") || type == QLatin1String("page")
+ ? type : QLatin1String("href");
+}
+
+// "See also" links may appear as nested links:
+// <see-also>QAbstractXmlReceiver<link raw="isValid()" href="qxmlquery.html#isValid" type="function">isValid()</link>
+// which is handled in handleLinkTag
+// or direct text:
+// <see-also>rootIsDecorated()</see-also>
+// which is handled here.
+
+void QtXmlToSphinx::handleSeeAlsoTag(QXmlStreamReader& reader)
+{
+ switch (reader.tokenType()) {
+ case QXmlStreamReader::StartElement:
+ m_output << INDENT << ".. seealso:: ";
+ break;
+ case QXmlStreamReader::Characters: {
+ // Direct embedded link: <see-also>rootIsDecorated()</see-also>
+ const auto textR = reader.text().trimmed();
+ if (!textR.isEmpty()) {
+ const QString text = textR.toString();
+ if (m_seeAlsoContext.isNull()) {
+ const QString type = text.endsWith(QLatin1String("()"))
+ ? functionLinkType() : classLinkType();
+ m_seeAlsoContext.reset(handleLinkStart(type, text));
+ }
+ handleLinkText(m_seeAlsoContext.data(), text);
+ }
+ }
+ break;
+ case QXmlStreamReader::EndElement:
+ if (!m_seeAlsoContext.isNull()) { // direct, no nested </link> seen
+ handleLinkEnd(m_seeAlsoContext.data());
+ m_seeAlsoContext.reset();
+ }
+ m_output << Qt::endl << Qt::endl;
+ break;
+ default:
+ break;
+ }
+}
+
+static inline QString fallbackPathAttribute() { return QStringLiteral("path"); }
+
+static inline bool snippetComparison()
+{
+ return ReportHandler::debugLevel() >= ReportHandler::FullDebug;
+}
+
+template <class Indent> // const char*/class Indentor
+void formatSnippet(QTextStream &str, Indent indent, const QString &snippet)
+{
+ const auto lines = QStringView{snippet}.split(QLatin1Char('\n'));
+ for (const auto &line : lines) {
+ if (!line.trimmed().isEmpty())
+ str << indent << line;
+ str << Qt::endl;
+ }
+}
+
+static QString msgSnippetComparison(const QString &location, const QString &identifier,
+ const QString &pythonCode, const QString &fallbackCode)
+{
+ QString result;
+ QTextStream str(&result);
+ str << "Python snippet " << location;
+ if (!identifier.isEmpty())
+ str << " [" << identifier << ']';
+ str << ":\n";
+ formatSnippet(str, " ", pythonCode);
+ str << "Corresponding fallback snippet:\n";
+ formatSnippet(str, " ", fallbackCode);
+ str << "-- end --\n";
+ return result;
+}
+
+void QtXmlToSphinx::handleSnippetTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ const bool consecutiveSnippet = m_lastTagName == QLatin1String("snippet")
+ || m_lastTagName == QLatin1String("dots") || m_lastTagName == QLatin1String("codeline");
+ if (consecutiveSnippet) {
+ m_output.flush();
+ m_output.string()->chop(2);
+ }
+ QString location = reader.attributes().value(QLatin1String("location")).toString();
+ QString identifier = reader.attributes().value(QLatin1String("identifier")).toString();
+ QString errorMessage;
+ const QString pythonCode =
+ readFromLocations(m_generator->codeSnippetDirs(), location, identifier, &errorMessage);
+ if (!errorMessage.isEmpty())
+ qCWarning(lcShibokenDoc, "%s", qPrintable(msgTagWarning(reader, m_context, m_lastTagName, errorMessage)));
+ // Fall back to C++ snippet when "path" attribute is present.
+ // Also read fallback snippet when comparison is desired.
+ QString fallbackCode;
+ if ((pythonCode.isEmpty() || snippetComparison())
+ && reader.attributes().hasAttribute(fallbackPathAttribute())) {
+ const QString fallback = reader.attributes().value(fallbackPathAttribute()).toString();
+ if (QFileInfo::exists(fallback)) {
+ if (pythonCode.isEmpty())
+ qCWarning(lcShibokenDoc, "%s", qPrintable(msgFallbackWarning(reader, m_context, m_lastTagName, location, identifier, fallback)));
+ fallbackCode = readFromLocation(fallback, identifier, &errorMessage);
+ if (!errorMessage.isEmpty())
+ qCWarning(lcShibokenDoc, "%s", qPrintable(msgTagWarning(reader, m_context, m_lastTagName, errorMessage)));
+ }
+ }
+
+ if (!pythonCode.isEmpty() && !fallbackCode.isEmpty() && snippetComparison())
+ qCDebug(lcShibokenDoc, "%s", qPrintable(msgSnippetComparison(location, identifier, pythonCode, fallbackCode)));
+
+ if (!consecutiveSnippet)
+ m_output << INDENT << "::\n\n";
+
+ Indentation indentation(INDENT);
+ const QString code = pythonCode.isEmpty() ? fallbackCode : pythonCode;
+ if (code.isEmpty())
+ m_output << INDENT << "<Code snippet \"" << location << ':' << identifier << "\" not found>\n";
+ else
+ formatSnippet(m_output, INDENT, code);
+ m_output << Qt::endl;
+ }
+}
+void QtXmlToSphinx::handleDotsTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ const bool consecutiveSnippet = m_lastTagName == QLatin1String("snippet")
+ || m_lastTagName == QLatin1String("dots") || m_lastTagName == QLatin1String("codeline");
+ if (consecutiveSnippet) {
+ m_output.flush();
+ m_output.string()->chop(2);
+ } else {
+ m_output << INDENT << "::\n\n";
+ }
+ Indentation indentation(INDENT);
+ pushOutputBuffer();
+ m_output << INDENT;
+ int indent = reader.attributes().value(QLatin1String("indent")).toInt();
+ for (int i = 0; i < indent; ++i)
+ m_output << ' ';
+ } else if (token == QXmlStreamReader::Characters) {
+ m_output << reader.text().toString();
+ } else if (token == QXmlStreamReader::EndElement) {
+ m_output << popOutputBuffer() << "\n\n\n";
+ }
+}
+
+void QtXmlToSphinx::handleTableTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ m_currentTable.clear();
+ m_tableHasHeader = false;
+ } else if (token == QXmlStreamReader::EndElement) {
+ // write the table on m_output
+ m_currentTable.setHeaderEnabled(m_tableHasHeader);
+ m_currentTable.normalize();
+ m_output << ensureEndl << m_currentTable;
+ m_currentTable.clear();
+ }
+}
+
+void QtXmlToSphinx::handleTermTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ pushOutputBuffer();
+ } else if (token == QXmlStreamReader::Characters) {
+ m_output << reader.text().toString().replace(QLatin1String("::"), QLatin1String("."));
+ } else if (token == QXmlStreamReader::EndElement) {
+ TableCell cell;
+ cell.data = popOutputBuffer().trimmed();
+ m_currentTable.appendRow(TableRow(1, cell));
+ }
+}
+
+
+void QtXmlToSphinx::handleItemTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ if (m_currentTable.isEmpty())
+ m_currentTable.appendRow({});
+ TableRow& row = m_currentTable.last();
+ TableCell cell;
+ cell.colSpan = reader.attributes().value(QLatin1String("colspan")).toShort();
+ cell.rowSpan = reader.attributes().value(QLatin1String("rowspan")).toShort();
+ row << cell;
+ pushOutputBuffer();
+ } else if (token == QXmlStreamReader::EndElement) {
+ QString data = popOutputBuffer().trimmed();
+ if (!m_currentTable.isEmpty()) {
+ TableRow& row = m_currentTable.last();
+ if (!row.isEmpty())
+ row.last().data = data;
+ }
+ }
+}
+
+void QtXmlToSphinx::handleRowTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ m_tableHasHeader = reader.name() == QLatin1String("header");
+ m_currentTable.appendRow({});
+ }
+}
+
+enum ListType { BulletList, OrderedList, EnumeratedList };
+
+static inline ListType webXmlListType(QStringView t)
+{
+ if (t == QLatin1String("enum"))
+ return EnumeratedList;
+ if (t == QLatin1String("ordered"))
+ return OrderedList;
+ return BulletList;
+}
+
+void QtXmlToSphinx::handleListTag(QXmlStreamReader& reader)
+{
+ // BUG We do not support a list inside a table cell
+ static ListType listType = BulletList;
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ listType = webXmlListType(reader.attributes().value(QLatin1String("type")));
+ if (listType == EnumeratedList) {
+ m_currentTable.appendRow(TableRow{TableCell(QLatin1String("Constant")),
+ TableCell(QLatin1String("Description"))});
+ m_tableHasHeader = true;
+ }
+ INDENT.indent--;
+ } else if (token == QXmlStreamReader::EndElement) {
+ INDENT.indent++;
+ if (!m_currentTable.isEmpty()) {
+ switch (listType) {
+ case BulletList:
+ case OrderedList: {
+ m_output << Qt::endl;
+ const char *separator = listType == BulletList ? "* " : "#. ";
+ const char *indent = listType == BulletList ? " " : " ";
+ for (const TableCell &cell : m_currentTable.constFirst()) {
+ const auto itemLines = QStringView{cell.data}.split(QLatin1Char('\n'));
+ m_output << INDENT << separator << itemLines.constFirst() << Qt::endl;
+ for (int i = 1, max = itemLines.count(); i < max; ++i)
+ m_output << INDENT << indent << itemLines[i] << Qt::endl;
+ }
+ m_output << Qt::endl;
+ }
+ break;
+ case EnumeratedList:
+ m_currentTable.setHeaderEnabled(m_tableHasHeader);
+ m_currentTable.normalize();
+ m_output << ensureEndl << m_currentTable;
+ break;
+ }
+ }
+ m_currentTable.clear();
+ }
+}
+
+void QtXmlToSphinx::handleLinkTag(QXmlStreamReader& reader)
+{
+ switch (reader.tokenType()) {
+ case QXmlStreamReader::StartElement: {
+ // <link> embedded in <see-also> means the characters of <see-also> are no link.
+ m_seeAlsoContext.reset();
+ const QString type = fixLinkType(reader.attributes().value(QLatin1String("type")));
+ const QString ref = reader.attributes().value(linkSourceAttribute(type)).toString();
+ m_linkContext.reset(handleLinkStart(type, ref));
+ }
+ break;
+ case QXmlStreamReader::Characters:
+ Q_ASSERT(!m_linkContext.isNull());
+ handleLinkText(m_linkContext.data(), reader.text().toString());
+ break;
+ case QXmlStreamReader::EndElement:
+ Q_ASSERT(!m_linkContext.isNull());
+ handleLinkEnd(m_linkContext.data());
+ m_linkContext.reset();
+ break;
+ default:
+ break;
+ }
+}
+
+QtXmlToSphinx::LinkContext *QtXmlToSphinx::handleLinkStart(const QString &type, QString ref) const
+{
+ ref.replace(QLatin1String("::"), QLatin1String("."));
+ ref.remove(QLatin1String("()"));
+ auto *result = new LinkContext(ref);
+
+ if (m_insideBold)
+ result->flags |= LinkContext::InsideBold;
+ else if (m_insideItalic)
+ result->flags |= LinkContext::InsideItalic;
+
+ if (type == functionLinkType() && !m_context.isEmpty()) {
+ result->type = LinkContext::Method;
+ const auto rawlinklist = QStringView{result->linkRef}.split(QLatin1Char('.'));
+ if (rawlinklist.size() == 1 || rawlinklist.constFirst() == m_context) {
+ QString context = resolveContextForMethod(rawlinklist.constLast().toString());
+ if (!result->linkRef.startsWith(context))
+ result->linkRef.prepend(context + QLatin1Char('.'));
+ } else {
+ result->linkRef = expandFunction(result->linkRef);
+ }
+ } else if (type == functionLinkType() && m_context.isEmpty()) {
+ result->type = LinkContext::Function;
+ } else if (type == classLinkType()) {
+ result->type = LinkContext::Class;
+ if (const TypeEntry *type = TypeDatabase::instance()->findType(result->linkRef)) {
+ result->linkRef = type->qualifiedTargetLangName();
+ } else { // fall back to the old heuristic if the type wasn't found.
+ const auto rawlinklist = QStringView{result->linkRef}.split(QLatin1Char('.'));
+ QStringList splittedContext = m_context.split(QLatin1Char('.'));
+ if (rawlinklist.size() == 1 || rawlinklist.constFirst() == splittedContext.constLast()) {
+ splittedContext.removeLast();
+ result->linkRef.prepend(QLatin1Char('~') + splittedContext.join(QLatin1Char('.'))
+ + QLatin1Char('.'));
+ }
+ }
+ } else if (type == QLatin1String("enum")) {
+ result->type = LinkContext::Attribute;
+ } else if (type == QLatin1String("page")) {
+ // Module, external web page or reference
+ if (result->linkRef == m_generator->moduleName())
+ result->type = LinkContext::Module;
+ else if (result->linkRef.startsWith(QLatin1String("http")))
+ result->type = LinkContext::External;
+ else
+ result->type = LinkContext::Reference;
+ } else if (type == QLatin1String("external")) {
+ result->type = LinkContext::External;
+ } else {
+ result->type = LinkContext::Reference;
+ }
+ return result;
+}
+
+// <link raw="Model/View Classes" href="model-view-programming.html#model-view-classes"
+// type="page" page="Model/View Programming">Model/View Classes</link>
+// <link type="page" page="http://doc.qt.io/qt-5/class.html">QML types</link>
+// <link raw="Qt Quick" href="qtquick-index.html" type="page" page="Qt Quick">Qt Quick</link>
+// <link raw="QObject" href="qobject.html" type="class">QObject</link>
+// <link raw="Qt::Window" href="qt.html#WindowType-enum" type="enum" enum="Qt::WindowType">Qt::Window</link>
+// <link raw="QNetworkSession::reject()" href="qnetworksession.html#reject" type="function">QNetworkSession::reject()</link>
+
+static QString fixLinkText(const QtXmlToSphinx::LinkContext *linkContext,
+ QString linktext)
+{
+ if (linkContext->type == QtXmlToSphinx::LinkContext::External
+ || linkContext->type == QtXmlToSphinx::LinkContext::Reference) {
+ return linktext;
+ }
+ // For the language reference documentation, strip the module name.
+ // Clear the link text if that matches the function/class/enumeration name.
+ const int lastSep = linktext.lastIndexOf(QLatin1String("::"));
+ if (lastSep != -1)
+ linktext.remove(0, lastSep + 2);
+ else
+ stripPythonQualifiers(&linktext);
+ if (linkContext->linkRef == linktext)
+ return QString();
+ if ((linkContext->type & QtXmlToSphinx::LinkContext::FunctionMask) != 0
+ && (linkContext->linkRef + QLatin1String("()")) == linktext) {
+ return QString();
+ }
+ return linktext;
+}
+
+void QtXmlToSphinx::handleLinkText(LinkContext *linkContext, const QString &linktext) const
+{
+ linkContext->linkText = fixLinkText(linkContext, linktext);
+}
+
+void QtXmlToSphinx::handleLinkEnd(LinkContext *linkContext)
+{
+ m_output << *linkContext;
+}
+
+// Copy images that are placed in a subdirectory "images" under the webxml files
+// by qdoc to a matching subdirectory under the "rst/PySide2/<module>" directory
+static bool copyImage(const QString &href, const QString &docDataDir,
+ const QString &context, const QString &outputDir,
+ QString *errorMessage)
+{
+ const QChar slash = QLatin1Char('/');
+ const int lastSlash = href.lastIndexOf(slash);
+ const QString imagePath = lastSlash != -1 ? href.left(lastSlash) : QString();
+ const QString imageFileName = lastSlash != -1 ? href.right(href.size() - lastSlash - 1) : href;
+ QFileInfo imageSource(docDataDir + slash + href);
+ if (!imageSource.exists()) {
+ QTextStream(errorMessage) << "Image " << href << " does not exist in "
+ << QDir::toNativeSeparators(docDataDir);
+ return false;
+ }
+ // Determine directory from context, "Pyside2.QtGui.QPainter" ->"Pyside2/QtGui".
+ // FIXME: Not perfect yet, should have knowledge about namespaces (DataVis3D) or
+ // nested classes "Pyside2.QtGui.QTouchEvent.QTouchPoint".
+ QString relativeTargetDir = context;
+ const int lastDot = relativeTargetDir.lastIndexOf(QLatin1Char('.'));
+ if (lastDot != -1)
+ relativeTargetDir.truncate(lastDot);
+ relativeTargetDir.replace(QLatin1Char('.'), slash);
+ if (!imagePath.isEmpty())
+ relativeTargetDir += slash + imagePath;
+
+ const QString targetDir = outputDir + slash + relativeTargetDir;
+ const QString targetFileName = targetDir + slash + imageFileName;
+ if (QFileInfo::exists(targetFileName))
+ return true;
+ if (!QFileInfo::exists(targetDir)) {
+ const QDir outDir(outputDir);
+ if (!outDir.mkpath(relativeTargetDir)) {
+ QTextStream(errorMessage) << "Cannot create " << QDir::toNativeSeparators(relativeTargetDir)
+ << " under " << QDir::toNativeSeparators(outputDir);
+ return false;
+ }
+ }
+
+ QFile source(imageSource.absoluteFilePath());
+ if (!source.copy(targetFileName)) {
+ QTextStream(errorMessage) << "Cannot copy " << QDir::toNativeSeparators(source.fileName())
+ << " to " << QDir::toNativeSeparators(targetFileName) << ": "
+ << source.errorString();
+ return false;
+ }
+ qCDebug(lcShibokenDoc()).noquote().nospace() << __FUNCTION__ << " href=\""
+ << href << "\", context=\"" << context << "\", docDataDir=\""
+ << docDataDir << "\", outputDir=\"" << outputDir << "\", copied \""
+ << source.fileName() << "\"->\"" << targetFileName << '"';
+ return true;
+}
+
+bool QtXmlToSphinx::copyImage(const QString &href) const
+{
+ QString errorMessage;
+ const bool result =
+ ::copyImage(href, m_generator->docDataDir(), m_context,
+ m_generator->outputDirectory(), &errorMessage);
+ if (!result)
+ qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage));
+ return result;
+}
+
+void QtXmlToSphinx::handleImageTag(QXmlStreamReader& reader)
+{
+ if (reader.tokenType() != QXmlStreamReader::StartElement)
+ return;
+ const QString href = reader.attributes().value(QLatin1String("href")).toString();
+ if (copyImage(href))
+ m_output << INDENT << ".. image:: " << href << Qt::endl << Qt::endl;
+}
+
+void QtXmlToSphinx::handleInlineImageTag(QXmlStreamReader& reader)
+{
+ if (reader.tokenType() != QXmlStreamReader::StartElement)
+ return;
+ const QString href = reader.attributes().value(QLatin1String("href")).toString();
+ if (!copyImage(href))
+ return;
+ // Handle inline images by substitution references. Insert a unique tag
+ // enclosed by '|' and define it further down. Determine tag from the base
+ //file name with number.
+ QString tag = href;
+ int pos = tag.lastIndexOf(QLatin1Char('/'));
+ if (pos != -1)
+ tag.remove(0, pos + 1);
+ pos = tag.indexOf(QLatin1Char('.'));
+ if (pos != -1)
+ tag.truncate(pos);
+ tag += QString::number(m_inlineImages.size() + 1);
+ m_inlineImages.append(InlineImage{tag, href});
+ m_output << '|' << tag << '|' << ' ';
+}
+
+void QtXmlToSphinx::handleRawTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ QString format = reader.attributes().value(QLatin1String("format")).toString();
+ m_output << INDENT << ".. raw:: " << format.toLower() << Qt::endl << Qt::endl;
+ } else if (token == QXmlStreamReader::Characters) {
+ const auto lst(reader.text().split(QLatin1Char('\n')));
+ for (const auto &row : lst)
+ m_output << INDENT << INDENT << row << Qt::endl;
+ } else if (token == QXmlStreamReader::EndElement) {
+ m_output << Qt::endl << Qt::endl;
+ }
+}
+
+void QtXmlToSphinx::handleCodeTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ m_output << INDENT << "::\n\n";
+ INDENT.indent++;
+ } else if (token == QXmlStreamReader::Characters) {
+ const auto lst(reader.text().split(QLatin1Char('\n')));
+ for (const auto &row : lst)
+ m_output << INDENT << INDENT << row << Qt::endl;
+ } else if (token == QXmlStreamReader::EndElement) {
+ m_output << Qt::endl << Qt::endl;
+ INDENT.indent--;
+ }
+}
+
+void QtXmlToSphinx::handleUnknownTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement)
+ qCDebug(lcShibokenDoc).noquote().nospace() << "Unknown QtDoc tag: \"" << reader.name().toString() << "\".";
+}
+
+void QtXmlToSphinx::handleSuperScriptTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ m_output << " :sup:`";
+ pushOutputBuffer();
+ } else if (token == QXmlStreamReader::Characters) {
+ m_output << reader.text().toString();
+ } else if (token == QXmlStreamReader::EndElement) {
+ m_output << popOutputBuffer();
+ m_output << '`';
+ }
+}
+
+void QtXmlToSphinx::handlePageTag(QXmlStreamReader &reader)
+{
+ if (reader.tokenType() != QXmlStreamReader::StartElement)
+ return;
+
+ const auto title = reader.attributes().value(titleAttribute());
+ if (!title.isEmpty())
+ m_output << rstLabel(title.toString());
+
+ const auto fullTitle = reader.attributes().value(fullTitleAttribute());
+ const int size = fullTitle.isEmpty()
+ ? writeEscapedRstText(m_output, title)
+ : writeEscapedRstText(m_output, fullTitle);
+
+ m_output << Qt::endl << Pad('*', size) << Qt::endl << Qt::endl;
+}
+
+void QtXmlToSphinx::handleTargetTag(QXmlStreamReader &reader)
+{
+ if (reader.tokenType() != QXmlStreamReader::StartElement)
+ return;
+ const auto name = reader.attributes().value(nameAttribute());
+ if (!name.isEmpty())
+ m_output << INDENT << rstLabel(name.toString());
+}
+
+void QtXmlToSphinx::handleIgnoredTag(QXmlStreamReader&)
+{
+}
+
+void QtXmlToSphinx::handleUselessTag(QXmlStreamReader&)
+{
+ // Tag "description" just marks the init of "Detailed description" title.
+ // Tag "definition" just marks enums. We have a different way to process them.
+}
+
+void QtXmlToSphinx::handleAnchorTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::StartElement) {
+ QString anchor;
+ if (reader.attributes().hasAttribute(QLatin1String("id")))
+ anchor = reader.attributes().value(QLatin1String("id")).toString();
+ else if (reader.attributes().hasAttribute(QLatin1String("name")))
+ anchor = reader.attributes().value(QLatin1String("name")).toString();
+ if (!anchor.isEmpty() && m_opened_anchor != anchor) {
+ m_opened_anchor = anchor;
+ if (!m_context.isEmpty())
+ anchor.prepend(m_context + QLatin1Char('_'));
+ m_output << INDENT << rstLabel(anchor);
+ }
+ } else if (token == QXmlStreamReader::EndElement) {
+ m_opened_anchor.clear();
+ }
+}
+
+void QtXmlToSphinx::handleRstPassTroughTag(QXmlStreamReader& reader)
+{
+ if (reader.tokenType() == QXmlStreamReader::Characters)
+ m_output << reader.text();
+}
+
+void QtXmlToSphinx::handleQuoteFileTag(QXmlStreamReader& reader)
+{
+ QXmlStreamReader::TokenType token = reader.tokenType();
+ if (token == QXmlStreamReader::Characters) {
+ QString location = reader.text().toString();
+ location.prepend(m_generator->libSourceDir() + QLatin1Char('/'));
+ QString errorMessage;
+ QString code = readFromLocation(location, QString(), &errorMessage);
+ if (!errorMessage.isEmpty())
+ qCWarning(lcShibokenDoc, "%s", qPrintable(msgTagWarning(reader, m_context, m_lastTagName, errorMessage)));
+ m_output << INDENT << "::\n\n";
+ Indentation indentation(INDENT);
+ if (code.isEmpty())
+ m_output << INDENT << "<Code snippet \"" << location << "\" not found>\n";
+ else
+ formatCode(m_output, code, INDENT);
+ m_output << Qt::endl;
+ }
+}
+
+bool QtXmlToSphinx::convertToRst(QtDocGenerator *generator,
+ const QString &sourceFileName,
+ const QString &targetFileName,
+ const QString &context, QString *errorMessage)
+{
+ QFile sourceFile(sourceFileName);
+ if (!sourceFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ if (errorMessage)
+ *errorMessage = msgCannotOpenForReading(sourceFile);
+ return false;
+ }
+ const QString doc = QString::fromUtf8(sourceFile.readAll());
+ sourceFile.close();
+
+ FileOut targetFile(targetFileName);
+ QtXmlToSphinx x(generator, doc, context);
+ targetFile.stream << x;
+ return targetFile.done(errorMessage) != FileOut::Failure;
+}
+
+void QtXmlToSphinx::Table::normalize()
+{
+ if (m_normalized || isEmpty())
+ return;
+
+ //QDoc3 generates tables with wrong number of columns. We have to
+ //check and if necessary, merge the last columns.
+ int maxCols = -1;
+ for (const auto &row : qAsConst(m_rows)) {
+ if (row.count() > maxCols)
+ maxCols = row.count();
+ }
+ if (maxCols <= 0)
+ return;
+ // add col spans
+ for (int row = 0; row < m_rows.count(); ++row) {
+ for (int col = 0; col < m_rows.at(row).count(); ++col) {
+ QtXmlToSphinx::TableCell& cell = m_rows[row][col];
+ bool mergeCols = (col >= maxCols);
+ if (cell.colSpan > 0) {
+ QtXmlToSphinx::TableCell newCell;
+ newCell.colSpan = -1;
+ for (int i = 0, max = cell.colSpan-1; i < max; ++i) {
+ m_rows[row].insert(col + 1, newCell);
+ }
+ cell.colSpan = 0;
+ col++;
+ } else if (mergeCols) {
+ m_rows[row][maxCols - 1].data += QLatin1Char(' ') + cell.data;
+ }
+ }
+ }
+
+ // row spans
+ const int numCols = m_rows.constFirst().count();
+ for (int col = 0; col < numCols; ++col) {
+ for (int row = 0; row < m_rows.count(); ++row) {
+ if (col < m_rows[row].count()) {
+ QtXmlToSphinx::TableCell& cell = m_rows[row][col];
+ if (cell.rowSpan > 0) {
+ QtXmlToSphinx::TableCell newCell;
+ newCell.rowSpan = -1;
+ int targetRow = row + 1;
+ const int targetEndRow =
+ std::min(targetRow + cell.rowSpan - 1, int(m_rows.count()));
+ cell.rowSpan = 0;
+ for ( ; targetRow < targetEndRow; ++targetRow)
+ m_rows[targetRow].insert(col, newCell);
+ row++;
+ }
+ }
+ }
+ }
+ m_normalized = true;
+}
+
+QTextStream& operator<<(QTextStream& s, const QtXmlToSphinx::Table &table)
+{
+ table.format(s);
+ return s;
+}
+
+void QtXmlToSphinx::Table::format (QTextStream& s) const
+{
+ if (isEmpty())
+ return;
+
+ if (!isNormalized()) {
+ qCDebug(lcShibokenDoc) << "Attempt to print an unnormalized table!";
+ return;
+ }
+
+ // calc width and height of each column and row
+ const int headerColumnCount = m_rows.constFirst().count();
+ QVector<int> colWidths(headerColumnCount);
+ QVector<int> rowHeights(m_rows.count());
+ for (int i = 0, maxI = m_rows.count(); i < maxI; ++i) {
+ const QtXmlToSphinx::TableRow& row = m_rows.at(i);
+ for (int j = 0, maxJ = std::min(row.count(), colWidths.size()); j < maxJ; ++j) {
+ const auto rowLines = QStringView{row[j].data}.split(QLatin1Char('\n')); // cache this would be a good idea
+ for (const auto &str : rowLines)
+ colWidths[j] = std::max(colWidths[j], int(str.size()));
+ rowHeights[i] = std::max(rowHeights[i], int(row[j].data.count(QLatin1Char('\n')) + 1));
+ }
+ }
+
+ if (!*std::max_element(colWidths.begin(), colWidths.end()))
+ return; // empty table (table with empty cells)
+
+ // create a horizontal line to be used later.
+ QString horizontalLine = QLatin1String("+");
+ for (int i = 0, max = colWidths.count(); i < max; ++i) {
+ horizontalLine += QString(colWidths.at(i), QLatin1Char('-'));
+ horizontalLine += QLatin1Char('+');
+ }
+
+ // write table rows
+ for (int i = 0, maxI = m_rows.count(); i < maxI; ++i) { // for each row
+ const QtXmlToSphinx::TableRow& row = m_rows.at(i);
+
+ // print line
+ s << INDENT << '+';
+ for (int col = 0; col < headerColumnCount; ++col) {
+ char c;
+ if (col >= row.length() || row[col].rowSpan == -1)
+ c = ' ';
+ else if (i == 1 && hasHeader())
+ c = '=';
+ else
+ c = '-';
+ s << Pad(c, colWidths.at(col)) << '+';
+ }
+ s << Qt::endl;
+
+
+ // Print the table cells
+ for (int rowLine = 0; rowLine < rowHeights[i]; ++rowLine) { // for each line in a row
+ int j = 0;
+ for (int maxJ = std::min(int(row.count()), headerColumnCount); j < maxJ; ++j) { // for each column
+ const QtXmlToSphinx::TableCell& cell = row[j];
+ const auto rowLines = QStringView{cell.data}.split(QLatin1Char('\n')); // FIXME: Cache this!!!
+ if (!j) // First column, so we need print the identation
+ s << INDENT;
+
+ if (!j || !cell.colSpan)
+ s << '|';
+ else
+ s << ' ';
+ if (rowLine < rowLines.count())
+ s << qSetFieldWidth(colWidths[j]) << Qt::left << rowLines.at(rowLine) << qSetFieldWidth(0);
+ else
+ s << Pad(' ', colWidths.at(j));
+ }
+ for ( ; j < headerColumnCount; ++j) // pad
+ s << '|' << Pad(' ', colWidths.at(j));
+ s << "|\n";
+ }
+ }
+ s << INDENT << horizontalLine << Qt::endl << Qt::endl;
+}
+
+static QString getFuncName(const AbstractMetaFunction* cppFunc) {
+ static bool hashInitialized = false;
+ static QHash<QString, QString> operatorsHash;
+ if (!hashInitialized) {
+ operatorsHash.insert(QLatin1String("operator+"), QLatin1String("__add__"));
+ operatorsHash.insert(QLatin1String("operator+="), QLatin1String("__iadd__"));
+ operatorsHash.insert(QLatin1String("operator-"), QLatin1String("__sub__"));
+ operatorsHash.insert(QLatin1String("operator-="), QLatin1String("__isub__"));
+ operatorsHash.insert(QLatin1String("operator*"), QLatin1String("__mul__"));
+ operatorsHash.insert(QLatin1String("operator*="), QLatin1String("__imul__"));
+ operatorsHash.insert(QLatin1String("operator/"), QLatin1String("__div__"));
+ operatorsHash.insert(QLatin1String("operator/="), QLatin1String("__idiv__"));
+ operatorsHash.insert(QLatin1String("operator%"), QLatin1String("__mod__"));
+ operatorsHash.insert(QLatin1String("operator%="), QLatin1String("__imod__"));
+ operatorsHash.insert(QLatin1String("operator<<"), QLatin1String("__lshift__"));
+ operatorsHash.insert(QLatin1String("operator<<="), QLatin1String("__ilshift__"));
+ operatorsHash.insert(QLatin1String("operator>>"), QLatin1String("__rshift__"));
+ operatorsHash.insert(QLatin1String("operator>>="), QLatin1String("__irshift__"));
+ operatorsHash.insert(QLatin1String("operator&"), QLatin1String("__and__"));
+ operatorsHash.insert(QLatin1String("operator&="), QLatin1String("__iand__"));
+ operatorsHash.insert(QLatin1String("operator|"), QLatin1String("__or__"));
+ operatorsHash.insert(QLatin1String("operator|="), QLatin1String("__ior__"));
+ operatorsHash.insert(QLatin1String("operator^"), QLatin1String("__xor__"));
+ operatorsHash.insert(QLatin1String("operator^="), QLatin1String("__ixor__"));
+ operatorsHash.insert(QLatin1String("operator=="), QLatin1String("__eq__"));
+ operatorsHash.insert(QLatin1String("operator!="), QLatin1String("__ne__"));
+ operatorsHash.insert(QLatin1String("operator<"), QLatin1String("__lt__"));
+ operatorsHash.insert(QLatin1String("operator<="), QLatin1String("__le__"));
+ operatorsHash.insert(QLatin1String("operator>"), QLatin1String("__gt__"));
+ operatorsHash.insert(QLatin1String("operator>="), QLatin1String("__ge__"));
+ hashInitialized = true;
+ }
+
+ QHash<QString, QString>::const_iterator it = operatorsHash.constFind(cppFunc->name());
+ QString result = it != operatorsHash.cend() ? it.value() : cppFunc->name();
+ result.replace(QLatin1String("::"), QLatin1String("."));
+ return result;
+}
+
+QtDocGenerator::QtDocGenerator() : m_docParser(nullptr)
+{
+}
+
+QtDocGenerator::~QtDocGenerator()
+{
+ delete m_docParser;
+}
+
+QString QtDocGenerator::fileNameSuffix() const
+{
+ return QLatin1String(".rst");
+}
+
+bool QtDocGenerator::shouldGenerate(const AbstractMetaClass *cls) const
+{
+ return Generator::shouldGenerate(cls)
+ && cls->typeEntry()->type() != TypeEntry::SmartPointerType;
+}
+
+QString QtDocGenerator::fileNameForContext(const GeneratorContext &context) const
+{
+ const AbstractMetaClass *metaClass = context.metaClass();
+ if (!context.forSmartPointer()) {
+ return metaClass->name() + fileNameSuffix();
+ }
+ const AbstractMetaType &smartPointerType = context.preciseType();
+ QString fileNameBase = getFileNameBaseForSmartPointer(smartPointerType, metaClass);
+ return fileNameBase + fileNameSuffix();
+}
+
+void QtDocGenerator::writeFormattedText(QTextStream &s, const Documentation &doc,
+ const AbstractMetaClass *metaClass,
+ Documentation::Type docType)
+{
+ QString metaClassName;
+
+ if (metaClass)
+ metaClassName = metaClass->fullName();
+
+ if (doc.format() == Documentation::Native) {
+ QtXmlToSphinx x(this,doc.value(docType), metaClassName);
+ s << x;
+ } else {
+ const QString &value = doc.value(docType);
+ const auto lines = QStringView{value}.split(QLatin1Char('\n'));
+ int typesystemIndentation = std::numeric_limits<int>::max();
+ // check how many spaces must be removed from the beginning of each line
+ for (const auto &line : lines) {
+ const auto it = std::find_if(line.cbegin(), line.cend(),
+ [] (QChar c) { return !c.isSpace(); });
+ if (it != line.cend())
+ typesystemIndentation = qMin(typesystemIndentation, int(it - line.cbegin()));
+ }
+ if (typesystemIndentation == std::numeric_limits<int>::max())
+ typesystemIndentation = 0;
+ for (const auto &line : lines) {
+ s << INDENT
+ << (typesystemIndentation > 0 && typesystemIndentation < line.size()
+ ? line.right(line.size() - typesystemIndentation) : line)
+ << Qt::endl;
+ }
+ }
+
+ s << Qt::endl;
+}
+
+static void writeInheritedByList(QTextStream& s, const AbstractMetaClass* metaClass, const AbstractMetaClassList& allClasses)
+{
+ AbstractMetaClassList res;
+ for (AbstractMetaClass *c : allClasses) {
+ if (c != metaClass && c->inheritsFrom(metaClass))
+ res << c;
+ }
+
+ if (res.isEmpty())
+ return;
+
+ s << "**Inherited by:** ";
+ QStringList classes;
+ for (AbstractMetaClass *c : qAsConst(res))
+ classes << QLatin1String(":ref:`") + c->name() + QLatin1Char('`');
+ s << classes.join(QLatin1String(", ")) << Qt::endl << Qt::endl;
+}
+
+// Extract the <brief> section from a WebXML (class) documentation and remove it
+// from the source.
+static bool extractBrief(Documentation *sourceDoc, Documentation *brief)
+{
+ if (sourceDoc->format() != Documentation::Native)
+ return false;
+ QString value = sourceDoc->value();
+ const int briefStart = value.indexOf(briefStartElement());
+ if (briefStart < 0)
+ return false;
+ const int briefEnd = value.indexOf(briefEndElement(), briefStart + briefStartElement().size());
+ if (briefEnd < briefStart)
+ return false;
+ const int briefLength = briefEnd + briefEndElement().size() - briefStart;
+ brief->setFormat(Documentation::Native);
+ QString briefValue = value.mid(briefStart, briefLength);
+ briefValue.insert(briefValue.size() - briefEndElement().size(),
+ QLatin1String("<rst> More_...</rst>"));
+ brief->setValue(briefValue);
+ value.remove(briefStart, briefLength);
+ sourceDoc->setValue(value);
+ return true;
+}
+
+void QtDocGenerator::generateClass(QTextStream &s, const GeneratorContext &classContext)
+{
+ const AbstractMetaClass *metaClass = classContext.metaClass();
+ qCDebug(lcShibokenDoc).noquote().nospace() << "Generating Documentation for " << metaClass->fullName();
+
+ m_packages[metaClass->package()] << fileNameForContext(classContext);
+
+ m_docParser->setPackageName(metaClass->package());
+ m_docParser->fillDocumentation(const_cast<AbstractMetaClass*>(metaClass));
+
+ QString className = metaClass->name();
+ s << ".. _" << className << ":" << "\n\n";
+ s << ".. currentmodule:: " << metaClass->package() << "\n\n\n";
+
+ s << className << Qt::endl;
+ s << Pad('*', className.count()) << Qt::endl << Qt::endl;
+
+ auto documentation = metaClass->documentation();
+ Documentation brief;
+ if (extractBrief(&documentation, &brief))
+ writeFormattedText(s, brief.value(), metaClass);
+
+ s << ".. inheritance-diagram:: " << metaClass->fullName() << Qt::endl
+ << " :parts: 2" << Qt::endl << Qt::endl;
+ // TODO: This would be a parameter in the future...
+
+
+ writeInheritedByList(s, metaClass, classes());
+
+ const auto version = versionOf(metaClass->typeEntry());
+ if (!version.isNull())
+ s << rstVersionAdded(version);
+ if (metaClass->attributes().testFlag(AbstractMetaAttributes::Deprecated))
+ s << rstDeprecationNote("class");
+
+ writeFunctionList(s, metaClass);
+
+ //Function list
+ AbstractMetaFunctionList functionList = metaClass->functions();
+ std::sort(functionList.begin(), functionList.end(), functionSort);
+
+ s << "\nDetailed Description\n"
+ "--------------------\n\n"
+ << ".. _More:\n";
+
+ writeInjectDocumentation(s, TypeSystem::DocModificationPrepend, metaClass, nullptr);
+ if (!writeInjectDocumentation(s, TypeSystem::DocModificationReplace, metaClass, nullptr))
+ writeFormattedText(s, documentation.value(), metaClass);
+
+ if (!metaClass->isNamespace())
+ writeConstructors(s, metaClass);
+ writeEnums(s, metaClass);
+ if (!metaClass->isNamespace())
+ writeFields(s, metaClass);
+
+
+ QStringList uniqueFunctions;
+ for (AbstractMetaFunction *func : qAsConst(functionList)) {
+ if (shouldSkip(func))
+ continue;
+
+ if (func->isStatic())
+ s << ".. staticmethod:: ";
+ else
+ s << ".. method:: ";
+
+ writeFunction(s, metaClass, func, !uniqueFunctions.contains(func->name()));
+ uniqueFunctions.append(func->name());
+ }
+
+ writeInjectDocumentation(s, TypeSystem::DocModificationAppend, metaClass, nullptr);
+}
+
+void QtDocGenerator::writeFunctionList(QTextStream& s, const AbstractMetaClass* cppClass)
+{
+ QStringList functionList;
+ QStringList virtualList;
+ QStringList signalList;
+ QStringList slotList;
+ QStringList staticFunctionList;
+
+ const AbstractMetaFunctionList &classFunctions = cppClass->functions();
+ for (AbstractMetaFunction *func : classFunctions) {
+ if (shouldSkip(func))
+ continue;
+
+ QString className;
+ if (!func->isConstructor())
+ className = cppClass->fullName() + QLatin1Char('.');
+ else if (func->implementingClass() && func->implementingClass()->enclosingClass())
+ className = func->implementingClass()->enclosingClass()->fullName() + QLatin1Char('.');
+ QString funcName = getFuncName(func);
+
+ QString str = QLatin1String("def :meth:`");
+
+ str += funcName;
+ str += QLatin1Char('<');
+ if (!funcName.startsWith(className))
+ str += className;
+ str += funcName;
+ str += QLatin1String(">` (");
+ str += parseArgDocStyle(cppClass, func);
+ str += QLatin1Char(')');
+
+ if (func->isStatic())
+ staticFunctionList << str;
+ else if (func->isVirtual())
+ virtualList << str;
+ else if (func->isSignal())
+ signalList << str;
+ else if (func->isSlot())
+ slotList << str;
+ else
+ functionList << str;
+ }
+
+ if (!functionList.isEmpty() || !staticFunctionList.isEmpty()) {
+ QtXmlToSphinx::Table functionTable;
+
+ s << "\nSynopsis\n--------\n\n";
+
+ writeFunctionBlock(s, QLatin1String("Functions"), functionList);
+ writeFunctionBlock(s, QLatin1String("Virtual functions"), virtualList);
+ writeFunctionBlock(s, QLatin1String("Slots"), slotList);
+ writeFunctionBlock(s, QLatin1String("Signals"), signalList);
+ writeFunctionBlock(s, QLatin1String("Static functions"), staticFunctionList);
+ }
+}
+
+void QtDocGenerator::writeFunctionBlock(QTextStream& s, const QString& title, QStringList& functions)
+{
+ if (!functions.isEmpty()) {
+ s << title << Qt::endl
+ << QString(title.size(), QLatin1Char('^')) << Qt::endl;
+
+ std::sort(functions.begin(), functions.end());
+
+ s << ".. container:: function_list\n\n";
+ Indentation indentation(INDENT);
+ for (const QString &func : qAsConst(functions))
+ s << INDENT << '*' << ' ' << func << Qt::endl;
+
+ s << Qt::endl << Qt::endl;
+ }
+}
+
+void QtDocGenerator::writeEnums(QTextStream& s, const AbstractMetaClass* cppClass)
+{
+ static const QString section_title = QLatin1String(".. attribute:: ");
+
+ for (AbstractMetaEnum *en : cppClass->enums()) {
+ s << section_title << cppClass->fullName() << '.' << en->name() << Qt::endl << Qt::endl;
+ writeFormattedText(s, en->documentation().value(), cppClass);
+ const auto version = versionOf(en->typeEntry());
+ if (!version.isNull())
+ s << rstVersionAdded(version);
+ }
+
+}
+
+void QtDocGenerator::writeFields(QTextStream& s, const AbstractMetaClass* cppClass)
+{
+ static const QString section_title = QLatin1String(".. attribute:: ");
+
+ const AbstractMetaFieldList &fields = cppClass->fields();
+ for (AbstractMetaField *field : fields) {
+ s << section_title << cppClass->fullName() << "." << field->name() << Qt::endl << Qt::endl;
+ //TODO: request for member ‘documentation’ is ambiguous
+ writeFormattedText(s, field->AbstractMetaAttributes::documentation().value(), cppClass);
+ }
+}
+
+void QtDocGenerator::writeConstructors(QTextStream& s, const AbstractMetaClass* cppClass)
+{
+ static const QString sectionTitle = QLatin1String(".. class:: ");
+
+ AbstractMetaFunctionList lst = cppClass->queryFunctions(AbstractMetaClass::Constructors | AbstractMetaClass::Visible);
+ for (int i = lst.size() - 1; i >= 0; --i) {
+ if (lst.at(i)->isModifiedRemoved() || lst.at(i)->functionType() == AbstractMetaFunction::MoveConstructorFunction)
+ lst.removeAt(i);
+ }
+
+ bool first = true;
+ QHash<QString, AbstractMetaArgument> arg_map;
+
+ IndentorBase<1> indent1;
+ indent1.indent = INDENT.total();
+ if (lst.isEmpty()) {
+ s << sectionTitle << cppClass->fullName();
+ } else {
+ for (AbstractMetaFunction *func : qAsConst(lst)) {
+ s << indent1;
+ if (first) {
+ first = false;
+ s << sectionTitle;
+ indent1.indent += sectionTitle.size();
+ }
+ s << functionSignature(cppClass, func) << "\n\n";
+
+ const auto version = versionOf(func->typeEntry());
+ if (!version.isNull())
+ s << indent1 << rstVersionAdded(version);
+ if (func->attributes().testFlag(AbstractMetaAttributes::Deprecated))
+ s << indent1 << rstDeprecationNote("constructor");
+
+ const AbstractMetaArgumentList &arguments = func->arguments();
+ for (const AbstractMetaArgument &arg : arguments) {
+ if (!arg_map.contains(arg.name())) {
+ arg_map.insert(arg.name(), arg);
+ }
+ }
+ }
+ }
+
+ s << Qt::endl;
+
+ for (auto it = arg_map.cbegin(), end = arg_map.cend(); it != end; ++it) {
+ Indentation indentation(INDENT, 2);
+ writeParameterType(s, cppClass, it.value());
+ }
+
+ s << Qt::endl;
+
+ for (AbstractMetaFunction *func : qAsConst(lst))
+ writeFormattedText(s, func->documentation().value(), cppClass);
+}
+
+QString QtDocGenerator::parseArgDocStyle(const AbstractMetaClass* /* cppClass */,
+ const AbstractMetaFunction* func)
+{
+ QString ret;
+ int optArgs = 0;
+
+ const AbstractMetaArgumentList &arguments = func->arguments();
+ for (const AbstractMetaArgument &arg : arguments) {
+
+ if (func->argumentRemoved(arg.argumentIndex() + 1))
+ continue;
+
+ bool thisIsoptional = !arg.defaultValueExpression().isEmpty();
+ if (optArgs || thisIsoptional) {
+ ret += QLatin1Char('[');
+ optArgs++;
+ }
+
+ if (arg.argumentIndex() > 0)
+ ret += QLatin1String(", ");
+
+ ret += arg.name();
+
+ if (thisIsoptional) {
+ QString defValue = arg.defaultValueExpression();
+ if (defValue == QLatin1String("QString()")) {
+ defValue = QLatin1String("\"\"");
+ } else if (defValue == QLatin1String("QStringList()")
+ || defValue.startsWith(QLatin1String("QVector"))
+ || defValue.startsWith(QLatin1String("QList"))) {
+ defValue = QLatin1String("list()");
+ } else if (defValue == QLatin1String("QVariant()")) {
+ defValue = none();
+ } else {
+ defValue.replace(QLatin1String("::"), QLatin1String("."));
+ if (defValue == QLatin1String("nullptr"))
+ defValue = none();
+ else if (defValue == QLatin1String("0") && arg.type().isObject())
+ defValue = none();
+ }
+ ret += QLatin1Char('=') + defValue;
+ }
+ }
+
+ ret += QString(optArgs, QLatin1Char(']'));
+ return ret;
+}
+
+void QtDocGenerator::writeDocSnips(QTextStream &s,
+ const CodeSnipList &codeSnips,
+ TypeSystem::CodeSnipPosition position,
+ TypeSystem::Language language)
+{
+ Indentation indentation(INDENT);
+ QStringList invalidStrings;
+ const static QString startMarkup = QLatin1String("[sphinx-begin]");
+ const static QString endMarkup = QLatin1String("[sphinx-end]");
+
+ invalidStrings << QLatin1String("*") << QLatin1String("//") << QLatin1String("/*") << QLatin1String("*/");
+
+ for (const CodeSnip &snip : codeSnips) {
+ if ((snip.position != position) ||
+ !(snip.language & language))
+ continue;
+
+ QString code = snip.code();
+ while (code.contains(startMarkup) && code.contains(endMarkup)) {
+ int startBlock = code.indexOf(startMarkup) + startMarkup.size();
+ int endBlock = code.indexOf(endMarkup);
+
+ if ((startBlock == -1) || (endBlock == -1))
+ break;
+
+ QString codeBlock = code.mid(startBlock, endBlock - startBlock);
+ const QStringList rows = codeBlock.split(QLatin1Char('\n'));
+ int currentRow = 0;
+ int offset = 0;
+
+ for (QString row : rows) {
+ for (const QString &invalidString : qAsConst(invalidStrings))
+ row.remove(invalidString);
+
+ if (row.trimmed().size() == 0) {
+ if (currentRow == 0)
+ continue;
+ s << Qt::endl;
+ }
+
+ if (currentRow == 0) {
+ //find offset
+ for (auto c : row) {
+ if (c == QLatin1Char(' '))
+ offset++;
+ else if (c == QLatin1Char('\n'))
+ offset = 0;
+ else
+ break;
+ }
+ }
+ s << QStringView{row}.mid(offset) << Qt::endl;
+ currentRow++;
+ }
+
+ code = code.mid(endBlock+endMarkup.size());
+ }
+ }
+}
+
+bool QtDocGenerator::writeInjectDocumentation(QTextStream& s,
+ TypeSystem::DocModificationMode mode,
+ const AbstractMetaClass* cppClass,
+ const AbstractMetaFunction* func)
+{
+ Indentation indentation(INDENT);
+ bool didSomething = false;
+
+ const DocModificationList &mods = cppClass->typeEntry()->docModifications();
+ for (const DocModification &mod : mods) {
+ if (mod.mode() == mode) {
+ bool modOk = func ? mod.signature() == func->minimalSignature() : mod.signature().isEmpty();
+
+ if (modOk) {
+ Documentation doc;
+ Documentation::Format fmt;
+
+ if (mod.format() == TypeSystem::NativeCode)
+ fmt = Documentation::Native;
+ else if (mod.format() == TypeSystem::TargetLangCode)
+ fmt = Documentation::Target;
+ else
+ continue;
+
+ doc.setValue(mod.code(), Documentation::Detailed, fmt);
+ writeFormattedText(s, doc.value(), cppClass);
+ didSomething = true;
+ }
+ }
+ }
+
+ s << Qt::endl;
+
+ // TODO: Deprecate the use of doc string on glue code.
+ // This is pre "add-function" and "inject-documentation" tags.
+ const TypeSystem::CodeSnipPosition pos = mode == TypeSystem::DocModificationPrepend
+ ? TypeSystem::CodeSnipPositionBeginning : TypeSystem::CodeSnipPositionEnd;
+ if (func)
+ writeDocSnips(s, func->injectedCodeSnips(), pos, TypeSystem::TargetLangCode);
+ else
+ writeDocSnips(s, cppClass->typeEntry()->codeSnips(), pos, TypeSystem::TargetLangCode);
+ return didSomething;
+}
+
+QString QtDocGenerator::functionSignature(const AbstractMetaClass* cppClass, const AbstractMetaFunction* func)
+{
+ QString funcName;
+
+ funcName = cppClass->fullName();
+ if (!func->isConstructor())
+ funcName += QLatin1Char('.') + getFuncName(func);
+
+ return funcName + QLatin1Char('(') + parseArgDocStyle(cppClass, func)
+ + QLatin1Char(')');
+}
+
+QString QtDocGenerator::translateToPythonType(const AbstractMetaType &type,
+ const AbstractMetaClass* cppClass)
+{
+ static const QStringList nativeTypes = {boolT(), floatT(), intT(),
+ QLatin1String("object"),
+ QLatin1String("str")
+ };
+ const QString name = type.name();
+ if (nativeTypes.contains(name))
+ return name;
+
+ static const QMap<QString, QString> typeMap = {
+ { QLatin1String("PyObject"), QLatin1String("object") },
+ { QLatin1String("QString"), QLatin1String("str") },
+ { QLatin1String("uchar"), QLatin1String("str") },
+ { QLatin1String("QStringList"), QLatin1String("list of strings") },
+ { qVariantT(), QLatin1String("object") },
+ { QLatin1String("quint32"), intT() },
+ { QLatin1String("uint32_t"), intT() },
+ { QLatin1String("quint64"), intT() },
+ { QLatin1String("qint64"), intT() },
+ { QLatin1String("size_t"), intT() },
+ { QLatin1String("int64_t"), intT() },
+ { QLatin1String("qreal"), floatT() }
+ };
+ const auto found = typeMap.find(name);
+ if (found != typeMap.end())
+ return found.value();
+
+ QString strType;
+ if (type.isConstant() && name == QLatin1String("char") && type.indirections() == 1) {
+ strType = QLatin1String("str");
+ } else if (name.startsWith(unsignedShortT())) {
+ strType = intT();
+ } else if (name.startsWith(unsignedT())) { // uint and ulong
+ strType = intT();
+ } else if (type.isContainer()) {
+ QString strType = translateType(type, cppClass, Options(ExcludeConst) | ExcludeReference);
+ strType.remove(QLatin1Char('*'));
+ strType.remove(QLatin1Char('>'));
+ strType.remove(QLatin1Char('<'));
+ strType.replace(QLatin1String("::"), QLatin1String("."));
+ if (strType.contains(QLatin1String("QList")) || strType.contains(QLatin1String("QVector"))) {
+ strType.replace(QLatin1String("QList"), QLatin1String("list of "));
+ strType.replace(QLatin1String("QVector"), QLatin1String("list of "));
+ } else if (strType.contains(QLatin1String("QHash")) || strType.contains(QLatin1String("QMap"))) {
+ strType.remove(QLatin1String("QHash"));
+ strType.remove(QLatin1String("QMap"));
+ QStringList types = strType.split(QLatin1Char(','));
+ strType = QString::fromLatin1("Dictionary with keys of type %1 and values of type %2.")
+ .arg(types[0], types[1]);
+ }
+ } else {
+ const AbstractMetaClass *k = AbstractMetaClass::findClass(classes(), type.typeEntry());
+ strType = k ? k->fullName() : type.name();
+ strType = QStringLiteral(":any:`") + strType + QLatin1Char('`');
+ }
+ return strType;
+}
+
+void QtDocGenerator::writeParameterType(QTextStream& s, const AbstractMetaClass* cppClass,
+ const AbstractMetaArgument &arg)
+{
+ s << INDENT << ":param " << arg.name() << ": "
+ << translateToPythonType(arg.type(), cppClass) << Qt::endl;
+}
+
+void QtDocGenerator::writeFunctionParametersType(QTextStream &s, const AbstractMetaClass *cppClass,
+ const AbstractMetaFunction *func)
+{
+ s << Qt::endl;
+ const AbstractMetaArgumentList &funcArgs = func->arguments();
+ for (const AbstractMetaArgument &arg : funcArgs) {
+
+ if (func->argumentRemoved(arg.argumentIndex() + 1))
+ continue;
+
+ writeParameterType(s, cppClass, arg);
+ }
+
+ if (!func->isConstructor() && !func->isVoid()) {
+
+ QString retType;
+ // check if the return type was modified
+ const FunctionModificationList &mods = func->modifications();
+ for (const FunctionModification &mod : mods) {
+ for (const ArgumentModification &argMod : mod.argument_mods) {
+ if (argMod.index == 0) {
+ retType = argMod.modified_type;
+ break;
+ }
+ }
+ }
+
+ if (retType.isEmpty())
+ retType = translateToPythonType(func->type(), cppClass);
+ s << INDENT << ":rtype: " << retType << Qt::endl;
+ }
+ s << Qt::endl;
+}
+
+void QtDocGenerator::writeFunction(QTextStream& s, const AbstractMetaClass* cppClass,
+ const AbstractMetaFunction* func, bool indexed)
+{
+ s << functionSignature(cppClass, func);
+
+ {
+ Indentation indentation(INDENT);
+ if (!indexed)
+ s << QLatin1Char('\n') << INDENT << QLatin1String(":noindex:");
+ s << "\n\n";
+ writeFunctionParametersType(s, cppClass, func);
+ const auto version = versionOf(func->typeEntry());
+ if (!version.isNull())
+ s << INDENT << rstVersionAdded(version);
+ if (func->attributes().testFlag(AbstractMetaAttributes::Deprecated))
+ s << INDENT << rstDeprecationNote("function");
+ }
+ writeInjectDocumentation(s, TypeSystem::DocModificationPrepend, cppClass, func);
+ if (!writeInjectDocumentation(s, TypeSystem::DocModificationReplace, cppClass, func)) {
+ writeFormattedText(s, func->documentation(), cppClass, Documentation::Brief);
+ writeFormattedText(s, func->documentation(), cppClass, Documentation::Detailed);
+ }
+ writeInjectDocumentation(s, TypeSystem::DocModificationAppend, cppClass, func);
+}
+
+static void writeFancyToc(QTextStream& s, const QStringList& items, int cols = 2)
+{
+ using TocMap = QMap<QChar, QStringList>;
+ TocMap tocMap;
+ QChar Q = QLatin1Char('Q');
+ QChar idx;
+ for (QString item : items) {
+ if (item.isEmpty())
+ continue;
+ item.chop(4); // Remove the .rst extension
+ // skip namespace if necessary
+ const QString className = item.split(QLatin1Char('.')).last();
+ if (className.startsWith(Q) && className.length() > 1)
+ idx = className[1];
+ else
+ idx = className[0];
+ tocMap[idx] << item;
+ }
+ QtXmlToSphinx::Table table;
+ QtXmlToSphinx::TableRow row;
+
+ int itemsPerCol = (items.size() + tocMap.size()*2) / cols;
+ QString currentColData;
+ int i = 0;
+ QTextStream ss(&currentColData);
+ QMutableMapIterator<QChar, QStringList> it(tocMap);
+ while (it.hasNext()) {
+ it.next();
+ std::sort(it.value().begin(), it.value().end());
+
+ if (i)
+ ss << Qt::endl;
+
+ ss << "**" << it.key() << "**\n\n";
+ i += 2; // a letter title is equivalent to two entries in space
+ for (const QString &item : qAsConst(it.value())) {
+ ss << "* :doc:`" << item << "`\n";
+ ++i;
+
+ // end of column detected!
+ if (i > itemsPerCol) {
+ ss.flush();
+ QtXmlToSphinx::TableCell cell(currentColData);
+ row << cell;
+ currentColData.clear();
+ i = 0;
+ }
+ }
+ }
+ if (i) {
+ ss.flush();
+ QtXmlToSphinx::TableCell cell(currentColData);
+ row << cell;
+ currentColData.clear();
+ i = 0;
+ }
+ table.appendRow(row);
+ table.normalize();
+ s << ".. container:: pysidetoc\n\n";
+ s << table;
+}
+
+bool QtDocGenerator::finishGeneration()
+{
+ if (!classes().isEmpty())
+ writeModuleDocumentation();
+ if (!m_additionalDocumentationList.isEmpty())
+ writeAdditionalDocumentation();
+ return true;
+}
+
+void QtDocGenerator::writeModuleDocumentation()
+{
+ QMap<QString, QStringList>::iterator it = m_packages.begin();
+ for (; it != m_packages.end(); ++it) {
+ QString key = it.key();
+ key.replace(QLatin1Char('.'), QLatin1Char('/'));
+ QString outputDir = outputDirectory() + QLatin1Char('/') + key;
+ FileOut output(outputDir + QLatin1String("/index.rst"));
+ QTextStream& s = output.stream;
+
+ s << ".. module:: " << it.key() << Qt::endl << Qt::endl;
+
+ const QString &title = it.key();
+ s << title << Qt::endl;
+ s << Pad('*', title.length()) << Qt::endl << Qt::endl;
+
+ /* Avoid showing "Detailed Description for *every* class in toc tree */
+ Indentation indentation(INDENT);
+ // Store the it.key() in a QString so that it can be stripped off unwanted
+ // information when neeeded. For example, the RST files in the extras directory
+ // doesn't include the PySide# prefix in their names.
+ const QString moduleName = it.key();
+ const int lastIndex = moduleName.lastIndexOf(QLatin1Char('.'));
+
+ // Search for extra-sections
+ if (!m_extraSectionDir.isEmpty()) {
+ QDir extraSectionDir(m_extraSectionDir);
+ if (!extraSectionDir.exists())
+ qCWarning(lcShibokenDoc) << m_extraSectionDir << "doesn't exist";
+
+ QStringList fileList = extraSectionDir.entryList(QStringList() << (moduleName.mid(lastIndex + 1) + QLatin1String("?*.rst")), QDir::Files);
+ QStringList::iterator it2 = fileList.begin();
+ for (; it2 != fileList.end(); ++it2) {
+ QString origFileName(*it2);
+ it2->remove(0, moduleName.indexOf(QLatin1Char('.')));
+ QString newFilePath = outputDir + QLatin1Char('/') + *it2;
+ if (QFile::exists(newFilePath))
+ QFile::remove(newFilePath);
+ if (!QFile::copy(m_extraSectionDir + QLatin1Char('/') + origFileName, newFilePath)) {
+ qCDebug(lcShibokenDoc).noquote().nospace() << "Error copying extra doc "
+ << QDir::toNativeSeparators(m_extraSectionDir + QLatin1Char('/') + origFileName)
+ << " to " << QDir::toNativeSeparators(newFilePath);
+ }
+ }
+ it.value().append(fileList);
+ }
+
+ writeFancyToc(s, it.value());
+
+ s << INDENT << ".. container:: hide\n\n" << indent(INDENT)
+ << INDENT << ".. toctree::\n" << indent(INDENT)
+ << INDENT << ":maxdepth: 1\n\n";
+ for (const QString &className : qAsConst(it.value()))
+ s << INDENT << className << Qt::endl;
+ s << "\n\n" << outdent(INDENT) << outdent(INDENT)
+ << "Detailed Description\n--------------------\n\n";
+
+ // module doc is always wrong and C++istic, so go straight to the extra directory!
+ QFile moduleDoc(m_extraSectionDir + QLatin1Char('/') + moduleName.mid(lastIndex + 1) + QLatin1String(".rst"));
+ if (moduleDoc.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ s << moduleDoc.readAll();
+ moduleDoc.close();
+ } else {
+ // try the normal way
+ Documentation moduleDoc = m_docParser->retrieveModuleDocumentation(it.key());
+ if (moduleDoc.format() == Documentation::Native) {
+ QString context = it.key();
+ stripPythonQualifiers(&context);
+ QtXmlToSphinx x(this, moduleDoc.value(), context);
+ s << x;
+ } else {
+ s << moduleDoc.value();
+ }
+ }
+ }
+}
+
+static inline QString msgNonExistentAdditionalDocFile(const QString &dir,
+ const QString &fileName)
+{
+ const QString result = QLatin1Char('"') + fileName
+ + QLatin1String("\" does not exist in ")
+ + QDir::toNativeSeparators(dir) + QLatin1Char('.');
+ return result;
+}
+
+void QtDocGenerator::writeAdditionalDocumentation()
+{
+ QFile additionalDocumentationFile(m_additionalDocumentationList);
+ if (!additionalDocumentationFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ qCWarning(lcShibokenDoc, "%s",
+ qPrintable(msgCannotOpenForReading(additionalDocumentationFile)));
+ return;
+ }
+
+ QDir outDir(outputDirectory());
+ const QString rstSuffix = fileNameSuffix();
+
+ QString errorMessage;
+ int successCount = 0;
+ int count = 0;
+
+ QString targetDir = outDir.absolutePath();
+
+ while (!additionalDocumentationFile.atEnd()) {
+ const QByteArray lineBA = additionalDocumentationFile.readLine().trimmed();
+ if (lineBA.isEmpty() || lineBA.startsWith('#'))
+ continue;
+ const QString line = QFile::decodeName(lineBA);
+ // Parse "[directory]" specification
+ if (line.size() > 2 && line.startsWith(QLatin1Char('[')) && line.endsWith(QLatin1Char(']'))) {
+ const QString dir = line.mid(1, line.size() - 2);
+ if (dir.isEmpty() || dir == QLatin1String(".")) {
+ targetDir = outDir.absolutePath();
+ } else {
+ if (!outDir.exists(dir) && !outDir.mkdir(dir)) {
+ qCWarning(lcShibokenDoc, "Cannot create directory %s under %s",
+ qPrintable(dir),
+ qPrintable(QDir::toNativeSeparators(outputDirectory())));
+ break;
+ }
+ targetDir = outDir.absoluteFilePath(dir);
+ }
+ } else {
+ // Normal file entry
+ QFileInfo fi(m_docDataDir + QLatin1Char('/') + line);
+ if (fi.isFile()) {
+ const QString rstFileName = fi.baseName() + rstSuffix;
+ const QString rstFile = targetDir + QLatin1Char('/') + rstFileName;
+ const QString context = targetDir.mid(targetDir.lastIndexOf(QLatin1Char('/')) + 1);
+ if (QtXmlToSphinx::convertToRst(this, fi.absoluteFilePath(),
+ rstFile, context, &errorMessage)) {
+ ++successCount;
+ qCDebug(lcShibokenDoc).nospace().noquote() << __FUNCTION__
+ << " converted " << fi.fileName()
+ << ' ' << rstFileName;
+ } else {
+ qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage));
+ }
+ } else {
+ qCWarning(lcShibokenDoc, "%s",
+ qPrintable(msgNonExistentAdditionalDocFile(m_docDataDir, line)));
+ }
+ ++count;
+ }
+ }
+ additionalDocumentationFile.close();
+
+ qCInfo(lcShibokenDoc, "Created %d/%d additional documentation files.",
+ successCount, count);
+}
+
+#ifdef __WIN32__
+# define PATH_SEP ';'
+#else
+# define PATH_SEP ':'
+#endif
+
+bool QtDocGenerator::doSetup()
+{
+ if (m_codeSnippetDirs.isEmpty())
+ m_codeSnippetDirs = m_libSourceDir.split(QLatin1Char(PATH_SEP));
+
+ if (!m_docParser)
+ m_docParser = new QtDocParser;
+
+ if (m_libSourceDir.isEmpty() || m_docDataDir.isEmpty()) {
+ qCWarning(lcShibokenDoc) << "Documentation data dir and/or Qt source dir not informed, "
+ "documentation will not be extracted from Qt sources.";
+ return false;
+ }
+
+ m_docParser->setDocumentationDataDirectory(m_docDataDir);
+ m_docParser->setLibrarySourceDirectory(m_libSourceDir);
+ return true;
+}
+
+
+Generator::OptionDescriptions QtDocGenerator::options() const
+{
+ return OptionDescriptions()
+ << qMakePair(QLatin1String("doc-parser=<parser>"),
+ QLatin1String("The documentation parser used to interpret the documentation\n"
+ "input files (qdoc|doxygen)"))
+ << qMakePair(QLatin1String("documentation-code-snippets-dir=<dir>"),
+ QLatin1String("Directory used to search code snippets used by the documentation"))
+ << qMakePair(QLatin1String("documentation-data-dir=<dir>"),
+ QLatin1String("Directory with XML files generated by documentation tool"))
+ << qMakePair(QLatin1String("documentation-extra-sections-dir=<dir>"),
+ QLatin1String("Directory used to search for extra documentation sections"))
+ << qMakePair(QLatin1String("library-source-dir=<dir>"),
+ QLatin1String("Directory where library source code is located"))
+ << qMakePair(additionalDocumentationOption() + QLatin1String("=<file>"),
+ QLatin1String("List of additional XML files to be converted to .rst files\n"
+ "(for example, tutorials)."));
+}
+
+bool QtDocGenerator::handleOption(const QString &key, const QString &value)
+{
+ if (key == QLatin1String("library-source-dir")) {
+ m_libSourceDir = value;
+ return true;
+ }
+ if (key == QLatin1String("documentation-data-dir")) {
+ m_docDataDir = value;
+ return true;
+ }
+ if (key == QLatin1String("documentation-code-snippets-dir")) {
+ m_codeSnippetDirs = value.split(QLatin1Char(PATH_SEP));
+ return true;
+ }
+ if (key == QLatin1String("documentation-extra-sections-dir")) {
+ m_extraSectionDir = value;
+ return true;
+ }
+ if (key == QLatin1String("doc-parser")) {
+ qCDebug(lcShibokenDoc).noquote().nospace() << "doc-parser: " << value;
+ if (value == QLatin1String("doxygen"))
+ m_docParser = new DoxygenParser;
+ return true;
+ }
+ if (key == additionalDocumentationOption()) {
+ m_additionalDocumentationList = value;
+ return true;
+ }
+ return false;
+}