diff options
author | Luca Di Sera <luca.disera@qt.io> | 2022-04-05 14:23:33 +0200 |
---|---|---|
committer | Luca Di Sera <luca.disera@qt.io> | 2022-05-10 15:37:25 +0200 |
commit | 7a42bbe2b38a7c6f97678143d61d1bb16e323df3 (patch) | |
tree | c78ec05486b33adae0db2cd49b4bf9bd9545f2f0 /src/qdoc/generator.cpp | |
parent | 6a3a49994444f7291ba356e9970928805c377ab4 (diff) | |
download | qttools-7a42bbe2b38a7c6f97678143d61d1bb16e323df3.tar.gz |
QDoc: Apply the FileResolver abstraction to the code
The recently added `FileResolver` abstraction encapsulate the logic that
QDoc uses for finding user-provided files in the documentation that QDoc
will need to interface with.
Previously, this process was mostly contained in `Doc::resolve_file`.
This was considered problematic as `Doc` is an extremely complex class
with many responsibilities, coupling the logic with many unrequired
preconditions and making testing of this simple logic almost
unattainable.
Furthermore, a similar logic in QDoc was provided by `Config::findFile`,
which was sometimes used instead of `Doc::resolveFile`, fracturing the
behavior of the code.
To avoid those issues, allow for the testing of the logic for file
resolution and to generally simplify the code and make it more
consistent, `Doc::resolveFile` was removed and substituted with the
`FileResolver` abstraction.
A few changes were made to support this new idiom.
- An instance of `FileResolver` is now built in `processQdocconfFile`
and passed as a reference to systems that require it during
construction.
A small exception was made for `DocParser` where the instance is passed
as a pointer through `DocParser::initialize` to support the current code
structure.
The required changes to the relevant entities constructors were made to
allow for this.
- `FileResolver` is provided as an explicit dependency to anything that
requires the capability of resolving files, such as generators.
Generally during construction.
This is intended as a first step in removing the intitialize-terminate
idiom, reducing the scope of some of the mutable state and increasing
the expliciteness of its provenance.
To allow for this, the main routing of QDoc was modified to construct
the Generators classes during the production of the documentation in
`processQdocconfFile`.
This was required as an initialized `FileResolver` is needed for the
generators to work. As `FileResolver` instead depends on a parsed
configuration, it was not possible anymore to construct the generator
once before doing anything else.
A small defensive addition to `Generator::terminate` was added, removing all
generators from the list of generators. This should not actually be
required, but it was added to ensure that the now more limited scope of
the generators and the possibility that they would be constructed more
than once would not collide with their broader, unrequired global scope.
- To take into account the simplified data requirements for
`FileResolver`, compared to the previous logic, such the usage of a
single list of directories, the extraction of relevant values from the
configuration in `processQdocconfFile` was modified to further mold the
data into a suitable format for `FileResolver`.
- As the data is relevant to searching directories is contained within
the `FileResolver` instance that underlying systems use and does not
require any processing to be retrieved, some of the caching members in,
for example, `Generator` that contained a reference to this data were
removed.
See, for example, `Generator::s_exampleDirs`.
As a consequence of all those changes, `Generator::augmentImageDirs` and
`Generator::setImageFileExtensions` were removed as unused.
- `Generator::generateExampleFilePage`, was modified from having two
overload to a single method with a default argument to reduce the bloat.
- `Generator::addImageToCopy` was modified to use a `ResolvedFile`.
The method copies an image from the examples to the output directory.
This should be done only when the image is available.
- Places where `Doc::resolveFile` were called were modified to use
`FileResolver::resolve`.
Due to this change some underlying methods were modified to require a
`ResolvedFile`.
`Doc::resolveFile` was removed as a consequence.
- `Doc::quoteFromFile`, which previously depended on `Doc::ResolveFile`,
was modified to require a `ResolvedFile`.
This lightens the responsibility of the method to quoting only, instead
of finding the file and quoting, ensuring that it is called only when
the quoting can actually happen. Furthermore, it removes the need for
some of the error reporting which is bubbled up to the caller during
file resolution.
This was further required to avoid having `Doc` require a `FileResolver`
instance, as `Doc`'s constructor is called in many places.
- The usage of a `userFriendlyFilePath` was removed from code using
`Config::findFile`.
A run of QDoc on the current documentation shows that
`userFrinedlyFilePath` is always equal to the original path that was
passed to find file except in two cases where `userFriendlyFilePath`
would be null.
Consumer code didn't seem to make any use of the null state of the
`userFriendlyFilePath` and ignoring it doesn't seem to change the output
documentation, such that it was considered safe to avoid.
It is currently unclear what the original purpose of this element was.
As `Doc::resolveFile` implicitly used `Config::findFile` and exposed
back the `userFriendlyFilePath`, this is true for all replaced usages of
the method too, which allowed ignoring the element in the first place.
- A now unused overload of `Config::findFile` taking a series of
extensions for the searched for file was removed.
- `Config::copyFile` was modified to remove some of the processing done
to its input parameters which is now unused.
A simplified version of it was retained to handle a known case that is
actually used by the code.
- `DocParser` was modified to internally make use of `FileResolver` for
when resolving files for quoting and some code handling.
It previously used a mix of `Doc::quoteFromFile` and its internal
`quoteFromFile`.
The usages were made consistent depending only on the internal
`quoteFromFile`.
`quoteFromFile` itself was further changed to take into account the
changes in `Doc::quoteFromFile`, now resolving the file itself.
- Some of the code was littered with TODO and REMARK comments to keep
track of things that were noticed during the change that will not be
addressed at this point in time.
Change-Id: Ifb2e03696f6de64dac54470f7a969d323a88c0a7
Task-number: QTBUG-100381
Reviewed-by: Topi Reiniƶ <topi.reinio@qt.io>
Diffstat (limited to 'src/qdoc/generator.cpp')
-rw-r--r-- | src/qdoc/generator.cpp | 172 |
1 files changed, 99 insertions, 73 deletions
diff --git a/src/qdoc/generator.cpp b/src/qdoc/generator.cpp index 030dbdf06..c2beb07f6 100644 --- a/src/qdoc/generator.cpp +++ b/src/qdoc/generator.cpp @@ -61,14 +61,9 @@ QT_BEGIN_NAMESPACE Generator *Generator::s_currentGenerator; -QStringList Generator::s_exampleDirs; -QStringList Generator::s_exampleImgExts; QMap<QString, QMap<QString, QString>> Generator::s_fmtLeftMaps; QMap<QString, QMap<QString, QString>> Generator::s_fmtRightMaps; QList<Generator *> Generator::s_generators; -QStringList Generator::s_imageDirs; -QStringList Generator::s_imageFiles; -QMap<QString, QStringList> Generator::s_imgFileExts; QString Generator::s_outDir; QString Generator::s_outSubdir; QStringList Generator::s_outFileNames; @@ -94,7 +89,8 @@ static QLatin1String quot("""); Sets a pointer to the QDoc database singleton, which is available to the generator subclasses. */ -Generator::Generator() +Generator::Generator(FileResolver& file_resolver) + : file_resolver{file_resolver} { m_qdb = QDocDatabase::qdocDB(); s_generators.prepend(this); @@ -949,21 +945,38 @@ void Generator::generateLinkToExample(const ExampleNode *en, CodeMarker *marker, generateText(text, nullptr, marker); } -void Generator::addImageToCopy(const ExampleNode *en, const QString &file) +void Generator::addImageToCopy(const ExampleNode *en, const ResolvedFile& resolved_file) { QDir dirInfo; QString userFriendlyFilePath; - const QString prefix("/images/used-in-examples/"); - QString srcPath = Config::findFile(en->location(), QStringList(), s_exampleDirs, file, - s_exampleImgExts, &userFriendlyFilePath); - s_outFileNames << prefix.mid(1) + userFriendlyFilePath; - userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/')); - QString imgOutDir = s_outDir + prefix + userFriendlyFilePath; + // TODO: [uncentralized-output-directory-structure] + const QString prefix("/images/used-in-examples"); + + // TODO: Generators probably should not need to keep track of which files were generated. + // Understand if we really need this information and where it should + // belong, considering that it should be part of whichever system + // would actually store the file itself. + s_outFileNames << prefix.mid(1) + "/" + resolved_file.get_query(); + + + // TODO: [uncentralized-output-directory-structure] + QString imgOutDir = s_outDir + prefix + "/" + QFileInfo{resolved_file.get_query()}.path(); if (!dirInfo.mkpath(imgOutDir)) en->location().fatal(QStringLiteral("Cannot create output directory '%1'").arg(imgOutDir)); - Config::copyFile(en->location(), srcPath, file, imgOutDir); + Config::copyFile(en->location(), resolved_file.get_path(), QFileInfo{resolved_file.get_query()}.fileName(), imgOutDir); } +// TODO: [multi-purpose-function-with-flag][generate-file-list] +// Avoid the use of a boolean flag to dispatch to the correct +// implementation trough branching. +// We always have to process both images and files, such that we +// should consider to remove the branching altogheter, performing both +// operations in a single call. +// Otherwise, if this turns out to be infeasible, complex or +// possibly-confusing, consider extracting the processing code outside +// the function and provide two higer-level dispathing functions for +// files and images. + /*! This function is called when the documentation for an example is being formatted. It outputs a list of files for the example, which @@ -992,22 +1005,33 @@ void Generator::generateFileList(const ExampleNode *en, CodeMarker *marker, bool text << Atom::ParaLeft << tag << Atom::ParaRight; text << Atom(Atom::ListLeft, openedList.styleString()); - QString path; - for (const auto &file : qAsConst(paths)) { - if (images) { - if (!file.isEmpty()) - addImageToCopy(en, file); - } else { - generateExampleFilePage(en, file, marker); + for (const auto &path : qAsConst(paths)) { + auto maybe_resolved_file{file_resolver.resolve(path)}; + if (!maybe_resolved_file) { + // TODO: [uncentralized-admonition][failed-resolve-file] + QString details = std::transform_reduce( + file_resolver.get_search_directories().cbegin(), + file_resolver.get_search_directories().cend(), + u"Searched directories:"_qs, + std::plus(), + [](const DirectoryPath& directory_path){ return " " + directory_path.value(); } + ); + + en->location().warning(u"(Generator)Cannot find file to quote from: %1"_qs.arg(path), details); + + continue; } + auto file{*maybe_resolved_file}; + if (images) addImageToCopy(en, file); + else generateExampleFilePage(en, file, marker); + openedList.next(); text << Atom(Atom::ListItemNumber, openedList.numberString()) << Atom(Atom::ListItemLeft, openedList.styleString()) << Atom::ParaLeft - << Atom(atomType, file) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << file + << Atom(atomType, file.get_query()) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) << file.get_query() << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) << Atom::ParaRight << Atom(Atom::ListItemRight, openedList.styleString()); - path = file; } text << Atom(Atom::ListRight, openedList.styleString()); if (!paths.isEmpty()) @@ -1589,27 +1613,6 @@ QStringList Generator::getMetadataElements(const Aggregate *inner, const QString return result; } -/*! - Returns a relative path name for an image. - */ -QString Generator::imageFileName(const Node *relative, const QString &fileBase) -{ - QString userFriendlyFilePath; - QString filePath = Config::findFile(relative->doc().location(), s_imageFiles, s_imageDirs, - fileBase, s_imgFileExts[format()], &userFriendlyFilePath); - - if (filePath.isEmpty()) - return QString(); - - QString path = Config::copyFile(relative->doc().location(), filePath, userFriendlyFilePath, - outputDir() + QLatin1String("/images")); - qsizetype images_slash = path.lastIndexOf("images/"); - QString relImagePath; - if (images_slash != -1) - relImagePath = path.mid(images_slash); - return relImagePath; -} - QString Generator::indent(int level, const QString &markedCode) { if (level == 0) @@ -1640,15 +1643,6 @@ void Generator::initialize() s_outputFormats = config.getOutputFormats(); s_redirectDocumentationToDevNull = config.getBool(CONFIG_REDIRECTDOCUMENTATIONTODEVNULL); - s_imageFiles = config.getCanonicalPathList(CONFIG_IMAGES); - s_imageDirs = config.getCanonicalPathList(CONFIG_IMAGEDIRS); - s_exampleDirs = config.getCanonicalPathList(CONFIG_EXAMPLEDIRS); - s_exampleImgExts = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS); - - QString imagesDotFileExtensions = CONFIG_IMAGES + Config::dot + CONFIG_FILEEXTENSIONS; - for (const auto &ext : config.subVars(imagesDotFileExtensions)) - s_imgFileExts[ext] = config.getStringList(imagesDotFileExtensions + Config::dot + ext); - for (auto &g : s_generators) { if (s_outputFormats.contains(g->format())) { s_currentGenerator = g; @@ -1714,12 +1708,39 @@ void Generator::initialize() */ void Generator::copyTemplateFiles(const QString &configVar, const QString &subDir) { + // TODO: [resolving-files-unlinked-to-doc] + // This is another case of resolving files, albeit it doesn't use Doc::resolveFile. + // While it may be left out of a first iteration of the file + // resolution logic, it should later be integrated into it. + // This should come naturally when the output directory logic is + // extracted and copying a file should require a validated + // intermediate format. + // Do note that what is done here is a bit different from the + // resolve file routine that is done for other user-given paths. + // Thas is, the paths will always be absolute and not relative as + // they are resolved from the configuration. + // Ideally, this could be solved in the configuration already, + // together with the other configuration resolution processes that + // do not abide by the same constraints that, for example, snippet + // resolution uses. Config &config = Config::instance(); QStringList files = config.getCanonicalPathList(configVar, Config::Validate); if (!files.isEmpty()) { QDir dirInfo; + // TODO: [uncentralized-output-directory-structure] + // As with other places in the generation pass, the details of + // where something is saved in the output directory are spread + // to whichever part of the generation does the saving. + // It is hence complex to build a model of how an output + // directory looks like, as the knowledge has no specific + // entry point or chain-path that can be followed in full. + // Each of those operations should be centralized in a system + // that uniquely knows what the format of the output-directory + // is and how to perform operations on it. + // Later, move this operation to that centralized system. QString templateDir = s_outDir + QLatin1Char('/') + subDir; if (!dirInfo.exists(templateDir) && !dirInfo.mkdir(templateDir)) { + // TODO: [uncentralized-admonition] config.lastLocation().fatal( QStringLiteral("Cannot create %1 directory '%2'").arg(subDir, templateDir)); } else { @@ -1789,18 +1810,6 @@ void Generator::initializeFormat() } /*! - Appends each directory path in \a moreImageDirs to the - list of image directories. - */ -void Generator::augmentImageDirs(QSet<QString> &moreImageDirs) -{ - if (moreImageDirs.isEmpty()) - return; - for (const auto &it : moreImageDirs) - s_imageDirs.append(it); -} - -/*! Updates the generator's m_showInternal from the Config. */ void Generator::initializeGenerator() @@ -1950,11 +1959,6 @@ QString Generator::plainCode(const QString &markedCode) return t; } -void Generator::setImageFileExtensions(const QStringList &extensions) -{ - s_imgFileExts[format()] = extensions; -} - int Generator::skipAtoms(const Atom *atom, Atom::AtomType type) const { int skipAhead = 0; @@ -2037,11 +2041,33 @@ void Generator::terminate() generator->terminateGenerator(); } + // REMARK: Generators currently, due to recent changes and the + // transitive nature of the current codebase, receive some of + // their dependencies in the constructor and some of them in their + // initialize-terminate lifetime. + // This means that generators need to be constructed and + // destructed between usages such that if multiple usages are + // required, the generators present in the list will have been + // destroyed by then such that accessing them would be an error. + // The current codebase calls initialize and the correspective + // terminate with the same scope as the lifetime of the + // generators. + // Then, clearing the list ensures that, if another generator + // execution is needed, the stale generators will not be removed + // as to be replaced by newly constructed ones. + // Do note that it is not clear that this will happen for any call + // in Qt's documentation and this should work only because of the + // form of the current codebase and the scoping of the + // initialize-terminate calls. As such, this should be considered + // a patchwork that may or may not be doing anything and that may + // break due to changes in other parts of the codebase. + // + // This is still to be considered temporary as the whole + // initialize-terminate idiom must be removed from the codebase. + s_generators.clear(); + s_fmtLeftMaps.clear(); s_fmtRightMaps.clear(); - s_imgFileExts.clear(); - s_imageFiles.clear(); - s_imageDirs.clear(); s_outDir.clear(); } |