forked from qt-creator/qt-creator
Examples: Read and handle "documentation dependencies"
Some examples are defined in a Qt module (and therefore examples- manifest), but have dependencies on other modules. The example-manifests contain a `module` attribute for their `instructionals` tag, and examples can contain "docdependencies" items in their meta data, which are comma-separated lists of these module names. Read the `module` attributes (the meta data is already generically read) and check if the current set of examples-manifest contains all required ones for an example. Hide it, if not. Task-number: QTBUG-120759 Change-Id: Ib8ff80cf8a13965e57b5c4317e5d121c4127278b Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Kai Köhne <kai.koehne@qt.io>
This commit is contained in:
@@ -299,35 +299,53 @@ ExamplesViewController::ExamplesViewController(ExampleSetModel *exampleSetModel,
|
|||||||
updateExamples();
|
updateExamples();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isValidExampleOrDemo(ExampleItem *item)
|
static std::function<bool(ExampleItem *)> isValidExampleOrDemo(
|
||||||
|
const QSet<QString> &instructionalsModules)
|
||||||
{
|
{
|
||||||
QTC_ASSERT(item, return false);
|
return [instructionalsModules](ExampleItem *item) -> bool {
|
||||||
if (item->type == Tutorial)
|
QTC_ASSERT(item, return false);
|
||||||
return true;
|
if (item->type == Tutorial)
|
||||||
static QString invalidPrefix = QLatin1String("qthelp:////"); /* means that the qthelp url
|
return true;
|
||||||
|
static QString invalidPrefix = QLatin1String("qthelp:////"); /* means that the qthelp url
|
||||||
doesn't have any namespace */
|
doesn't have any namespace */
|
||||||
QString reason;
|
QString reason;
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
if (!item->hasSourceCode || !item->projectPath.exists()) {
|
if (!item->hasSourceCode || !item->projectPath.exists()) {
|
||||||
ok = false;
|
ok = false;
|
||||||
reason = QString::fromLatin1("projectPath \"%1\" empty or does not exist")
|
reason = QString::fromLatin1("projectPath \"%1\" empty or does not exist")
|
||||||
.arg(item->projectPath.toUserOutput());
|
.arg(item->projectPath.toUserOutput());
|
||||||
} else if (item->imageUrl.startsWith(invalidPrefix) || !QUrl(item->imageUrl).isValid()) {
|
} else if (item->imageUrl.startsWith(invalidPrefix) || !QUrl(item->imageUrl).isValid()) {
|
||||||
ok = false;
|
ok = false;
|
||||||
reason = QString::fromLatin1("imageUrl \"%1\" not valid").arg(item->imageUrl);
|
reason = QString::fromLatin1("imageUrl \"%1\" not valid").arg(item->imageUrl);
|
||||||
} else if (!item->docUrl.isEmpty()
|
} else if (!item->docUrl.isEmpty()
|
||||||
&& (item->docUrl.startsWith(invalidPrefix) || !QUrl(item->docUrl).isValid())) {
|
&& (item->docUrl.startsWith(invalidPrefix) || !QUrl(item->docUrl).isValid())) {
|
||||||
ok = false;
|
ok = false;
|
||||||
reason = QString::fromLatin1("docUrl \"%1\" non-empty but not valid").arg(item->docUrl);
|
reason = QString::fromLatin1("docUrl \"%1\" non-empty but not valid").arg(item->docUrl);
|
||||||
}
|
}
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
item->tags.append(QLatin1String("broken"));
|
item->tags.append(QLatin1String("broken"));
|
||||||
qCDebug(log) << QString::fromLatin1("ERROR: Item \"%1\" broken: %2").arg(item->name, reason);
|
qCDebug(log) << QString::fromLatin1("ERROR: Item \"%1\" broken: %2")
|
||||||
}
|
.arg(item->name, reason);
|
||||||
if (item->description.isEmpty())
|
}
|
||||||
qCDebug(log) << QString::fromLatin1("WARNING: Item \"%1\" has no description")
|
if (item->description.isEmpty())
|
||||||
.arg(item->name);
|
qCDebug(log) << QString::fromLatin1("WARNING: Item \"%1\" has no description")
|
||||||
return ok || debugExamples();
|
.arg(item->name);
|
||||||
|
// a single docdependencies entry is a string of items concatenated with ','
|
||||||
|
// the collected meta data can be a list of this
|
||||||
|
for (const QString &entry : item->metaData.value("docdependencies")) {
|
||||||
|
const QStringList deps = entry.split(',');
|
||||||
|
for (const QString &dep : deps) {
|
||||||
|
if (!instructionalsModules.contains(dep)) {
|
||||||
|
item->tags.append("unresolvedDependency");
|
||||||
|
qCDebug(log) << QLatin1String("INFO: Item \"%1\" requires \"%2\"")
|
||||||
|
.arg(item->name, dep);
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ok || debugExamples();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ordered list of "known" categories
|
// ordered list of "known" categories
|
||||||
@@ -365,6 +383,7 @@ void ExamplesViewController::updateExamples()
|
|||||||
&demosInstallPath,
|
&demosInstallPath,
|
||||||
&qtVersion,
|
&qtVersion,
|
||||||
m_isExamples);
|
m_isExamples);
|
||||||
|
QSet<QString> instructionalsModules;
|
||||||
QStringList categoryOrder;
|
QStringList categoryOrder;
|
||||||
QList<ExampleItem *> items;
|
QList<ExampleItem *> items;
|
||||||
for (const QString &exampleSource : sources) {
|
for (const QString &exampleSource : sources) {
|
||||||
@@ -382,10 +401,12 @@ void ExamplesViewController::updateExamples()
|
|||||||
<< result.error();
|
<< result.error();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
items += filtered(result->items, isValidExampleOrDemo);
|
instructionalsModules.insert(result->instructionalsModule);
|
||||||
|
items += result->items;
|
||||||
if (categoryOrder.isEmpty())
|
if (categoryOrder.isEmpty())
|
||||||
categoryOrder = result->categoryOrder;
|
categoryOrder = result->categoryOrder;
|
||||||
}
|
}
|
||||||
|
items = filtered(items, isValidExampleOrDemo(instructionalsModules));
|
||||||
|
|
||||||
if (m_isExamples) {
|
if (m_isExamples) {
|
||||||
if (m_exampleSetModel->selectedQtSupports(Android::Constants::ANDROID_DEVICE_TYPE)) {
|
if (m_exampleSetModel->selectedQtSupports(Android::Constants::ANDROID_DEVICE_TYPE)) {
|
||||||
|
@@ -283,20 +283,21 @@ expected_str<ParsedExamples> parseExamples(const QByteArray &manifestData,
|
|||||||
const bool examples)
|
const bool examples)
|
||||||
{
|
{
|
||||||
const FilePath path = manifestPath.parentDir();
|
const FilePath path = manifestPath.parentDir();
|
||||||
QStringList categoryOrder;
|
ParsedExamples result;
|
||||||
QList<ExampleItem *> items;
|
|
||||||
QXmlStreamReader reader(manifestData);
|
QXmlStreamReader reader(manifestData);
|
||||||
while (!reader.atEnd()) {
|
while (!reader.atEnd()) {
|
||||||
switch (reader.readNext()) {
|
switch (reader.readNext()) {
|
||||||
case QXmlStreamReader::StartElement:
|
case QXmlStreamReader::StartElement:
|
||||||
if (categoryOrder.isEmpty() && reader.name() == QLatin1String("categories"))
|
if (reader.name() == QLatin1String("instructionals"))
|
||||||
categoryOrder = parseCategories(&reader);
|
result.instructionalsModule = reader.attributes().value("module").toString();
|
||||||
|
else if (result.categoryOrder.isEmpty() && reader.name() == QLatin1String("categories"))
|
||||||
|
result.categoryOrder = parseCategories(&reader);
|
||||||
else if (examples && reader.name() == QLatin1String("examples"))
|
else if (examples && reader.name() == QLatin1String("examples"))
|
||||||
items += parseExamples(&reader, path, examplesInstallPath);
|
result.items += parseExamples(&reader, path, examplesInstallPath);
|
||||||
else if (examples && reader.name() == QLatin1String("demos"))
|
else if (examples && reader.name() == QLatin1String("demos"))
|
||||||
items += parseDemos(&reader, path, demosInstallPath);
|
result.items += parseDemos(&reader, path, demosInstallPath);
|
||||||
else if (!examples && reader.name() == QLatin1String("tutorials"))
|
else if (!examples && reader.name() == QLatin1String("tutorials"))
|
||||||
items += parseTutorials(&reader, path);
|
result.items += parseTutorials(&reader, path);
|
||||||
break;
|
break;
|
||||||
default: // nothing
|
default: // nothing
|
||||||
break;
|
break;
|
||||||
@@ -304,14 +305,14 @@ expected_str<ParsedExamples> parseExamples(const QByteArray &manifestData,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reader.hasError()) {
|
if (reader.hasError()) {
|
||||||
qDeleteAll(items);
|
qDeleteAll(result.items);
|
||||||
return make_unexpected(QString("Could not parse file \"%1\" as XML document: %2:%3: %4")
|
return make_unexpected(QString("Could not parse file \"%1\" as XML document: %2:%3: %4")
|
||||||
.arg(manifestPath.toUserOutput())
|
.arg(manifestPath.toUserOutput())
|
||||||
.arg(reader.lineNumber())
|
.arg(reader.lineNumber())
|
||||||
.arg(reader.columnNumber())
|
.arg(reader.columnNumber())
|
||||||
.arg(reader.errorString()));
|
.arg(reader.errorString()));
|
||||||
}
|
}
|
||||||
return {{items, categoryOrder}};
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool sortByHighlightedAndName(ExampleItem *first, ExampleItem *second)
|
static bool sortByHighlightedAndName(ExampleItem *first, ExampleItem *second)
|
||||||
|
@@ -34,6 +34,7 @@ public:
|
|||||||
class QTSUPPORT_TEST_EXPORT ParsedExamples
|
class QTSUPPORT_TEST_EXPORT ParsedExamples
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
QString instructionalsModule;
|
||||||
QList<ExampleItem *> items;
|
QList<ExampleItem *> items;
|
||||||
QStringList categoryOrder;
|
QStringList categoryOrder;
|
||||||
};
|
};
|
||||||
|
@@ -71,6 +71,7 @@ static ExampleItem fetchItem()
|
|||||||
void tst_Examples::parsing_data()
|
void tst_Examples::parsing_data()
|
||||||
{
|
{
|
||||||
QTest::addColumn<QByteArray>("data");
|
QTest::addColumn<QByteArray>("data");
|
||||||
|
QTest::addColumn<QString>("instructionalsModule");
|
||||||
QTest::addColumn<bool>("isExamples");
|
QTest::addColumn<bool>("isExamples");
|
||||||
QTest::addColumn<QString>("name");
|
QTest::addColumn<QString>("name");
|
||||||
QTest::addColumn<QString>("description");
|
QTest::addColumn<QString>("description");
|
||||||
@@ -94,7 +95,7 @@ void tst_Examples::parsing_data()
|
|||||||
|
|
||||||
QTest::addRow("example")
|
QTest::addRow("example")
|
||||||
<< QByteArray(R"raw(
|
<< QByteArray(R"raw(
|
||||||
<instructionals module="Qt">
|
<instructionals module="QtGui">
|
||||||
<examples>
|
<examples>
|
||||||
<example docUrl="qthelp://org.qt-project.qtwidgets.660/qtwidgets/qtwidgets-widgets-analogclock-example.html"
|
<example docUrl="qthelp://org.qt-project.qtwidgets.660/qtwidgets/qtwidgets-widgets-analogclock-example.html"
|
||||||
imageUrl="qthelp://org.qt-project.qtwidgets.660/qtwidgets/images/analogclock-example.png"
|
imageUrl="qthelp://org.qt-project.qtwidgets.660/qtwidgets/images/analogclock-example.png"
|
||||||
@@ -119,8 +120,8 @@ void tst_Examples::parsing_data()
|
|||||||
<category>Embedded</category>
|
<category>Embedded</category>
|
||||||
</categories>
|
</categories>
|
||||||
</instructionals>
|
</instructionals>
|
||||||
)raw") << /*isExamples=*/true
|
)raw") << "QtGui"
|
||||||
<< "Analog Clock"
|
<< /*isExamples=*/true << "Analog Clock"
|
||||||
<< "The Analog Clock example shows how to draw the contents of a custom widget."
|
<< "The Analog Clock example shows how to draw the contents of a custom widget."
|
||||||
<< "qthelp://org.qt-project.qtwidgets.660/qtwidgets/images/analogclock-example.png"
|
<< "qthelp://org.qt-project.qtwidgets.660/qtwidgets/images/analogclock-example.png"
|
||||||
<< QStringList{"ios", "widgets"}
|
<< QStringList{"ios", "widgets"}
|
||||||
@@ -140,17 +141,18 @@ void tst_Examples::parsing_data()
|
|||||||
|
|
||||||
QTest::addRow("no category, highlighted")
|
QTest::addRow("no category, highlighted")
|
||||||
<< QByteArray(R"raw(
|
<< QByteArray(R"raw(
|
||||||
<instructionals module="Qt">
|
<instructionals module="QtQuick3D">
|
||||||
<examples>
|
<examples>
|
||||||
<example name="No Category, highlighted"
|
<example name="No Category, highlighted"
|
||||||
isHighlighted="true">
|
isHighlighted="true">
|
||||||
</example>
|
</example>
|
||||||
</examples>
|
</examples>
|
||||||
</instructionals>
|
</instructionals>
|
||||||
)raw") << /*isExamples=*/true
|
)raw") << "QtQuick3D"
|
||||||
<< "No Category, highlighted" << QString() << QString() << QStringList()
|
<< /*isExamples=*/true << "No Category, highlighted" << QString() << QString()
|
||||||
<< FilePath("examples") << QString() << FilePaths() << FilePath() << FilePaths() << Example
|
<< QStringList() << FilePath("examples") << QString() << FilePaths() << FilePath()
|
||||||
<< /*hasSourceCode=*/false << false << /*isHighlighted=*/true << ""
|
<< FilePaths() << Example << /*hasSourceCode=*/false << false << /*isHighlighted=*/true
|
||||||
|
<< ""
|
||||||
<< "" << QStringList() << MetaData() << QStringList{"Featured"} << QStringList();
|
<< "" << QStringList() << MetaData() << QStringList{"Featured"} << QStringList();
|
||||||
|
|
||||||
QTest::addRow("tutorial with category")
|
QTest::addRow("tutorial with category")
|
||||||
@@ -172,8 +174,8 @@ void tst_Examples::parsing_data()
|
|||||||
</tutorial>
|
</tutorial>
|
||||||
</tutorials>
|
</tutorials>
|
||||||
</instructionals>
|
</instructionals>
|
||||||
)raw") << /*isExamples=*/false
|
)raw") << "Qt"
|
||||||
<< "A tutorial"
|
<< /*isExamples=*/false << "A tutorial"
|
||||||
<< "A dummy tutorial."
|
<< "A dummy tutorial."
|
||||||
<< ":qtsupport/images/icons/tutorialicon.png"
|
<< ":qtsupport/images/icons/tutorialicon.png"
|
||||||
<< QStringList{"qt creator", "build", "compile", "help"} << FilePath()
|
<< QStringList{"qt creator", "build", "compile", "help"} << FilePath()
|
||||||
@@ -187,6 +189,7 @@ void tst_Examples::parsing_data()
|
|||||||
void tst_Examples::parsing()
|
void tst_Examples::parsing()
|
||||||
{
|
{
|
||||||
QFETCH(QByteArray, data);
|
QFETCH(QByteArray, data);
|
||||||
|
QFETCH(QString, instructionalsModule);
|
||||||
QFETCH(bool, isExamples);
|
QFETCH(bool, isExamples);
|
||||||
QFETCH(QStringList, categories);
|
QFETCH(QStringList, categories);
|
||||||
QFETCH(QStringList, categoryOrder);
|
QFETCH(QStringList, categoryOrder);
|
||||||
@@ -198,6 +201,7 @@ void tst_Examples::parsing()
|
|||||||
FilePath("demos"),
|
FilePath("demos"),
|
||||||
isExamples);
|
isExamples);
|
||||||
QVERIFY(result);
|
QVERIFY(result);
|
||||||
|
QCOMPARE(result->instructionalsModule, instructionalsModule);
|
||||||
QCOMPARE(result->categoryOrder, categoryOrder);
|
QCOMPARE(result->categoryOrder, categoryOrder);
|
||||||
QCOMPARE(result->items.size(), 1);
|
QCOMPARE(result->items.size(), 1);
|
||||||
const ExampleItem item = *result->items.at(0);
|
const ExampleItem item = *result->items.at(0);
|
||||||
|
Reference in New Issue
Block a user