Warning: That file was not part of the compilation database. It may have many parsing errors.
1 | /**************************************************************************** |
---|---|
2 | ** |
3 | ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
5 | ** |
6 | ** This file is part of the tools applications of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and Digia. For licensing terms and |
14 | ** conditions see http://qt.digia.com/licensing. For further information |
15 | ** use the contact form at http://qt.digia.com/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 2.1 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 2.1 requirements |
23 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
24 | ** |
25 | ** In addition, as a special exception, Digia gives you certain additional |
26 | ** rights. These rights are described in the Digia Qt LGPL Exception |
27 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
28 | ** |
29 | ** GNU General Public License Usage |
30 | ** Alternatively, this file may be used under the terms of the GNU |
31 | ** General Public License version 3.0 as published by the Free Software |
32 | ** Foundation and appearing in the file LICENSE.GPL included in the |
33 | ** packaging of this file. Please review the following information to |
34 | ** ensure the GNU General Public License version 3.0 requirements will be |
35 | ** met: http://www.gnu.org/copyleft/gpl.html. |
36 | ** |
37 | ** |
38 | ** $QT_END_LICENSE$ |
39 | ** |
40 | ****************************************************************************/ |
41 | |
42 | #include <qfileinfo.h> |
43 | #include <qregexp.h> |
44 | #include <qdebug.h> |
45 | |
46 | #include "quoter.h" |
47 | |
48 | QT_BEGIN_NAMESPACE |
49 | |
50 | static void replaceMultipleNewlines(QString &s) |
51 | { |
52 | const int n = s.size(); |
53 | bool slurping = false; |
54 | int j = -1; |
55 | const QChar newLine = QLatin1Char( |
56 | QChar *d = s.data(); |
57 | for (int i = 0; i != n; ++i) { |
58 | const QChar c = d[i]; |
59 | bool hit = (c == newLine); |
60 | if (slurping && hit) |
61 | continue; |
62 | d[++j] = c; |
63 | slurping = hit; |
64 | } |
65 | s.resize(++j); |
66 | } |
67 | |
68 | // This is equivalent to line.split( QRegExp("\n(?!\n|$)") ) but much faster |
69 | QStringList Quoter::splitLines(const QString &line) |
70 | { |
71 | QStringList result; |
72 | int i = line.size(); |
73 | while (true) { |
74 | int j = i - 1; |
75 | while (j >= 0 && line.at(j) == QLatin1Char( |
76 | --j; |
77 | while (j >= 0 && line.at(j) != QLatin1Char( |
78 | --j; |
79 | result.prepend(line.mid(j + 1, i - j - 1)); |
80 | if (j < 0) |
81 | break; |
82 | i = j; |
83 | } |
84 | return result; |
85 | } |
86 | |
87 | /* |
88 | Transforms 'int x = 3 + 4' into 'int x=3+4'. A white space is kept |
89 | between 'int' and 'x' because it is meaningful in C++. |
90 | */ |
91 | static void trimWhiteSpace( QString& str ) |
92 | { |
93 | enum { Normal, MetAlnum, MetSpace } state = Normal; |
94 | const int n = str.length(); |
95 | |
96 | int j = -1; |
97 | QChar *d = str.data(); |
98 | for ( int i = 0; i != n; ++i ) { |
99 | const QChar c = d[i]; |
100 | if ( c.isLetterOrNumber() ) { |
101 | if ( state == Normal ) { |
102 | state = MetAlnum; |
103 | } else { |
104 | if ( state == MetSpace ) |
105 | str[++j] = c; |
106 | state = Normal; |
107 | } |
108 | str[++j] = c; |
109 | } else if ( c.isSpace() ) { |
110 | if ( state == MetAlnum ) |
111 | state = MetSpace; |
112 | } else { |
113 | state = Normal; |
114 | str[++j] = c; |
115 | } |
116 | } |
117 | str.resize(++j); |
118 | } |
119 | |
120 | Quoter::Quoter() |
121 | : silent( false ) |
122 | { |
123 | /* We're going to hard code these delimiters: |
124 | * C++, Qt, Qt Script, Java: |
125 | //! [<id>] |
126 | * .pro, .py files: |
127 | #! [<id>] |
128 | * .html, .qrc, .ui, .xq, .xml files: |
129 | <!-- [<id>] --> |
130 | */ |
131 | commentHash["pro"] = "#!"; |
132 | commentHash["py"] = "#!"; |
133 | commentHash["html"] = "<!--"; |
134 | commentHash["qrc"] = "<!--"; |
135 | commentHash["ui"] = "<!--"; |
136 | commentHash["xml"] = "<!--"; |
137 | commentHash["xq"] = "<!--"; |
138 | } |
139 | |
140 | void Quoter::reset() |
141 | { |
142 | silent = false; |
143 | plainLines.clear(); |
144 | markedLines.clear(); |
145 | codeLocation = Location::null; |
146 | } |
147 | |
148 | void Quoter::quoteFromFile( const QString& userFriendlyFilePath, |
149 | const QString& plainCode, |
150 | const QString& markedCode ) |
151 | { |
152 | silent = false; |
153 | |
154 | /* |
155 | Split the source code into logical lines. Empty lines are |
156 | treated specially. Before: |
157 | |
158 | p->alpha(); |
159 | p->beta(); |
160 | |
161 | p->gamma(); |
162 | |
163 | |
164 | p->delta(); |
165 | |
166 | After: |
167 | |
168 | p->alpha(); |
169 | p->beta();\n |
170 | p->gamma();\n\n |
171 | p->delta(); |
172 | |
173 | Newlines are preserved because they affect codeLocation. |
174 | */ |
175 | codeLocation = Location( userFriendlyFilePath ); |
176 | |
177 | plainLines = splitLines(plainCode); |
178 | markedLines = splitLines(markedCode); |
179 | if (markedLines.count() != plainLines.count()) { |
180 | codeLocation.warning(tr("Something is wrong with qdoc's handling of marked code")); |
181 | markedLines = plainLines; |
182 | } |
183 | |
184 | /* |
185 | Squeeze blanks (cat -s). |
186 | */ |
187 | QStringList::Iterator m = markedLines.begin(); |
188 | while ( m != markedLines.end() ) { |
189 | replaceMultipleNewlines( *m ); |
190 | ++m; |
191 | } |
192 | codeLocation.start(); |
193 | } |
194 | |
195 | QString Quoter::quoteLine( const Location& docLocation, const QString& command, |
196 | const QString& pattern ) |
197 | { |
198 | if ( plainLines.isEmpty() ) { |
199 | failedAtEnd( docLocation, command ); |
200 | return QString(); |
201 | } |
202 | |
203 | if ( pattern.isEmpty() ) { |
204 | docLocation.warning( tr("Missing pattern after '\\%1'").arg(command) ); |
205 | return QString(); |
206 | } |
207 | |
208 | if ( match(docLocation, pattern, plainLines.first()) ) |
209 | return getLine(); |
210 | |
211 | if ( !silent ) { |
212 | docLocation.warning( tr("Command '\\%1' failed").arg(command) ); |
213 | codeLocation.warning( tr("Pattern '%1' didn't match here") |
214 | .arg(pattern) ); |
215 | silent = true; |
216 | } |
217 | return QString(); |
218 | } |
219 | |
220 | QString Quoter::quoteSnippet(const Location &docLocation, const QString &identifier) |
221 | { |
222 | QString comment = commentForCode(); |
223 | QString delimiter = comment + QString(" [%1]").arg(identifier); |
224 | QString t; |
225 | int indent = 0; |
226 | |
227 | while (!plainLines.isEmpty()) { |
228 | if (match(docLocation, delimiter, plainLines.first())) { |
229 | QString startLine = getLine(); |
230 | while (indent < startLine.length() && startLine[indent] == QLatin1Char( |
231 | indent++; |
232 | break; |
233 | } |
234 | getLine(); |
235 | } |
236 | while (!plainLines.isEmpty()) { |
237 | QString line = plainLines.first(); |
238 | if (match(docLocation, delimiter, line)) { |
239 | QString lastLine = getLine(indent); |
240 | int dIndex = lastLine.indexOf(delimiter); |
241 | if (dIndex > 0) { |
242 | // The delimiter might be preceded on the line by other |
243 | // delimeters, so look for the first comment on the line. |
244 | QString leading = lastLine.left(dIndex); |
245 | dIndex = leading.indexOf(comment); |
246 | if (dIndex != -1) |
247 | leading = leading.left(dIndex); |
248 | if (leading.endsWith(QLatin1String("<@comment>"))) |
249 | leading.chop(10); |
250 | if (!leading.trimmed().isEmpty()) |
251 | t += leading; |
252 | } |
253 | return t; |
254 | } |
255 | |
256 | t += removeSpecialLines(line, comment, indent); |
257 | } |
258 | failedAtEnd(docLocation, QString("snippet (%1)").arg(delimiter)); |
259 | return t; |
260 | } |
261 | |
262 | QString Quoter::quoteTo( const Location& docLocation, const QString& command, |
263 | const QString& pattern ) |
264 | { |
265 | QString t; |
266 | QString comment = commentForCode(); |
267 | |
268 | if ( pattern.isEmpty() ) { |
269 | while ( !plainLines.isEmpty() ) { |
270 | QString line = plainLines.first(); |
271 | t += removeSpecialLines(line, comment); |
272 | } |
273 | } else { |
274 | while ( !plainLines.isEmpty() ) { |
275 | if ( match(docLocation, pattern, plainLines.first()) ) { |
276 | return t; |
277 | } |
278 | t += getLine(); |
279 | } |
280 | failedAtEnd( docLocation, command ); |
281 | } |
282 | return t; |
283 | } |
284 | |
285 | QString Quoter::quoteUntil( const Location& docLocation, const QString& command, |
286 | const QString& pattern ) |
287 | { |
288 | QString t = quoteTo( docLocation, command, pattern ); |
289 | t += getLine(); |
290 | return t; |
291 | } |
292 | |
293 | QString Quoter::getLine(int unindent) |
294 | { |
295 | if ( plainLines.isEmpty() ) |
296 | return QString(); |
297 | |
298 | plainLines.removeFirst(); |
299 | |
300 | QString t = markedLines.takeFirst(); |
301 | int i = 0; |
302 | while (i < unindent && i < t.length() && t[i] == QLatin1Char( |
303 | i++; |
304 | |
305 | t = t.mid(i); |
306 | t += QLatin1Char( |
307 | codeLocation.advanceLines( t.count( QLatin1Char( |
308 | return t; |
309 | } |
310 | |
311 | bool Quoter::match( const Location& docLocation, const QString& pattern0, |
312 | const QString& line ) |
313 | { |
314 | QString str = line; |
315 | while ( str.endsWith(QLatin1Char( |
316 | str.truncate( str.length() - 1 ); |
317 | |
318 | QString pattern = pattern0; |
319 | if ( pattern.startsWith(QLatin1Char( |
320 | && pattern.endsWith(QLatin1Char( |
321 | && pattern.length() > 2 ) { |
322 | QRegExp rx( pattern.mid(1, pattern.length() - 2) ); |
323 | if ( !silent && !rx.isValid() ) { |
324 | docLocation.warning( tr("Invalid regular expression '%1'") |
325 | .arg(rx.pattern()) ); |
326 | silent = true; |
327 | } |
328 | return str.indexOf( rx ) != -1; |
329 | } |
330 | trimWhiteSpace(str); |
331 | trimWhiteSpace(pattern); |
332 | return str.indexOf(pattern) != -1; |
333 | } |
334 | |
335 | void Quoter::failedAtEnd( const Location& docLocation, const QString& command ) |
336 | { |
337 | if (!silent && !command.isEmpty()) { |
338 | if ( codeLocation.filePath().isEmpty() ) { |
339 | docLocation.warning( tr("Unexpected '\\%1'").arg(command) ); |
340 | } else { |
341 | docLocation.warning( tr("Command '\\%1' failed at end of file '%2'") |
342 | .arg(command).arg(codeLocation.filePath()) ); |
343 | } |
344 | silent = true; |
345 | } |
346 | } |
347 | |
348 | QString Quoter::commentForCode() const |
349 | { |
350 | QString suffix = QFileInfo(codeLocation.fileName()).suffix(); |
351 | return commentHash.value(suffix, "//!"); |
352 | } |
353 | |
354 | QString Quoter::removeSpecialLines(const QString &line, const QString &comment, int unindent) |
355 | { |
356 | QString t; |
357 | |
358 | // Remove special macros to support Qt namespacing. |
359 | QString trimmed = line.trimmed(); |
360 | if (trimmed.startsWith("QT_BEGIN_NAMESPACE")) { |
361 | getLine(); |
362 | } else if (trimmed.startsWith("QT_END_NAMESPACE")) { |
363 | getLine(); |
364 | t += QLatin1Char( |
365 | } else if (!trimmed.startsWith(comment)) { |
366 | // Ordinary code |
367 | t += getLine(unindent); |
368 | } else { |
369 | // Comments |
370 | if (line.contains(QLatin1Char( |
371 | t += QLatin1Char( |
372 | getLine(); |
373 | } |
374 | return t; |
375 | } |
376 | |
377 | QT_END_NAMESPACE |
378 |
Warning: That file was not part of the compilation database. It may have many parsing errors.