|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the tools applications of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 /* |
|
43 config.cpp |
|
44 */ |
|
45 |
|
46 #include <QtCore> |
|
47 |
|
48 #include "archiveextractor.h" |
|
49 #include "config.h" |
|
50 #include "uncompressor.h" |
|
51 #include <stdlib.h> |
|
52 |
|
53 QT_BEGIN_NAMESPACE |
|
54 |
|
55 /* |
|
56 An entry on the MetaStack. |
|
57 */ |
|
58 class MetaStackEntry |
|
59 { |
|
60 public: |
|
61 void open(); |
|
62 void close(); |
|
63 |
|
64 QStringList accum; |
|
65 QStringList next; |
|
66 }; |
|
67 |
|
68 /* |
|
69 */ |
|
70 void MetaStackEntry::open() |
|
71 { |
|
72 next.append(QString()); |
|
73 } |
|
74 |
|
75 /* |
|
76 */ |
|
77 void MetaStackEntry::close() |
|
78 { |
|
79 accum += next; |
|
80 next.clear(); |
|
81 } |
|
82 |
|
83 /* |
|
84 ### |
|
85 */ |
|
86 class MetaStack : private QStack<MetaStackEntry> |
|
87 { |
|
88 public: |
|
89 MetaStack(); |
|
90 |
|
91 void process(QChar ch, const Location& location); |
|
92 QStringList getExpanded(const Location& location); |
|
93 }; |
|
94 |
|
95 MetaStack::MetaStack() |
|
96 { |
|
97 push(MetaStackEntry()); |
|
98 top().open(); |
|
99 } |
|
100 |
|
101 void MetaStack::process(QChar ch, const Location& location) |
|
102 { |
|
103 if (ch == QLatin1Char('{')) { |
|
104 push(MetaStackEntry()); |
|
105 top().open(); |
|
106 } |
|
107 else if (ch == QLatin1Char('}')) { |
|
108 if (count() == 1) |
|
109 location.fatal(tr("Unexpected '}'")); |
|
110 |
|
111 top().close(); |
|
112 QStringList suffixes = pop().accum; |
|
113 QStringList prefixes = top().next; |
|
114 |
|
115 top().next.clear(); |
|
116 QStringList::ConstIterator pre = prefixes.begin(); |
|
117 while (pre != prefixes.end()) { |
|
118 QStringList::ConstIterator suf = suffixes.begin(); |
|
119 while (suf != suffixes.end()) { |
|
120 top().next << (*pre + *suf); |
|
121 ++suf; |
|
122 } |
|
123 ++pre; |
|
124 } |
|
125 } |
|
126 else if (ch == QLatin1Char(',') && count() > 1) { |
|
127 top().close(); |
|
128 top().open(); |
|
129 } |
|
130 else { |
|
131 QStringList::Iterator pre = top().next.begin(); |
|
132 while (pre != top().next.end()) { |
|
133 *pre += ch; |
|
134 ++pre; |
|
135 } |
|
136 } |
|
137 } |
|
138 |
|
139 QStringList MetaStack::getExpanded(const Location& location) |
|
140 { |
|
141 if (count() > 1) |
|
142 location.fatal(tr("Missing '}'")); |
|
143 |
|
144 top().close(); |
|
145 return top().accum; |
|
146 } |
|
147 |
|
148 QT_STATIC_CONST_IMPL QString Config::dot = QLatin1String("."); |
|
149 QMap<QString, QString> Config::uncompressedFiles; |
|
150 QMap<QString, QString> Config::extractedDirs; |
|
151 int Config::numInstances; |
|
152 |
|
153 /*! |
|
154 \class Config |
|
155 \brief The Config class contains the configuration variables |
|
156 for controlling how qdoc produces documentation. |
|
157 |
|
158 Its load() function, reads, parses, and processes a qdocconf file. |
|
159 */ |
|
160 |
|
161 /*! |
|
162 The constructor sets the \a programName and initializes all |
|
163 internal state variables to empty values. |
|
164 */ |
|
165 Config::Config(const QString& programName) |
|
166 : prog(programName) |
|
167 { |
|
168 loc = Location::null; |
|
169 lastLoc = Location::null; |
|
170 locMap.clear(); |
|
171 stringValueMap.clear(); |
|
172 stringListValueMap.clear(); |
|
173 numInstances++; |
|
174 } |
|
175 |
|
176 /*! |
|
177 The destructor deletes all the temporary files and |
|
178 directories it built. |
|
179 */ |
|
180 Config::~Config() |
|
181 { |
|
182 if (--numInstances == 0) { |
|
183 QMap<QString, QString>::ConstIterator f = uncompressedFiles.begin(); |
|
184 while (f != uncompressedFiles.end()) { |
|
185 QDir().remove(*f); |
|
186 ++f; |
|
187 } |
|
188 uncompressedFiles.clear(); |
|
189 |
|
190 QMap<QString, QString>::ConstIterator d = extractedDirs.begin(); |
|
191 while (d != extractedDirs.end()) { |
|
192 removeDirContents(*d); |
|
193 QDir dir(*d); |
|
194 QString name = dir.dirName(); |
|
195 dir.cdUp(); |
|
196 dir.rmdir(name); |
|
197 ++d; |
|
198 } |
|
199 extractedDirs.clear(); |
|
200 } |
|
201 } |
|
202 |
|
203 /*! |
|
204 Loads and parses the qdoc configuration file \a fileName. |
|
205 This function calls the other load() function, which does |
|
206 the loading, parsing, and processing of the configuration |
|
207 file. |
|
208 |
|
209 Intializes the location variables returned by location() |
|
210 and lastLocation(). |
|
211 */ |
|
212 void Config::load(const QString& fileName) |
|
213 { |
|
214 load(Location::null, fileName); |
|
215 if (loc.isEmpty()) { |
|
216 loc = Location(fileName); |
|
217 } |
|
218 else { |
|
219 loc.setEtc(true); |
|
220 } |
|
221 lastLoc = Location::null; |
|
222 } |
|
223 |
|
224 /*! |
|
225 Joins all the strings in \a values into a single string with the |
|
226 individual \a values separated by ' '. Then it inserts the result |
|
227 into the string list map with \a var as the key. |
|
228 |
|
229 It also inserts the \a values string list into a separate map, |
|
230 also with \a var as the key. |
|
231 */ |
|
232 void Config::setStringList(const QString& var, const QStringList& values) |
|
233 { |
|
234 stringValueMap[var] = values.join(QLatin1String(" ")); |
|
235 stringListValueMap[var] = values; |
|
236 } |
|
237 |
|
238 /*! |
|
239 Looks up the configuarion variable \a var in the string |
|
240 map and returns the boolean value. |
|
241 */ |
|
242 bool Config::getBool(const QString& var) const |
|
243 { |
|
244 return QVariant(getString(var)).toBool(); |
|
245 } |
|
246 |
|
247 /*! |
|
248 Looks up the configuration variable \a var in the string list |
|
249 map. Iterates through the string list found, interpreting each |
|
250 string in the list as an integer and adding it to a total sum. |
|
251 Returns the sum. |
|
252 */ |
|
253 int Config::getInt(const QString& var) const |
|
254 { |
|
255 QStringList strs = getStringList(var); |
|
256 QStringList::ConstIterator s = strs.begin(); |
|
257 int sum = 0; |
|
258 |
|
259 while (s != strs.end()) { |
|
260 sum += (*s).toInt(); |
|
261 ++s; |
|
262 } |
|
263 return sum; |
|
264 } |
|
265 |
|
266 /*! |
|
267 First, this function looks up the configuration variable \a var |
|
268 in the location map and, if found, sets the internal variable |
|
269 \c{lastLoc} to the Location that \a var maps to. |
|
270 |
|
271 Then it looks up the configuration variable \a var in the string |
|
272 map, and returns the string that \a var maps to. |
|
273 */ |
|
274 QString Config::getString(const QString& var) const |
|
275 { |
|
276 if (!locMap[var].isEmpty()) |
|
277 (Location&) lastLoc = locMap[var]; |
|
278 return stringValueMap[var]; |
|
279 } |
|
280 |
|
281 /*! |
|
282 Looks up the configuration variable \a var in the string |
|
283 list map, converts the string list it maps to into a set |
|
284 of strings, and returns the set. |
|
285 */ |
|
286 QSet<QString> Config::getStringSet(const QString& var) const |
|
287 { |
|
288 return QSet<QString>::fromList(getStringList(var)); |
|
289 } |
|
290 |
|
291 /*! |
|
292 First, this function looks up the configuration variable \a var |
|
293 in the location map and, if found, sets the internal variable |
|
294 \c{lastLoc} the Location that \a var maps to. |
|
295 |
|
296 Then it looks up the configuration variable \a var in the string |
|
297 list map, and returns the string list that \a var maps to. |
|
298 */ |
|
299 QStringList Config::getStringList(const QString& var) const |
|
300 { |
|
301 if (!locMap[var].isEmpty()) |
|
302 (Location&) lastLoc = locMap[var]; |
|
303 return stringListValueMap[var]; |
|
304 } |
|
305 |
|
306 /*! |
|
307 Calls getRegExpList() with the control variable \a var and |
|
308 iterates through the resulting list of regular expressions, |
|
309 concatening them with some extras characters to form a single |
|
310 QRegExp, which is returned/ |
|
311 |
|
312 \sa getRegExpList() |
|
313 */ |
|
314 QRegExp Config::getRegExp(const QString& var) const |
|
315 { |
|
316 QString pattern; |
|
317 QList<QRegExp> subRegExps = getRegExpList(var); |
|
318 QList<QRegExp>::ConstIterator s = subRegExps.begin(); |
|
319 |
|
320 while (s != subRegExps.end()) { |
|
321 if (!(*s).isValid()) |
|
322 return *s; |
|
323 if (!pattern.isEmpty()) |
|
324 pattern += QLatin1Char('|'); |
|
325 pattern += QLatin1String("(?:") + (*s).pattern() + QLatin1Char(')'); |
|
326 ++s; |
|
327 } |
|
328 if (pattern.isEmpty()) |
|
329 pattern = QLatin1String("$x"); // cannot match |
|
330 return QRegExp(pattern); |
|
331 } |
|
332 |
|
333 /*! |
|
334 Looks up the configuration variable \a var in the string list |
|
335 map, converts the string list to a list of regular expressions, |
|
336 and returns it. |
|
337 */ |
|
338 QList<QRegExp> Config::getRegExpList(const QString& var) const |
|
339 { |
|
340 QStringList strs = getStringList(var); |
|
341 QStringList::ConstIterator s = strs.begin(); |
|
342 QList<QRegExp> regExps; |
|
343 |
|
344 while (s != strs.end()) { |
|
345 regExps += QRegExp(*s); |
|
346 ++s; |
|
347 } |
|
348 return regExps; |
|
349 } |
|
350 |
|
351 /*! |
|
352 This function is slower than it could be. |
|
353 */ |
|
354 QSet<QString> Config::subVars(const QString& var) const |
|
355 { |
|
356 QSet<QString> result; |
|
357 QString varDot = var + QLatin1Char('.'); |
|
358 QMap<QString, QString>::ConstIterator v = stringValueMap.begin(); |
|
359 while (v != stringValueMap.end()) { |
|
360 if (v.key().startsWith(varDot)) { |
|
361 QString subVar = v.key().mid(varDot.length()); |
|
362 int dot = subVar.indexOf(QLatin1Char('.')); |
|
363 if (dot != -1) |
|
364 subVar.truncate(dot); |
|
365 result.insert(subVar); |
|
366 } |
|
367 ++v; |
|
368 } |
|
369 return result; |
|
370 } |
|
371 |
|
372 /*! |
|
373 Builds and returns a list of file pathnames for the file |
|
374 type specified by \a filesVar (e.g. "headers" or "sources"). |
|
375 The files are found in the directories specified by |
|
376 \a dirsVar, and they are filtered by \a defaultNameFilter |
|
377 if a better filter can't be constructed from \a filesVar. |
|
378 The directories in \a excludedDirs are avoided. |
|
379 */ |
|
380 QStringList Config::getAllFiles(const QString &filesVar, |
|
381 const QString &dirsVar, |
|
382 const QString &defaultNameFilter, |
|
383 const QSet<QString> &excludedDirs) |
|
384 { |
|
385 QStringList result = getStringList(filesVar); |
|
386 QStringList dirs = getStringList(dirsVar); |
|
387 |
|
388 QString nameFilter = getString(filesVar + dot + |
|
389 QLatin1String(CONFIG_FILEEXTENSIONS)); |
|
390 if (nameFilter.isEmpty()) |
|
391 nameFilter = defaultNameFilter; |
|
392 |
|
393 QStringList::ConstIterator d = dirs.begin(); |
|
394 while (d != dirs.end()) { |
|
395 result += getFilesHere(*d, nameFilter, excludedDirs); |
|
396 ++d; |
|
397 } |
|
398 return result; |
|
399 } |
|
400 |
|
401 /*! |
|
402 \a fileName is the path of the file to find. |
|
403 |
|
404 \a files and \a dirs are the lists where we must find the |
|
405 components of \a fileName. |
|
406 |
|
407 \a location is used for obtaining the file and line numbers |
|
408 for report qdoc errors. |
|
409 */ |
|
410 QString Config::findFile(const Location& location, |
|
411 const QStringList& files, |
|
412 const QStringList& dirs, |
|
413 const QString& fileName, |
|
414 QString& userFriendlyFilePath) |
|
415 { |
|
416 if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) { |
|
417 userFriendlyFilePath = fileName; |
|
418 return fileName; |
|
419 } |
|
420 |
|
421 QFileInfo fileInfo; |
|
422 QStringList components = fileName.split(QLatin1Char('?')); |
|
423 QString firstComponent = components.first(); |
|
424 |
|
425 QStringList::ConstIterator f = files.begin(); |
|
426 while (f != files.end()) { |
|
427 if (*f == firstComponent || |
|
428 (*f).endsWith(QLatin1Char('/') + firstComponent)) { |
|
429 fileInfo.setFile(*f); |
|
430 if (!fileInfo.exists()) |
|
431 location.fatal(tr("File '%1' does not exist").arg(*f)); |
|
432 break; |
|
433 } |
|
434 ++f; |
|
435 } |
|
436 |
|
437 if (fileInfo.fileName().isEmpty()) { |
|
438 QStringList::ConstIterator d = dirs.begin(); |
|
439 while (d != dirs.end()) { |
|
440 fileInfo.setFile(QDir(*d), firstComponent); |
|
441 if (fileInfo.exists()) { |
|
442 break; |
|
443 } |
|
444 ++d; |
|
445 } |
|
446 } |
|
447 |
|
448 userFriendlyFilePath = QString(); |
|
449 if (!fileInfo.exists()) |
|
450 return QString(); |
|
451 |
|
452 QStringList::ConstIterator c = components.begin(); |
|
453 for (;;) { |
|
454 bool isArchive = (c != components.end() - 1); |
|
455 ArchiveExtractor *extractor = 0; |
|
456 QString userFriendly = *c; |
|
457 |
|
458 if (isArchive) { |
|
459 extractor = ArchiveExtractor::extractorForFileName(userFriendly); |
|
460 } |
|
461 |
|
462 if (extractor == 0) { |
|
463 Uncompressor *uncompressor = |
|
464 Uncompressor::uncompressorForFileName(userFriendly); |
|
465 if (uncompressor != 0) { |
|
466 QString fileNameWithCorrectExtension = |
|
467 uncompressor->uncompressedFilePath( |
|
468 fileInfo.filePath()); |
|
469 QString uncompressed = uncompressedFiles[fileInfo.filePath()]; |
|
470 if (uncompressed.isEmpty()) { |
|
471 uncompressed = |
|
472 QTemporaryFile(fileInfo.filePath()).fileName(); |
|
473 uncompressor->uncompressFile(location, |
|
474 fileInfo.filePath(), |
|
475 uncompressed); |
|
476 uncompressedFiles[fileInfo.filePath()] = uncompressed; |
|
477 } |
|
478 fileInfo.setFile(uncompressed); |
|
479 |
|
480 if (isArchive) { |
|
481 extractor = ArchiveExtractor::extractorForFileName( |
|
482 fileNameWithCorrectExtension); |
|
483 } |
|
484 else { |
|
485 userFriendly = fileNameWithCorrectExtension; |
|
486 } |
|
487 } |
|
488 } |
|
489 userFriendlyFilePath += userFriendly; |
|
490 |
|
491 if (isArchive) { |
|
492 if (extractor == 0) |
|
493 location.fatal(tr("Unknown archive type '%1'") |
|
494 .arg(userFriendlyFilePath)); |
|
495 QString extracted = extractedDirs[fileInfo.filePath()]; |
|
496 if (extracted.isEmpty()) { |
|
497 extracted = QTemporaryFile(fileInfo.filePath()).fileName(); |
|
498 if (!QDir().mkdir(extracted)) |
|
499 location.fatal(tr("Cannot create temporary directory '%1'") |
|
500 .arg(extracted)); |
|
501 extractor->extractArchive(location, fileInfo.filePath(), |
|
502 extracted); |
|
503 extractedDirs[fileInfo.filePath()] = extracted; |
|
504 } |
|
505 ++c; |
|
506 fileInfo.setFile(QDir(extracted), *c); |
|
507 } |
|
508 else { |
|
509 break; |
|
510 } |
|
511 userFriendlyFilePath += "?"; |
|
512 } |
|
513 return fileInfo.filePath(); |
|
514 } |
|
515 |
|
516 /*! |
|
517 */ |
|
518 QString Config::findFile(const Location& location, |
|
519 const QStringList& files, |
|
520 const QStringList& dirs, |
|
521 const QString& fileBase, |
|
522 const QStringList& fileExtensions, |
|
523 QString& userFriendlyFilePath) |
|
524 { |
|
525 QStringList::ConstIterator e = fileExtensions.begin(); |
|
526 while (e != fileExtensions.end()) { |
|
527 QString filePath = findFile(location, files, dirs, fileBase + "." + *e, |
|
528 userFriendlyFilePath); |
|
529 if (!filePath.isEmpty()) |
|
530 return filePath; |
|
531 ++e; |
|
532 } |
|
533 return findFile(location, files, dirs, fileBase, userFriendlyFilePath); |
|
534 } |
|
535 |
|
536 /*! |
|
537 Copies the \a sourceFilePath to the file name constructed by |
|
538 concatenating \a targetDirPath and \a userFriendlySourceFilePath. |
|
539 \a location is for identifying the file and line number where |
|
540 a qdoc error occurred. The constructed output file name is |
|
541 returned. |
|
542 */ |
|
543 QString Config::copyFile(const Location& location, |
|
544 const QString& sourceFilePath, |
|
545 const QString& userFriendlySourceFilePath, |
|
546 const QString& targetDirPath) |
|
547 { |
|
548 QFile inFile(sourceFilePath); |
|
549 if (!inFile.open(QFile::ReadOnly)) { |
|
550 location.fatal(tr("Cannot open input file '%1': %2") |
|
551 .arg(inFile.fileName()).arg(inFile.errorString())); |
|
552 return ""; |
|
553 } |
|
554 |
|
555 QString outFileName = userFriendlySourceFilePath; |
|
556 int slash = outFileName.lastIndexOf("/"); |
|
557 if (slash != -1) |
|
558 outFileName = outFileName.mid(slash); |
|
559 |
|
560 QFile outFile(targetDirPath + "/" + outFileName); |
|
561 if (!outFile.open(QFile::WriteOnly)) { |
|
562 location.fatal(tr("Cannot open output file '%1': %2") |
|
563 .arg(outFile.fileName()).arg(outFile.errorString())); |
|
564 return ""; |
|
565 } |
|
566 |
|
567 char buffer[1024]; |
|
568 int len; |
|
569 while ((len = inFile.read(buffer, sizeof(buffer))) > 0) { |
|
570 outFile.write(buffer, len); |
|
571 } |
|
572 return outFileName; |
|
573 } |
|
574 |
|
575 /*! |
|
576 Finds the largest unicode digit in \a value in the range |
|
577 1..7 and returns it. |
|
578 */ |
|
579 int Config::numParams(const QString& value) |
|
580 { |
|
581 int max = 0; |
|
582 for (int i = 0; i != value.length(); i++) { |
|
583 uint c = value[i].unicode(); |
|
584 if (c > 0 && c < 8) |
|
585 max = qMax(max, (int)c); |
|
586 } |
|
587 return max; |
|
588 } |
|
589 |
|
590 /*! |
|
591 Removes everything from \a dir. This function is recursive. |
|
592 It doesn't remove \a dir itself, but if it was called |
|
593 recursively, then the caller will remove \a dir. |
|
594 */ |
|
595 bool Config::removeDirContents(const QString& dir) |
|
596 { |
|
597 QDir dirInfo(dir); |
|
598 QFileInfoList entries = dirInfo.entryInfoList(); |
|
599 |
|
600 bool ok = true; |
|
601 |
|
602 QFileInfoList::Iterator it = entries.begin(); |
|
603 while (it != entries.end()) { |
|
604 if ((*it).isFile()) { |
|
605 if (!dirInfo.remove((*it).fileName())) |
|
606 ok = false; |
|
607 } |
|
608 else if ((*it).isDir()) { |
|
609 if ((*it).fileName() != "." && (*it).fileName() != "..") { |
|
610 if (removeDirContents((*it).absoluteFilePath())) { |
|
611 if (!dirInfo.rmdir((*it).fileName())) |
|
612 ok = false; |
|
613 } |
|
614 else { |
|
615 ok = false; |
|
616 } |
|
617 } |
|
618 } |
|
619 ++it; |
|
620 } |
|
621 return ok; |
|
622 } |
|
623 |
|
624 /*! |
|
625 Returns true if \a ch is a letter, number, '_', '.', |
|
626 '{', '}', or ','. |
|
627 */ |
|
628 bool Config::isMetaKeyChar(QChar ch) |
|
629 { |
|
630 return ch.isLetterOrNumber() |
|
631 || ch == QLatin1Char('_') |
|
632 || ch == QLatin1Char('.') |
|
633 || ch == QLatin1Char('{') |
|
634 || ch == QLatin1Char('}') |
|
635 || ch == QLatin1Char(','); |
|
636 } |
|
637 |
|
638 /*! |
|
639 Load, parse, and process a qdoc configuration file. This |
|
640 function is only called by the other load() function, but |
|
641 this one is recursive, i.e., it calls itself when it sees |
|
642 an \c{include} statement in the qdog configuration file. |
|
643 */ |
|
644 void Config::load(Location location, const QString& fileName) |
|
645 { |
|
646 QRegExp keySyntax("\\w+(?:\\.\\w+)*"); |
|
647 |
|
648 #define SKIP_CHAR() \ |
|
649 do { \ |
|
650 location.advance(c); \ |
|
651 ++i; \ |
|
652 c = text.at(i); \ |
|
653 cc = c.unicode(); \ |
|
654 } while (0) |
|
655 |
|
656 #define SKIP_SPACES() \ |
|
657 while (c.isSpace() && cc != '\n') \ |
|
658 SKIP_CHAR() |
|
659 |
|
660 #define PUT_CHAR() \ |
|
661 word += c; \ |
|
662 SKIP_CHAR(); |
|
663 |
|
664 if (location.depth() > 16) |
|
665 location.fatal(tr("Too many nested includes")); |
|
666 |
|
667 QFile fin(fileName); |
|
668 if (!fin.open(QFile::ReadOnly | QFile::Text)) { |
|
669 fin.setFileName(fileName + ".qdoc"); |
|
670 if (!fin.open(QFile::ReadOnly | QFile::Text)) |
|
671 location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString())); |
|
672 } |
|
673 |
|
674 QString text = fin.readAll(); |
|
675 text += QLatin1String("\n\n"); |
|
676 text += QChar('\0'); |
|
677 fin.close(); |
|
678 |
|
679 location.push(fileName); |
|
680 location.start(); |
|
681 |
|
682 int i = 0; |
|
683 QChar c = text.at(0); |
|
684 uint cc = c.unicode(); |
|
685 while (i < (int) text.length()) { |
|
686 if (cc == 0) |
|
687 ++i; |
|
688 else if (c.isSpace()) { |
|
689 SKIP_CHAR(); |
|
690 } |
|
691 else if (cc == '#') { |
|
692 do { |
|
693 SKIP_CHAR(); |
|
694 } while (cc != '\n'); |
|
695 } |
|
696 else if (isMetaKeyChar(c)) { |
|
697 Location keyLoc = location; |
|
698 bool plus = false; |
|
699 QString stringValue; |
|
700 QStringList stringListValue; |
|
701 QString word; |
|
702 bool inQuote = false; |
|
703 bool prevWordQuoted = true; |
|
704 bool metWord = false; |
|
705 |
|
706 MetaStack stack; |
|
707 do { |
|
708 stack.process(c, location); |
|
709 SKIP_CHAR(); |
|
710 } while (isMetaKeyChar(c)); |
|
711 |
|
712 QStringList keys = stack.getExpanded(location); |
|
713 SKIP_SPACES(); |
|
714 |
|
715 if (keys.count() == 1 && keys.first() == "include") { |
|
716 QString includeFile; |
|
717 |
|
718 if (cc != '(') |
|
719 location.fatal(tr("Bad include syntax")); |
|
720 SKIP_CHAR(); |
|
721 SKIP_SPACES(); |
|
722 while (!c.isSpace() && cc != '#' && cc != ')') { |
|
723 includeFile += c; |
|
724 SKIP_CHAR(); |
|
725 } |
|
726 SKIP_SPACES(); |
|
727 if (cc != ')') |
|
728 location.fatal(tr("Bad include syntax")); |
|
729 SKIP_CHAR(); |
|
730 SKIP_SPACES(); |
|
731 if (cc != '#' && cc != '\n') |
|
732 location.fatal(tr("Trailing garbage")); |
|
733 |
|
734 /* |
|
735 Here is the recursive call. |
|
736 */ |
|
737 load(location, |
|
738 QFileInfo(QFileInfo(fileName).dir(), includeFile) |
|
739 .filePath()); |
|
740 } |
|
741 else { |
|
742 /* |
|
743 It wasn't an include statement, so it;s something else. |
|
744 */ |
|
745 if (cc == '+') { |
|
746 plus = true; |
|
747 SKIP_CHAR(); |
|
748 } |
|
749 if (cc != '=') |
|
750 location.fatal(tr("Expected '=' or '+=' after key")); |
|
751 SKIP_CHAR(); |
|
752 SKIP_SPACES(); |
|
753 |
|
754 for (;;) { |
|
755 if (cc == '\\') { |
|
756 int metaCharPos; |
|
757 |
|
758 SKIP_CHAR(); |
|
759 if (cc == '\n') { |
|
760 SKIP_CHAR(); |
|
761 } |
|
762 else if (cc > '0' && cc < '8') { |
|
763 word += QChar(c.digitValue()); |
|
764 SKIP_CHAR(); |
|
765 } |
|
766 else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) { |
|
767 word += "\a\b\f\n\r\t\v"[metaCharPos]; |
|
768 SKIP_CHAR(); |
|
769 } |
|
770 else { |
|
771 PUT_CHAR(); |
|
772 } |
|
773 } |
|
774 else if (c.isSpace() || cc == '#') { |
|
775 if (inQuote) { |
|
776 if (cc == '\n') |
|
777 location.fatal(tr("Unterminated string")); |
|
778 PUT_CHAR(); |
|
779 } |
|
780 else { |
|
781 if (!word.isEmpty()) { |
|
782 if (metWord) |
|
783 stringValue += QLatin1Char(' '); |
|
784 stringValue += word; |
|
785 stringListValue << word; |
|
786 metWord = true; |
|
787 word.clear(); |
|
788 prevWordQuoted = false; |
|
789 } |
|
790 if (cc == '\n' || cc == '#') |
|
791 break; |
|
792 SKIP_SPACES(); |
|
793 } |
|
794 } |
|
795 else if (cc == '"') { |
|
796 if (inQuote) { |
|
797 if (!prevWordQuoted) |
|
798 stringValue += QLatin1Char(' '); |
|
799 stringValue += word; |
|
800 if (!word.isEmpty()) |
|
801 stringListValue << word; |
|
802 metWord = true; |
|
803 word.clear(); |
|
804 prevWordQuoted = true; |
|
805 } |
|
806 inQuote = !inQuote; |
|
807 SKIP_CHAR(); |
|
808 } |
|
809 else if (cc == '$') { |
|
810 QString var; |
|
811 SKIP_CHAR(); |
|
812 while (c.isLetterOrNumber() || cc == '_') { |
|
813 var += c; |
|
814 SKIP_CHAR(); |
|
815 } |
|
816 if (!var.isEmpty()) { |
|
817 char *val = getenv(var.toLatin1().data()); |
|
818 if (val == 0) { |
|
819 location.fatal(tr("Environment variable '%1' undefined").arg(var)); |
|
820 } |
|
821 else { |
|
822 word += QString(val); |
|
823 } |
|
824 } |
|
825 } |
|
826 else { |
|
827 if (!inQuote && cc == '=') |
|
828 location.fatal(tr("Unexpected '='")); |
|
829 PUT_CHAR(); |
|
830 } |
|
831 } |
|
832 |
|
833 QStringList::ConstIterator key = keys.begin(); |
|
834 while (key != keys.end()) { |
|
835 if (!keySyntax.exactMatch(*key)) |
|
836 keyLoc.fatal(tr("Invalid key '%1'").arg(*key)); |
|
837 |
|
838 if (plus) { |
|
839 if (locMap[*key].isEmpty()) { |
|
840 locMap[*key] = keyLoc; |
|
841 } |
|
842 else { |
|
843 locMap[*key].setEtc(true); |
|
844 } |
|
845 if (stringValueMap[*key].isEmpty()) { |
|
846 stringValueMap[*key] = stringValue; |
|
847 } |
|
848 else { |
|
849 stringValueMap[*key] += |
|
850 QLatin1Char(' ') + stringValue; |
|
851 } |
|
852 stringListValueMap[*key] += stringListValue; |
|
853 } |
|
854 else { |
|
855 locMap[*key] = keyLoc; |
|
856 stringValueMap[*key] = stringValue; |
|
857 stringListValueMap[*key] = stringListValue; |
|
858 } |
|
859 ++key; |
|
860 } |
|
861 } |
|
862 } |
|
863 else { |
|
864 location.fatal(tr("Unexpected character '%1' at beginning of line") |
|
865 .arg(c)); |
|
866 } |
|
867 } |
|
868 } |
|
869 |
|
870 QStringList Config::getFilesHere(const QString& dir, |
|
871 const QString& nameFilter, |
|
872 const QSet<QString> &excludedDirs) |
|
873 { |
|
874 QStringList result; |
|
875 if (excludedDirs.contains(dir)) |
|
876 return result; |
|
877 |
|
878 QDir dirInfo(dir); |
|
879 QStringList fileNames; |
|
880 QStringList::const_iterator fn; |
|
881 |
|
882 dirInfo.setNameFilters(nameFilter.split(' ')); |
|
883 dirInfo.setSorting(QDir::Name); |
|
884 dirInfo.setFilter(QDir::Files); |
|
885 fileNames = dirInfo.entryList(); |
|
886 fn = fileNames.constBegin(); |
|
887 while (fn != fileNames.constEnd()) { |
|
888 if (!fn->startsWith(QLatin1Char('~'))) |
|
889 result.append(dirInfo.filePath(*fn)); |
|
890 ++fn; |
|
891 } |
|
892 |
|
893 dirInfo.setNameFilters(QStringList("*")); |
|
894 dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot); |
|
895 fileNames = dirInfo.entryList(); |
|
896 fn = fileNames.constBegin(); |
|
897 while (fn != fileNames.constEnd()) { |
|
898 result += getFilesHere(dirInfo.filePath(*fn), nameFilter, excludedDirs); |
|
899 ++fn; |
|
900 } |
|
901 return result; |
|
902 } |
|
903 |
|
904 QT_END_NAMESPACE |