Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
utilities.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "utilities.h"
5
6#include "inode.h"
7#include "location.h"
8
9#include <QtCore/qcryptographichash.h>
10#include <QtCore/qfileinfo.h>
11#include <QtCore/qprocess.h>
12
14
15Q_LOGGING_CATEGORY(lcQdoc, "qt.qdoc")
16Q_LOGGING_CATEGORY(lcQdocClang, "qt.qdoc.clang")
17
18/*!
19 \namespace Utilities
20 \internal
21 \brief This namespace holds QDoc-internal utility methods.
22 */
23namespace Utilities {
24static inline void setDebugEnabled(bool value)
25{
26 const_cast<QLoggingCategory &>(lcQdoc()).setEnabled(QtDebugMsg, value);
27 const_cast<QLoggingCategory &>(lcQdocClang()).setEnabled(QtDebugMsg, value);
28}
29
30void startDebugging(const QString &message)
31{
33 qCDebug(lcQdoc, "START DEBUGGING: %ls", qUtf16Printable(message));
34}
35
36void stopDebugging(const QString &message)
37{
38 qCDebug(lcQdoc, "STOP DEBUGGING: %ls", qUtf16Printable(message));
40}
41
43{
44 return lcQdoc().isEnabled(QtDebugMsg);
45}
46
47/*!
48 \brief Converts a string representation of a pointer address to an INode pointer.
49
50 This function takes a \a string, assumed to contain the numerical
51 representation of an INode pointer's address (as generated by
52 stringForNode()), and casts it back to an \c INode pointer.
53
54 \sa stringForNode()
55 */
56const INode *nodeForString(const QString &string)
57{
58 return reinterpret_cast<const INode *>(string.toULongLong());
59}
60/*!
61 \brief Converts an INode pointer address to its string representation.
62
63 This function takes a \a node pointer and returns a string that contains the
64 numerical value of its memory address. This is used for serialization or
65 passing node references where a direct pointer cannot be used.
66
67 \note The returned string is only valid within the same process. It does
68 \e {not} persist across runs.
69 */
71{
72 return QString::number(reinterpret_cast<quintptr>(node));
73}
74
75/*!
76 Returns a unique identifier based on location \a loc, with a
77 given \a prefix.
78*/
79QString uniqueIdentifier(const Location &loc, const QString &prefix)
80{
81 Q_ASSERT(!loc.filePath().isEmpty());
82 QFileInfo fi{loc.filePath()};
83 const auto id = QLatin1String("%1_%2_%3").arg(prefix, fi.fileName(), QString::number(loc.lineNo()));
84 return asAsciiPrintable(id);
85}
86
87
88/*!
89 \internal
90 Convenience method that's used to get the correct punctuation character for
91 the words at \a wordPosition in a list of \a numberOfWords length.
92 For the last position in the list, returns "." (full stop). For any other
93 word, this method calls comma().
94
95 \sa comma()
96 */
97QString separator(qsizetype wordPosition, qsizetype numberOfWords)
98{
99 static QString terminator = QStringLiteral(".");
100 if (wordPosition == numberOfWords - 1)
101 return terminator;
102 else
103 return comma(wordPosition, numberOfWords);
104}
105
106/*!
107 \internal
108 Convenience method that's used to get the correct punctuation character for
109 the words at \a wordPosition in a list of \a numberOfWords length.
110
111 For a list of length one, returns an empty QString. For a list of length
112 two, returns the string " and ". For any length beyond two, returns the
113 string ", " until the last element, which returns ", and ".
114
115 \sa comma()
116 */
117QString comma(qsizetype wordPosition, qsizetype numberOfWords)
118{
119 if (wordPosition == numberOfWords - 1)
120 return QString();
121 if (numberOfWords == 2)
122 return QStringLiteral(" and ");
123 if (wordPosition == 0 || wordPosition < numberOfWords - 2)
124 return QStringLiteral(", ");
125 return QStringLiteral(", and ");
126}
127
128/*!
129 \brief Returns an ascii-printable representation of \a str.
130
131 Replace non-ascii-printable characters in \a str from a subset of such
132 characters. The subset includes alphanumeric (alnum) characters
133 ([a-zA-Z0-9]), space, punctuation characters, and common symbols. Non-alnum
134 characters in this subset are replaced by a single hyphen. Leading,
135 trailing, and consecutive hyphens are removed, such that the resulting
136 string does not start or end with a hyphen. All characters are converted to
137 lowercase.
138
139 If any character in \a str is non-latin, or latin and not found in the
140 aforementioned subset (e.g. 'ß', 'å', or 'ö'), a hash of \a str is appended
141 to the final string.
142
143 Returns a string that is normalized for use where ascii-printable strings
144 are required, such as file names or fragment identifiers in URLs.
145
146 The implementation is equivalent to:
147
148 \code
149 name.replace(QRegularExpression("[^A-Za-z0-9]+"), " ");
150 name = name.simplified();
151 name.replace(QLatin1Char(' '), QLatin1Char('-'));
152 name = name.toLower();
153 \endcode
154
155 However, it has been measured to be approximately four times faster.
156*/
157QString asAsciiPrintable(const QString &str)
158{
159 auto legal_ascii = [](const uint value) {
160 const uint start_ascii_subset{ 32 };
161 const uint end_ascii_subset{ 126 };
162
163 return value >= start_ascii_subset && value <= end_ascii_subset;
164 };
165
166 QString result;
167 bool begun = false;
168 bool has_non_alnum_content{ false };
169
170 for (const auto &c : str) {
171 char16_t u = c.unicode();
172 if (!legal_ascii(u))
173 has_non_alnum_content = true;
174 if (u >= 'A' && u <= 'Z')
175 u += 'a' - 'A';
176 if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) {
177 result += QLatin1Char(u);
178 begun = true;
179 } else if (begun) {
180 result += QLatin1Char('-');
181 begun = false;
182 }
183 }
184 if (result.endsWith(QLatin1Char('-')))
185 result.chop(1);
186
187 if (has_non_alnum_content) {
188 auto title_hash = QString::fromLocal8Bit(
189 QCryptographicHash::hash(str.toUtf8(), QCryptographicHash::Md5).toHex());
190 title_hash.truncate(8);
191 if (!result.isEmpty())
192 result.append(QLatin1Char('-'));
193 result.append(title_hash);
194 }
195
196 return result;
197}
198
199QString protect(const QString &str)
200{
201 qsizetype n = str.size();
202 QString marked;
203 marked.reserve(n * 2 + 30);
204 const QChar *data = str.constData();
205 for (int i = 0; i != n; ++i) {
206 switch (data[i].unicode()) {
207 case '&':
208 marked += samp;
209 break;
210 case '<':
211 marked += slt;
212 break;
213 case '>':
214 marked += sgt;
215 break;
216 case '"':
217 marked += squot;
218 break;
219 default:
220 marked += data[i];
221 }
222 }
223 return marked;
224}
225
226/*!
227 \internal
228*/
229static bool runProcess(const QString &program, const QStringList &arguments,
230 QByteArray *stdOutIn, QByteArray *stdErrIn)
231{
232 QProcess process;
233 process.start(program, arguments, QProcess::ReadWrite);
234 if (!process.waitForStarted()) {
235 qCDebug(lcQdoc).nospace() << "Unable to start " << process.program()
236 << ": " << process.errorString();
237 return false;
238 }
239 process.closeWriteChannel();
240 const bool finished = process.waitForFinished();
241 const QByteArray stdErr = process.readAllStandardError();
242 if (stdErrIn)
243 *stdErrIn = stdErr;
244 if (stdOutIn)
245 *stdOutIn = process.readAllStandardOutput();
246
247 if (!finished) {
248 qCDebug(lcQdoc).nospace() << process.program() << " timed out: " << stdErr;
249 process.kill();
250 return false;
251 }
252
253 if (process.exitStatus() != QProcess::NormalExit) {
254 qCDebug(lcQdoc).nospace() << process.program() << " crashed: " << stdErr;
255 return false;
256 }
257
258 if (process.exitCode() != 0) {
259 qCDebug(lcQdoc).nospace() << process.program() << " exited with "
260 << process.exitCode() << ": " << stdErr;
261 return false;
262 }
263
264 return true;
265}
266
267/*!
268 \internal
269*/
271 return QByteArrayLiteral(" (framework directory)");
272}
273
274/*!
275 \internal
276 Determine the compiler's internal include paths from the output of
277
278 \badcode
279 [clang++|g++] -E -x c++ - -v </dev/null
280 \endcode
281
282 Output looks like:
283
284 \badcode
285 #include <...> search starts here:
286 /usr/local/include
287 /System/Library/Frameworks (framework directory)
288 End of search list.
289 \endcode
290*/
291QStringList getInternalIncludePaths(const QString &compiler)
292{
293 QStringList result;
294 QStringList arguments;
295 arguments << QStringLiteral("-E") << QStringLiteral("-x") << QStringLiteral("c++")
296 << QStringLiteral("-") << QStringLiteral("-v");
297 QByteArray stdOut;
298 QByteArray stdErr;
299 if (!runProcess(compiler, arguments, &stdOut, &stdErr))
300 return result;
301 const QByteArrayList stdErrLines = stdErr.split('\n');
302 bool isIncludeDir = false;
303 for (const QByteArray &line : stdErrLines) {
304 if (isIncludeDir) {
305 if (line.startsWith(QByteArrayLiteral("End of search list"))) {
306 isIncludeDir = false;
307 } else {
308 QByteArray prefix("-I");
309 QByteArray headerPath{line.trimmed()};
310 if (headerPath.endsWith(frameworkSuffix())) {
311 headerPath.truncate(headerPath.size() - frameworkSuffix().size());
312 prefix = QByteArrayLiteral("-F");
313 }
314 result.append(QString::fromLocal8Bit(prefix + headerPath));
315 }
316 } else if (line.startsWith(QByteArrayLiteral("#include <...> search starts here"))) {
317 isIncludeDir = true;
318 }
319 }
320
321 return result;
322}
323
324bool isGeneratedFile(const QString &path)
325{
326 QString fileName = QFileInfo(path).fileName();
327 return fileName.startsWith("moc_") ||
328 fileName.startsWith("qrc_") ||
329 fileName.startsWith("ui_");
330}
331
332/*!
333 Returns a string list containing the path and fragment components of the
334 given \a linkText. Fragments begin with a "#" character, following HTML
335 conventions.
336
337 Links with only a path or only a fragment will result in lists of one
338 element. A link with both path and fragment will result in a list of two
339 elements. Additional fragments are discarded.
340
341 Escaped "#" characters ("\\#") are interpreted differently to regular "#"
342 characters. These can be used to represent "#" characters in titles, but
343 not in fragments.
344*/
345QStringList pathAndFragment(const QString &linkText)
346{
347 QStringList pieces = linkText.split("\\#"_L1);
348 QStringList result;
349 QString next;
350
351 for (auto &piece : pieces) {
352 QStringList fragmentPieces = piece.split('#');
353 next += fragmentPieces.takeFirst();
354 if (!fragmentPieces.isEmpty()) {
355 result.append(next);
356 next = fragmentPieces.first();
357 }
358 if (result.count() == 2)
359 break;
360 }
361 if (!next.isEmpty() && result.count() < 2)
362 result.append(next);
363
364 return result;
365}
366
367/*!
368 Constructs an href link from an example file name.
369
370 The \a path is the path to the example file. The \a project is the
371 module name (used as prefix). The \a fileExt is the file extension
372 for the generated link.
373 */
374QString linkForExampleFile(const QString &path, const QString &project, const QString &fileExt)
375{
376 QString link = project.toLower() + QLatin1Char('-') + path;
377 return asAsciiPrintable(link) + QLatin1Char('.') + fileExt;
378}
379
380/*!
381 Constructs a title for a file or image page in an example.
382
383 Returns the base name of \a fileName with the appropriate suffix
384 based on \a kind: " Example File" for ExampleFileKind::File,
385 " Image File" for ExampleFileKind::Image.
386
387 Use this overload when you already know whether the file is an
388 example file or image (e.g., when iterating separate file/image lists).
389 */
390QString exampleFileTitle(const QString &fileName, ExampleFileKind kind)
391{
392 const QString suffix = [kind]() {
393 switch (kind) {
394 case ExampleFileKind::File:
395 return " Example File"_L1;
396 case ExampleFileKind::Image:
397 return " Image File"_L1;
398 }
399 Q_UNREACHABLE();
400 }();
401 return QFileInfo(fileName).fileName() + suffix;
402}
403
404/*!
405 Constructs a title for a file or image page in an example.
406
407 If \a fileName is found in \a files, returns the base name with
408 " Example File" suffix. If found in \a images, returns the base
409 name with " Image File" suffix. If not found in either list,
410 returns an empty string.
411
412 Matching uses exact string equality, not basename comparison.
413
414 \note This overload performs O(n) membership checks. When iterating
415 known file or image lists, prefer the overload taking ExampleFileKind.
416 */
417QString exampleFileTitle(const QStringList &files, const QStringList &images, const QString &fileName)
418{
419 if (files.contains(fileName))
420 return exampleFileTitle(fileName, ExampleFileKind::File);
421 if (images.contains(fileName))
422 return exampleFileTitle(fileName, ExampleFileKind::Image);
423 return {};
424}
425
426} // namespace Utilities
427
428QT_END_NAMESPACE
Definition inode.h:20
The Location class provides a way to mark a location in a file.
Definition location.h:20
Combined button and popup list for selecting options.
This namespace holds QDoc-internal utility methods.
Definition utilities.h:20
QString uniqueIdentifier(const Location &loc, const QString &prefix)
Returns a unique identifier based on location loc, with a given prefix.
Definition utilities.cpp:79
QString exampleFileTitle(const QStringList &files, const QStringList &images, const QString &fileName)
Constructs a title for a file or image page in an example.
static void setDebugEnabled(bool value)
Definition utilities.cpp:24
QStringList getInternalIncludePaths(const QString &compiler)
QString exampleFileTitle(const QString &fileName, ExampleFileKind kind)
Constructs a title for a file or image page in an example.
bool debugging()
Definition utilities.cpp:42
bool isGeneratedFile(const QString &path)
QString asAsciiPrintable(const QString &name)
Returns an ascii-printable representation of str.
QString protect(const QString &string)
const INode * nodeForString(const QString &string)
Converts a string representation of a pointer address to an INode pointer.
Definition utilities.cpp:56
void stopDebugging(const QString &message)
Definition utilities.cpp:36
static QByteArray frameworkSuffix()
QStringList pathAndFragment(const QString &linkText)
Returns a string list containing the path and fragment components of the given linkText.
QString comma(qsizetype wordPosition, qsizetype numberOfWords)
QString separator(qsizetype wordPosition, qsizetype numberOfWords)
Definition utilities.cpp:97
void startDebugging(const QString &message)
Definition utilities.cpp:30
static bool runProcess(const QString &program, const QStringList &arguments, QByteArray *stdOutIn, QByteArray *stdErrIn)
QString linkForExampleFile(const QString &path, const QString &project, const QString &fileExt)
Constructs an href link from an example file name.
QString stringForNode(const INode *node)
Converts an INode pointer address to its string representation.
Definition utilities.cpp:70