6#include <QtCore/qdebug.h>
7#include <QtCore/qfileinfo.h>
8#include <QtCore/qregularexpression.h>
12using namespace Qt::StringLiterals;
14QHash<QString, QString> Quoter::s_commentHash;
18 const qsizetype n = s.size();
19 bool slurping =
false;
21 const QChar newLine = QLatin1Char(
'\n');
23 for (
int i = 0; i != n; ++i) {
25 bool hit = (c == newLine);
35QStringList Quoter::splitLines(
const QString &line)
38 qsizetype i = line.size();
41 while (j >= 0 && line.at(j) == QLatin1Char(
'\n'))
43 while (j >= 0 && line.at(j) != QLatin1Char(
'\n'))
45 result.prepend(line.mid(j + 1, i - j - 1));
54
55
56
59 enum { Normal, MetAlnum, MetSpace } state = Normal;
60 const qsizetype n = str.size();
63 QChar *d = str.data();
64 for (
int i = 0; i != n; ++i) {
66 if (c.isLetterOrNumber()) {
67 if (state == Normal) {
70 if (state == MetSpace)
75 }
else if (c.isSpace()) {
76 if (state == MetAlnum)
86Quoter::Quoter() : m_silent(
false)
89
90
91
92
93
94
95
96 if (s_commentHash.empty()) {
97 s_commentHash[
"pro"] =
"#!";
98 s_commentHash[
"py"] =
"#!";
99 s_commentHash[
"cmake"] =
"#!";
100 s_commentHash[
"html"] =
"<!--";
101 s_commentHash[
"qrc"] =
"<!--";
102 s_commentHash[
"ui"] =
"<!--";
103 s_commentHash[
"xml"] =
"<!--";
104 s_commentHash[
"xq"] =
"<!--";
111 m_plainLines.clear();
112 m_markedLines.clear();
113 m_codeLocation = Location();
116void Quoter::quoteFromFile(
const QString &userFriendlyFilePath,
const QString &plainCode,
117 const QString &markedCode)
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142 m_codeLocation = Location(userFriendlyFilePath);
144 m_plainLines = splitLines(plainCode);
145 m_markedLines = splitLines(markedCode);
146 if (m_markedLines.size() != m_plainLines.size()) {
147 m_codeLocation.warning(
148 QStringLiteral(
"Something is wrong with qdoc's handling of marked code"));
149 m_markedLines = m_plainLines;
153
154
155 for (
auto &line : m_markedLines)
156 replaceMultipleNewlines(line);
157 m_codeLocation.start();
160QString Quoter::quoteLine(
const Location &docLocation,
const QString &command,
161 const QString &pattern)
163 if (m_plainLines.isEmpty()) {
164 failedAtEnd(docLocation, command);
168 if (pattern.isEmpty()) {
169 docLocation.warning(QStringLiteral(
"Missing pattern after '\\%1'").arg(command));
173 if (match(docLocation, pattern, m_plainLines.first()))
177 docLocation.warning(QStringLiteral(
"Command '\\%1' failed").arg(command));
178 m_codeLocation.warning(QStringLiteral(
"Pattern '%1' didn't match here").arg(pattern));
185
186
187
188int Quoter::calculateIndentation(
const QString &line)
const
191 while (indent < line.size() && line[indent] ==
' '_L1)
196Quoter::SnippetIndentation Quoter::analyzeContentIndentation(
const Location &docLocation,
const QString &delimiter)
198 SnippetIndentation result;
199 const QString comment = commentForCode();
201 for (
const QString &line : m_plainLines) {
202 if (match(docLocation, delimiter, line))
205 const QString trimmed = line.trimmed();
206 if (trimmed.isEmpty() ||
207 trimmed.startsWith(
"QT_BEGIN_NAMESPACE"_L1) ||
208 trimmed.startsWith(
"QT_END_NAMESPACE"_L1) ||
209 trimmed.startsWith(comment)) {
213 result.hasNonEmptyContent =
true;
214 result.minContentIndent = qMin(result.minContentIndent, calculateIndentation(line));
220QString Quoter::quoteSnippet(
const Location &docLocation,
const QString &identifier)
222 QString comment = commentForCode();
223 QString delimiter = comment + QString(
" [%1]").arg(identifier);
224 QString snippetContent;
225 int markerIndent = 0;
228 while (!m_plainLines.isEmpty()) {
229 if (match(docLocation, delimiter, m_plainLines.first())) {
230 markerIndent = calculateIndentation(m_plainLines.first());
237 const auto indentationInfo = analyzeContentIndentation(docLocation, delimiter);
239 indentationInfo.hasNonEmptyContent
240 ? qMin(markerIndent, indentationInfo.minContentIndent)
243 while (!m_plainLines.isEmpty()) {
244 QString line = m_plainLines.first();
245 if (match(docLocation, delimiter, line)) {
246 QString lastLine = getLine(unindent);
247 qsizetype dIndex = lastLine.indexOf(delimiter);
251 QString leading = lastLine.left(dIndex);
252 dIndex = leading.indexOf(comment);
254 leading = leading.left(dIndex);
255 if (leading.endsWith(QLatin1String(
"<@comment>")))
257 if (!leading.trimmed().isEmpty())
258 snippetContent += leading;
260 return snippetContent;
262 snippetContent += removeSpecialLines(line, comment, unindent);
265 failedAtEnd(docLocation, QString(
"snippet (%1)").arg(delimiter));
266 return snippetContent;
269QString Quoter::quoteTo(
const Location &docLocation,
const QString &command,
const QString &pattern)
272 QString comment = commentForCode();
274 if (pattern.isEmpty()) {
275 while (!m_plainLines.isEmpty()) {
276 QString line = m_plainLines.first();
277 t += removeSpecialLines(line, comment);
280 while (!m_plainLines.isEmpty()) {
281 if (match(docLocation, pattern, m_plainLines.first())) {
286 failedAtEnd(docLocation, command);
291QString Quoter::quoteUntil(
const Location &docLocation,
const QString &command,
292 const QString &pattern)
294 QString t = quoteTo(docLocation, command, pattern);
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319QString Quoter::getLine(
int unindent)
321 if (m_plainLines.isEmpty())
324 m_plainLines.removeFirst();
326 QString t = m_markedLines.takeFirst();
328 while (i < unindent && i < t.size() && t[i] == QLatin1Char(
' '))
332 t += QLatin1Char(
'\n');
333 m_codeLocation.advanceLines(t.count(QLatin1Char(
'\n')));
337bool Quoter::match(
const Location &docLocation,
const QString &pattern0,
const QString &line)
340 while (str.endsWith(QLatin1Char(
'\n')))
341 str.truncate(str.size() - 1);
343 QString pattern = pattern0;
344 if (pattern.startsWith(QLatin1Char(
'/')) && pattern.endsWith(QLatin1Char(
'/'))
345 && pattern.size() > 2) {
346 QRegularExpression rx(pattern.mid(1, pattern.size() - 2));
347 if (!m_silent && !rx.isValid()) {
349 QStringLiteral(
"Invalid regular expression '%1'").arg(rx.pattern()));
352 return str.indexOf(rx) != -1;
355 trimWhiteSpace(pattern);
356 return str.indexOf(pattern) != -1;
359void Quoter::failedAtEnd(
const Location &docLocation,
const QString &command)
361 if (!m_silent && !command.isEmpty()) {
362 if (m_codeLocation.filePath().isEmpty()) {
363 docLocation.warning(QStringLiteral(
"Unexpected '\\%1'").arg(command));
365 docLocation.warning(QStringLiteral(
"Command '\\%1' failed at end of file '%2'")
366 .arg(command, m_codeLocation.filePath()));
372QString Quoter::commentForCode()
const
374 QFileInfo fi = QFileInfo(m_codeLocation.fileName());
375 if (fi.fileName() ==
"CMakeLists.txt")
377 return s_commentHash.value(fi.suffix(),
"//!");
380QString Quoter::removeSpecialLines(
const QString &line,
const QString &comment,
int unindent)
385 QString trimmed = line.trimmed();
386 if (trimmed.startsWith(
"QT_BEGIN_NAMESPACE")) {
388 }
else if (trimmed.startsWith(
"QT_END_NAMESPACE")) {
390 t += QLatin1Char(
'\n');
391 }
else if (!trimmed.startsWith(comment)) {
393 t += getLine(unindent);
396 if (line.contains(QLatin1Char(
'\n')))
397 t += QLatin1Char(
'\n');
The Location class provides a way to mark a location in a file.
static void replaceMultipleNewlines(QString &s)
static void trimWhiteSpace(QString &str)