qdoc: Generate QA pages
qdoc has a new command line option, -write-qa-pages. Using this flag on the command line will tell qdoc to generate a QA html page in each module's output directory. The QA page contains information that is useful for Quality Assurance checking of the module's docs. The QA file name begins with "aaa" so it will always be listed at the top of the output directory. The file name for the QA file for QtCore, for example, is aaa-qtcore-qa-page.html. Currently, the QA page only contains a report listing the intermodule link count for each module that is the target of links from the documented module. The link report can be used to optimize the search order qdoc uses when resolving inter-module links. By default, the search order is the same as the ordering of the modules in the depends list in the .qdocconf file. Using the report, the user can reorder that list according to the number of links found in each module. i.e. in descending order of link count. The modules are listed in descending order of link count. Additionally, an actual depends variable is printed. It can be cut and pasted into the module's qdocconf file. Change-Id: I442596aeb54dcdd5db4a0821096a5273c15627e6 Task-number: QTBUG-41850 Reviewed-by: Topi Reiniö <topi.reinio@digia.com>bb10
parent
77165553af
commit
4f1de1432c
|
|
@ -119,6 +119,7 @@ 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
|
||||
|
|
|
|||
|
|
@ -231,6 +231,7 @@ struct ConfigStrings
|
|||
static QString IMAGEEXTENSIONS;
|
||||
static QString QMLONLY;
|
||||
static QString QMLTYPESPAGE;
|
||||
static QString WRITEQAPAGES;
|
||||
};
|
||||
|
||||
#define CONFIG_ALIAS ConfigStrings::ALIAS
|
||||
|
|
@ -305,6 +306,7 @@ struct ConfigStrings
|
|||
#define CONFIG_IMAGEEXTENSIONS ConfigStrings::IMAGEEXTENSIONS
|
||||
#define CONFIG_QMLONLY ConfigStrings::QMLONLY
|
||||
#define CONFIG_QMLTYPESPAGE ConfigStrings::QMLTYPESPAGE
|
||||
#define CONFIG_WRITEQAPAGES ConfigStrings::WRITEQAPAGES
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ QString Generator::outSubdir_;
|
|||
QStringList Generator::outFileNames_;
|
||||
QSet<QString> Generator::outputFormats;
|
||||
QHash<QString, QString> Generator::outputPrefixes;
|
||||
QString Generator::project;
|
||||
QString Generator::project_;
|
||||
QStringList Generator::scriptDirs;
|
||||
QStringList Generator::scriptFiles;
|
||||
QString Generator::sinceTitles[] =
|
||||
|
|
@ -94,6 +94,7 @@ bool Generator::autolinkErrors_ = false;
|
|||
bool Generator::redirectDocumentationToDevNull_ = false;
|
||||
Generator::QDocPass Generator::qdocPass_ = Generator::Neither;
|
||||
bool Generator::qdocSingleExec_ = false;
|
||||
bool Generator::qdocWriteQaPages_ = false;
|
||||
bool Generator::useOutputSubdirs_ = true;
|
||||
|
||||
void Generator::startDebugging(const QString& message)
|
||||
|
|
@ -317,7 +318,7 @@ QString Generator::fileBase(const Node *node) const
|
|||
if (node->isExample() || node->isExampleFile()) {
|
||||
QString modPrefix(node->moduleName());
|
||||
if (modPrefix.isEmpty()) {
|
||||
modPrefix = project;
|
||||
modPrefix = project_;
|
||||
}
|
||||
base.prepend(modPrefix.toLower() + QLatin1Char('-'));
|
||||
}
|
||||
|
|
@ -1646,7 +1647,7 @@ void Generator::initialize(const Config &config)
|
|||
++n;
|
||||
}
|
||||
|
||||
project = config.getString(CONFIG_PROJECT);
|
||||
project_ = config.getString(CONFIG_PROJECT);
|
||||
|
||||
QStringList prefixes = config.getStringList(CONFIG_OUTPUTPREFIXES);
|
||||
if (!prefixes.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -95,8 +95,10 @@ public:
|
|||
static bool preparing() { return (qdocPass_ == Prepare); }
|
||||
static bool generating() { return (qdocPass_ == Generate); }
|
||||
static bool singleExec() { return qdocSingleExec_; }
|
||||
static bool writeQaPages() { return qdocWriteQaPages_; }
|
||||
static void setSingleExec() { qdocSingleExec_ = true; }
|
||||
static QString defaultModuleName() { return project; }
|
||||
static void setWriteQaPages() { qdocWriteQaPages_ = true; }
|
||||
static QString defaultModuleName() { return project_; }
|
||||
static void resetUseOutputSubdirs() { useOutputSubdirs_ = false; }
|
||||
static bool useOutputSubdirs() { return useOutputSubdirs_; }
|
||||
|
||||
|
|
@ -105,6 +107,7 @@ protected:
|
|||
virtual void endSubPage();
|
||||
virtual QString fileBase(const Node* node) const;
|
||||
virtual QString fileExtension() const = 0;
|
||||
virtual void generateQAPage() { }
|
||||
virtual void generateAlsoList(const Node *node, CodeMarker *marker);
|
||||
virtual int generateAtom(const Atom *atom, const Node *relative, CodeMarker *marker);
|
||||
virtual void generateBody(const Node *node, CodeMarker *marker);
|
||||
|
|
@ -200,7 +203,7 @@ private:
|
|||
static QStringList imageDirs;
|
||||
static QStringList imageFiles;
|
||||
static QMap<QString, QStringList> imgFileExts;
|
||||
static QString project;
|
||||
static QString project_;
|
||||
static QString outDir_;
|
||||
static QString outSubdir_;
|
||||
static QStringList outFileNames_;
|
||||
|
|
@ -216,6 +219,7 @@ private:
|
|||
static bool redirectDocumentationToDevNull_;
|
||||
static QDocPass qdocPass_;
|
||||
static bool qdocSingleExec_;
|
||||
static bool qdocWriteQaPages_;
|
||||
static bool useOutputSubdirs_;
|
||||
|
||||
void generateReimplementedFrom(const FunctionNode *func, CodeMarker *marker);
|
||||
|
|
|
|||
|
|
@ -280,6 +280,8 @@ void HtmlGenerator::generateDocs()
|
|||
qflagsHref_ = linkForNode(qflags,0);
|
||||
if (!preparing())
|
||||
Generator::generateDocs();
|
||||
if (Generator::generating() && Generator::writeQaPages())
|
||||
generateQAPage();
|
||||
|
||||
if (!generating()) {
|
||||
QString fileBase = project.toLower().simplified().replace(QLatin1Char(' '), QLatin1Char('-'));
|
||||
|
|
@ -300,6 +302,46 @@ void HtmlGenerator::generateDocs()
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Output the module's Quality Assurance page.
|
||||
*/
|
||||
void HtmlGenerator::generateQAPage()
|
||||
{
|
||||
NamespaceNode* node = qdb_->primaryTreeRoot();
|
||||
beginSubPage(node, "aaa-" + defaultModuleName().toLower() + "-qa-page.html");
|
||||
CodeMarker* marker = CodeMarker::markerForFileName(node->location().filePath());
|
||||
QString title = "Quality Assurance Page for " + defaultModuleName();
|
||||
QString t = "Quality assurance information for checking the " + defaultModuleName() + " documentation.";
|
||||
generateHeader(title, node, marker);
|
||||
generateTitle(title, Text() << t, LargeSubTitle, node, marker);
|
||||
|
||||
QStringList strings;
|
||||
QVector<int> counts;
|
||||
QString depends = qdb_->getLinkCounts(strings, counts);
|
||||
if (!strings.isEmpty()) {
|
||||
t = "Intermodule Link Counts";
|
||||
QString ref = registerRef(t);
|
||||
out() << "<a name=\"" << ref << "\"></a>" << divNavTop << '\n';
|
||||
out() << "<h2 id=\"" << ref << "\">" << protectEnc(t) << "</h2>\n";
|
||||
out() << "<table class=\"valuelist\"><tr valign=\"top\" "
|
||||
<< "class=\"even\"><th class=\"tblConst\">Destination Module</th>"
|
||||
<< "<th class=\"tblval\">Link Count</th></tr>\n";
|
||||
for (int i = 0; i< strings.size(); ++i) {
|
||||
out() << "<tr><td class=\"topAlign\"><tt>" << strings.at(i)
|
||||
<< "</tt></td><td class=\"topAlign\"><tt>" << counts.at(i)
|
||||
<< "</tt></td></tr>\n";
|
||||
}
|
||||
out() << "</table>\n";
|
||||
t = "The Optimal \"depends\" Variable";
|
||||
out() << "<h2>" << protectEnc(t) << "</h2>\n";
|
||||
t = "Consider replacing the depends variable in " + defaultModuleName().toLower() +
|
||||
".qdocconf with this one, if the two are not identical:";
|
||||
out() << "<p>" << protectEnc(t) << "</p>\n";
|
||||
out() << "<p>" << protectEnc(depends) << "</p>\n";
|
||||
}
|
||||
endSubPage();
|
||||
}
|
||||
|
||||
/*!
|
||||
Generate html from an instance of Atom.
|
||||
*/
|
||||
|
|
@ -3676,6 +3718,9 @@ QString HtmlGenerator::getLink(const Atom *atom, const Node *relative, const Nod
|
|||
if (!(*node))
|
||||
return QString();
|
||||
|
||||
if (Generator::writeQaPages())
|
||||
qdb_->incrementLinkCount(*node);
|
||||
|
||||
QString url = (*node)->url();
|
||||
if (!url.isEmpty()) {
|
||||
if (ref.isEmpty())
|
||||
|
|
@ -3720,6 +3765,9 @@ QString HtmlGenerator::getAutoLink(const Atom *atom, const Node *relative, const
|
|||
if (!(*node))
|
||||
return QString();
|
||||
|
||||
if (Generator::writeQaPages())
|
||||
qdb_->incrementLinkCount(*node);
|
||||
|
||||
QString url = (*node)->url();
|
||||
if (!url.isEmpty()) {
|
||||
if (ref.isEmpty())
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ public:
|
|||
static QString sinceTitle(int i) { return sinceTitles[i]; }
|
||||
|
||||
protected:
|
||||
virtual void generateQAPage();
|
||||
virtual int generateAtom(const Atom *atom,
|
||||
const Node *relative,
|
||||
CodeMarker *marker);
|
||||
|
|
|
|||
|
|
@ -285,6 +285,15 @@ void Location::fatal(const QString& message, const QString& details) const
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes \a message and \a detals to stderr as a formatted
|
||||
report message.
|
||||
*/
|
||||
void Location::report(const QString& message, const QString& details) const
|
||||
{
|
||||
emitMessage(Report, message, details);
|
||||
}
|
||||
|
||||
/*!
|
||||
Gets several parameters from the \a config, including
|
||||
tab size, program name, and a regular expression that
|
||||
|
|
@ -371,7 +380,8 @@ void Location::emitMessage(MessageType type,
|
|||
result.prepend(tr(": error: "));
|
||||
else if (type == Warning)
|
||||
result.prepend(tr(": warning: "));
|
||||
result.prepend(toString());
|
||||
if (type != Report)
|
||||
result.prepend(toString());
|
||||
fprintf(stderr, "%s\n", result.toLatin1().data());
|
||||
fflush(stderr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ public:
|
|||
const QString& details = QString()) const;
|
||||
void fatal(const QString& message,
|
||||
const QString& details = QString()) const;
|
||||
void report(const QString& message,
|
||||
const QString& details = QString()) const;
|
||||
|
||||
static const Location null;
|
||||
|
||||
|
|
@ -94,7 +96,7 @@ public:
|
|||
static QString canonicalRelativePath(const QString &path);
|
||||
|
||||
private:
|
||||
enum MessageType { Warning, Error };
|
||||
enum MessageType { Warning, Error, Report };
|
||||
|
||||
struct StackEntry
|
||||
{
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ bool creationTimeBefore(const QFileInfo &fi1, const QFileInfo &fi2)
|
|||
static bool highlighting = false;
|
||||
static bool showInternal = false;
|
||||
static bool singleExec = false;
|
||||
static bool writeQaPages = false;
|
||||
static bool redirectDocumentationToDevNull = false;
|
||||
static bool noLinkErrors = false;
|
||||
static bool autolinkErrors = false;
|
||||
|
|
@ -221,6 +222,7 @@ static void processQdocconfFile(const QString &fileName)
|
|||
config.setStringList(CONFIG_SYNTAXHIGHLIGHTING, QStringList(highlighting ? "true" : "false"));
|
||||
config.setStringList(CONFIG_SHOWINTERNAL, QStringList(showInternal ? "true" : "false"));
|
||||
config.setStringList(CONFIG_SINGLEEXEC, QStringList(singleExec ? "true" : "false"));
|
||||
config.setStringList(CONFIG_WRITEQAPAGES, QStringList(writeQaPages ? "true" : "false"));
|
||||
config.setStringList(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL, QStringList(redirectDocumentationToDevNull ? "true" : "false"));
|
||||
config.setStringList(CONFIG_NOLINKERRORS, QStringList(noLinkErrors ? "true" : "false"));
|
||||
config.setStringList(CONFIG_AUTOLINKERRORS, QStringList(autolinkErrors ? "true" : "false"));
|
||||
|
|
@ -459,7 +461,6 @@ static void processQdocconfFile(const QString &fileName)
|
|||
codeParser->doneParsingHeaderFiles();
|
||||
|
||||
usedParsers.clear();
|
||||
//qDebug() << "CALL: resolveInheritance()";
|
||||
qdb->resolveInheritance();
|
||||
|
||||
/*
|
||||
|
|
@ -490,7 +491,6 @@ static void processQdocconfFile(const QString &fileName)
|
|||
targets, URLs, links, and other stuff that needs resolving.
|
||||
*/
|
||||
Generator::debug("Resolving stuff prior to generating docs");
|
||||
//qDebug() << "CALL: resolveIssues()";
|
||||
qdb->resolveIssues();
|
||||
}
|
||||
else {
|
||||
|
|
@ -533,7 +533,6 @@ static void processQdocconfFile(const QString &fileName)
|
|||
one.
|
||||
*/
|
||||
Generator::debug("Generating docs");
|
||||
//qDebug() << "CALL: generateDocs()";
|
||||
QSet<QString>::ConstIterator of = outputFormats.constBegin();
|
||||
while (of != outputFormats.constEnd()) {
|
||||
Generator* generator = Generator::generatorForFormat(*of);
|
||||
|
|
@ -543,8 +542,12 @@ static void processQdocconfFile(const QString &fileName)
|
|||
generator->generateDocs();
|
||||
++of;
|
||||
}
|
||||
#if 0
|
||||
if (Generator::generating() && Generator::writeQaPages())
|
||||
qdb->printLinkCounts(project);
|
||||
#endif
|
||||
qdb->clearLinkCounts();
|
||||
|
||||
//Generator::writeOutFileNames();
|
||||
Generator::debug("Terminating qdoc classes");
|
||||
if (Generator::debugging())
|
||||
Generator::stopDebugging(project);
|
||||
|
|
@ -681,6 +684,10 @@ int main(int argc, char **argv)
|
|||
singleExecOption.setDescription(QCoreApplication::translate("qdoc", "Run qdoc once over all the qdoc conf files."));
|
||||
parser.addOption(singleExecOption);
|
||||
|
||||
QCommandLineOption writeQaPagesOption(QStringList() << QStringLiteral("write-qa-pages"));
|
||||
writeQaPagesOption.setDescription(QCoreApplication::translate("qdoc", "Write QA pages."));
|
||||
parser.addOption(writeQaPagesOption);
|
||||
|
||||
parser.process(app);
|
||||
|
||||
defines += parser.values(defineOption);
|
||||
|
|
@ -688,6 +695,7 @@ int main(int argc, char **argv)
|
|||
highlighting = parser.isSet(highlightingOption);
|
||||
showInternal = parser.isSet(showInternalOption);
|
||||
singleExec = parser.isSet(singleExecOption);
|
||||
writeQaPages = parser.isSet(writeQaPagesOption);
|
||||
redirectDocumentationToDevNull = parser.isSet(redirectDocumentationToDevNullOption);
|
||||
Config::generateExamples = !parser.isSet(noExamplesOption);
|
||||
foreach (const QString &indexDir, parser.values(indexDirOption)) {
|
||||
|
|
@ -713,6 +721,8 @@ int main(int argc, char **argv)
|
|||
Generator::setQDocPass(Generator::Generate);
|
||||
if (parser.isSet(singleExecOption))
|
||||
Generator::setSingleExec();
|
||||
if (parser.isSet(writeQaPagesOption))
|
||||
Generator::setWriteQaPages();
|
||||
if (parser.isSet(logProgressOption))
|
||||
Location::startLoggingProgress();
|
||||
|
||||
|
|
|
|||
|
|
@ -274,7 +274,6 @@ const QVector<Tree*>& QDocForest::indexSearchOrder()
|
|||
*/
|
||||
NamespaceNode* QDocForest::newIndexTree(const QString& module)
|
||||
{
|
||||
//qDebug() << " New index tree:" << module;
|
||||
primaryTree_ = new Tree(module, qdb_);
|
||||
forest_.insert(module, primaryTree_);
|
||||
return primaryTree_->root();
|
||||
|
|
@ -286,7 +285,6 @@ NamespaceNode* QDocForest::newIndexTree(const QString& module)
|
|||
*/
|
||||
void QDocForest::newPrimaryTree(const QString& module)
|
||||
{
|
||||
//qDebug() << " New primary tree:" << module;
|
||||
primaryTree_ = new Tree(module, qdb_);
|
||||
}
|
||||
|
||||
|
|
@ -351,6 +349,61 @@ void QDocForest::mergeCollectionMaps(Node::Type nt, CNMultiMap& cnmm)
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Print the list of module names ordered according
|
||||
to how many successful searches each tree had.
|
||||
*/
|
||||
void QDocForest::printLinkCounts(const QString& project)
|
||||
{
|
||||
Location::null.report(QString("%1: Link Counts").arg(project));
|
||||
QMultiMap<int, QString> m;
|
||||
foreach (Tree* t, searchOrder()) {
|
||||
if (t->linkCount() < 0)
|
||||
m.insert(t->linkCount(), t->moduleName());
|
||||
}
|
||||
QString depends = "depends +=";
|
||||
QString module = project.toLower();
|
||||
QMultiMap<int, QString>::iterator i = m.begin();
|
||||
while (i != m.end()) {
|
||||
QString line = " " + i.value();
|
||||
if (i.value() != module)
|
||||
depends += " " + i.value();
|
||||
int pad = 30 - line.length();
|
||||
for (int k=0; k<pad; ++k)
|
||||
line += " ";
|
||||
line += "%1";
|
||||
Location::null.report(line.arg(-(i.key())));
|
||||
++i;
|
||||
}
|
||||
Location::null.report("Optimal depends variable:");
|
||||
Location::null.report(depends);
|
||||
}
|
||||
|
||||
/*!
|
||||
Print the list of module names ordered according
|
||||
to how many successful searches each tree had.
|
||||
*/
|
||||
QString QDocForest::getLinkCounts(QStringList& strings, QVector<int>& counts)
|
||||
{
|
||||
QMultiMap<int, QString> m;
|
||||
foreach (Tree* t, searchOrder()) {
|
||||
if (t->linkCount() < 0)
|
||||
m.insert(t->linkCount(), t->moduleName());
|
||||
}
|
||||
QString depends = "depends +=";
|
||||
QString module = Generator::defaultModuleName().toLower();
|
||||
QMultiMap<int, QString>::iterator i = m.begin();
|
||||
while (i != m.end()) {
|
||||
if (i.value() != module) {
|
||||
counts.append(-(i.key()));
|
||||
strings.append(i.value());
|
||||
depends += " " + i.value();
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return depends;
|
||||
}
|
||||
|
||||
/*! \class QDocDatabase
|
||||
This class provides exclusive access to the qdoc database,
|
||||
which consists of a forrest of trees and a lot of maps and
|
||||
|
|
|
|||
|
|
@ -194,6 +194,13 @@ class QDocForest
|
|||
}
|
||||
|
||||
void clearSearchOrder() { searchOrder_.clear(); }
|
||||
void clearLinkCounts()
|
||||
{
|
||||
foreach (Tree* t, searchOrder())
|
||||
t->clearLinkCount();
|
||||
}
|
||||
void printLinkCounts(const QString& project);
|
||||
QString getLinkCounts(QStringList& strings, QVector<int>& counts);
|
||||
|
||||
private:
|
||||
void newPrimaryTree(const QString& module);
|
||||
|
|
@ -379,6 +386,12 @@ class QDocDatabase
|
|||
void mergeCollections(Node::Type nt, CNMap& cnm, const Node* relative);
|
||||
void mergeCollections(CollectionNode* cn);
|
||||
void clearSearchOrder() { forest_.clearSearchOrder(); }
|
||||
void incrementLinkCount(const Node* t) { t->tree()->incrementLinkCount(); }
|
||||
void clearLinkCounts() { forest_.clearLinkCounts(); }
|
||||
void printLinkCounts(const QString& t) { forest_.printLinkCounts(t); }
|
||||
QString getLinkCounts(QStringList& strings, QVector<int>& counts) {
|
||||
return forest_.getLinkCounts(strings, counts);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class QDocIndexFiles;
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ QT_BEGIN_NAMESPACE
|
|||
Tree::Tree(const QString& module, QDocDatabase* qdb)
|
||||
: treeHasBeenAnalyzed_(false),
|
||||
docsHaveBeenGenerated_(false),
|
||||
linkCount_(0),
|
||||
module_(module),
|
||||
qdb_(qdb),
|
||||
root_(0, QString())
|
||||
|
|
|
|||
|
|
@ -197,10 +197,14 @@ class Tree
|
|||
public:
|
||||
const QString& moduleName() const { return module_; }
|
||||
const QString& indexFileName() const { return indexFileName_; }
|
||||
void incrementLinkCount() { --linkCount_; }
|
||||
void clearLinkCount() { linkCount_ = 0; }
|
||||
int linkCount() const { return linkCount_; }
|
||||
|
||||
private:
|
||||
bool treeHasBeenAnalyzed_;
|
||||
bool docsHaveBeenGenerated_;
|
||||
int linkCount_;
|
||||
QString module_;
|
||||
QString indexFileName_;
|
||||
QDocDatabase* qdb_;
|
||||
|
|
|
|||
Loading…
Reference in New Issue