|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 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 */ |
|
403 QString Config::findFile(const Location& location, |
|
404 const QStringList& files, |
|
405 const QStringList& dirs, |
|
406 const QString& fileName, |
|
407 QString& userFriendlyFilePath) |
|
408 { |
|
409 if (fileName.isEmpty() || fileName.startsWith(QLatin1Char('/'))) { |
|
410 userFriendlyFilePath = fileName; |
|
411 return fileName; |
|
412 } |
|
413 |
|
414 QFileInfo fileInfo; |
|
415 QStringList components = fileName.split(QLatin1Char('?')); |
|
416 QString firstComponent = components.first(); |
|
417 |
|
418 QStringList::ConstIterator f = files.begin(); |
|
419 while (f != files.end()) { |
|
420 if (*f == firstComponent || |
|
421 (*f).endsWith(QLatin1Char('/') + firstComponent)) { |
|
422 fileInfo.setFile(*f); |
|
423 if (!fileInfo.exists()) |
|
424 location.fatal(tr("File '%1' does not exist").arg(*f)); |
|
425 break; |
|
426 } |
|
427 ++f; |
|
428 } |
|
429 |
|
430 if (fileInfo.fileName().isEmpty()) { |
|
431 QStringList::ConstIterator d = dirs.begin(); |
|
432 while (d != dirs.end()) { |
|
433 fileInfo.setFile(QDir(*d), firstComponent); |
|
434 if (fileInfo.exists()) { |
|
435 break; |
|
436 } |
|
437 ++d; |
|
438 } |
|
439 } |
|
440 |
|
441 userFriendlyFilePath = QString(); |
|
442 if (!fileInfo.exists()) |
|
443 return QString(); |
|
444 |
|
445 QStringList::ConstIterator c = components.begin(); |
|
446 for (;;) { |
|
447 bool isArchive = (c != components.end() - 1); |
|
448 ArchiveExtractor *extractor = 0; |
|
449 QString userFriendly = *c; |
|
450 |
|
451 if (isArchive) { |
|
452 extractor = ArchiveExtractor::extractorForFileName(userFriendly); |
|
453 } |
|
454 |
|
455 if (extractor == 0) { |
|
456 Uncompressor *uncompressor = |
|
457 Uncompressor::uncompressorForFileName(userFriendly); |
|
458 if (uncompressor != 0) { |
|
459 QString fileNameWithCorrectExtension = |
|
460 uncompressor->uncompressedFilePath( |
|
461 fileInfo.filePath()); |
|
462 QString uncompressed = uncompressedFiles[fileInfo.filePath()]; |
|
463 if (uncompressed.isEmpty()) { |
|
464 uncompressed = |
|
465 QTemporaryFile(fileInfo.filePath()).fileName(); |
|
466 uncompressor->uncompressFile(location, |
|
467 fileInfo.filePath(), |
|
468 uncompressed); |
|
469 uncompressedFiles[fileInfo.filePath()] = uncompressed; |
|
470 } |
|
471 fileInfo.setFile(uncompressed); |
|
472 |
|
473 if (isArchive) { |
|
474 extractor = ArchiveExtractor::extractorForFileName( |
|
475 fileNameWithCorrectExtension); |
|
476 } |
|
477 else { |
|
478 userFriendly = fileNameWithCorrectExtension; |
|
479 } |
|
480 } |
|
481 } |
|
482 userFriendlyFilePath += userFriendly; |
|
483 |
|
484 if (isArchive) { |
|
485 if (extractor == 0) |
|
486 location.fatal(tr("Unknown archive type '%1'") |
|
487 .arg(userFriendlyFilePath)); |
|
488 QString extracted = extractedDirs[fileInfo.filePath()]; |
|
489 if (extracted.isEmpty()) { |
|
490 extracted = QTemporaryFile(fileInfo.filePath()).fileName(); |
|
491 if (!QDir().mkdir(extracted)) |
|
492 location.fatal(tr("Cannot create temporary directory '%1'") |
|
493 .arg(extracted)); |
|
494 extractor->extractArchive(location, fileInfo.filePath(), |
|
495 extracted); |
|
496 extractedDirs[fileInfo.filePath()] = extracted; |
|
497 } |
|
498 ++c; |
|
499 fileInfo.setFile(QDir(extracted), *c); |
|
500 } |
|
501 else { |
|
502 break; |
|
503 } |
|
504 userFriendlyFilePath += "?"; |
|
505 } |
|
506 return fileInfo.filePath(); |
|
507 } |
|
508 |
|
509 /*! |
|
510 */ |
|
511 QString Config::findFile(const Location& location, |
|
512 const QStringList& files, |
|
513 const QStringList& dirs, |
|
514 const QString& fileBase, |
|
515 const QStringList& fileExtensions, |
|
516 QString& userFriendlyFilePath) |
|
517 { |
|
518 QStringList::ConstIterator e = fileExtensions.begin(); |
|
519 while (e != fileExtensions.end()) { |
|
520 QString filePath = findFile(location, files, dirs, fileBase + "." + *e, |
|
521 userFriendlyFilePath); |
|
522 if (!filePath.isEmpty()) |
|
523 return filePath; |
|
524 ++e; |
|
525 } |
|
526 return findFile(location, files, dirs, fileBase, userFriendlyFilePath); |
|
527 } |
|
528 |
|
529 /*! |
|
530 */ |
|
531 QString Config::copyFile(const Location& location, |
|
532 const QString& sourceFilePath, |
|
533 const QString& userFriendlySourceFilePath, |
|
534 const QString& targetDirPath) |
|
535 { |
|
536 QFile inFile(sourceFilePath); |
|
537 if (!inFile.open(QFile::ReadOnly)) { |
|
538 location.fatal(tr("Cannot open input file '%1': %2") |
|
539 .arg(inFile.fileName()).arg(inFile.errorString())); |
|
540 return ""; |
|
541 } |
|
542 |
|
543 QString outFileName = userFriendlySourceFilePath; |
|
544 int slash = outFileName.lastIndexOf("/"); |
|
545 if (slash != -1) |
|
546 outFileName = outFileName.mid(slash); |
|
547 |
|
548 QFile outFile(targetDirPath + "/" + outFileName); |
|
549 if (!outFile.open(QFile::WriteOnly)) { |
|
550 location.fatal(tr("Cannot open output file '%1': %2") |
|
551 .arg(outFile.fileName()).arg(outFile.errorString())); |
|
552 return ""; |
|
553 } |
|
554 |
|
555 char buffer[1024]; |
|
556 int len; |
|
557 while ((len = inFile.read(buffer, sizeof(buffer))) > 0) { |
|
558 outFile.write(buffer, len); |
|
559 } |
|
560 return outFileName; |
|
561 } |
|
562 |
|
563 /*! |
|
564 Finds the largest unicode digit in \a value in the range |
|
565 1..7 and returns it. |
|
566 */ |
|
567 int Config::numParams(const QString& value) |
|
568 { |
|
569 int max = 0; |
|
570 for (int i = 0; i != value.length(); i++) { |
|
571 uint c = value[i].unicode(); |
|
572 if (c > 0 && c < 8) |
|
573 max = qMax(max, (int)c); |
|
574 } |
|
575 return max; |
|
576 } |
|
577 |
|
578 /*! |
|
579 Removes everything from \a dir. This function is recursive. |
|
580 It doesn't remove \a dir itself, but if it was called |
|
581 recursively, then the caller will remove \a dir. |
|
582 */ |
|
583 bool Config::removeDirContents(const QString& dir) |
|
584 { |
|
585 QDir dirInfo(dir); |
|
586 QFileInfoList entries = dirInfo.entryInfoList(); |
|
587 |
|
588 bool ok = true; |
|
589 |
|
590 QFileInfoList::Iterator it = entries.begin(); |
|
591 while (it != entries.end()) { |
|
592 if ((*it).isFile()) { |
|
593 if (!dirInfo.remove((*it).fileName())) |
|
594 ok = false; |
|
595 } |
|
596 else if ((*it).isDir()) { |
|
597 if ((*it).fileName() != "." && (*it).fileName() != "..") { |
|
598 if (removeDirContents((*it).absoluteFilePath())) { |
|
599 if (!dirInfo.rmdir((*it).fileName())) |
|
600 ok = false; |
|
601 } |
|
602 else { |
|
603 ok = false; |
|
604 } |
|
605 } |
|
606 } |
|
607 ++it; |
|
608 } |
|
609 return ok; |
|
610 } |
|
611 |
|
612 /*! |
|
613 Returns true if \a ch is a letter, number, '_', '.', |
|
614 '{', '}', or ','. |
|
615 */ |
|
616 bool Config::isMetaKeyChar(QChar ch) |
|
617 { |
|
618 return ch.isLetterOrNumber() |
|
619 || ch == QLatin1Char('_') |
|
620 || ch == QLatin1Char('.') |
|
621 || ch == QLatin1Char('{') |
|
622 || ch == QLatin1Char('}') |
|
623 || ch == QLatin1Char(','); |
|
624 } |
|
625 |
|
626 /*! |
|
627 Load, parse, and process a qdoc configuration file. This |
|
628 function is only called by the other load() function, but |
|
629 this one is recursive, i.e., it calls itself when it sees |
|
630 an \c{include} statement in the qdog configuration file. |
|
631 */ |
|
632 void Config::load(Location location, const QString& fileName) |
|
633 { |
|
634 QRegExp keySyntax("\\w+(?:\\.\\w+)*"); |
|
635 |
|
636 #define SKIP_CHAR() \ |
|
637 do { \ |
|
638 location.advance(c); \ |
|
639 ++i; \ |
|
640 c = text.at(i); \ |
|
641 cc = c.unicode(); \ |
|
642 } while (0) |
|
643 |
|
644 #define SKIP_SPACES() \ |
|
645 while (c.isSpace() && cc != '\n') \ |
|
646 SKIP_CHAR() |
|
647 |
|
648 #define PUT_CHAR() \ |
|
649 word += c; \ |
|
650 SKIP_CHAR(); |
|
651 |
|
652 if (location.depth() > 16) |
|
653 location.fatal(tr("Too many nested includes")); |
|
654 |
|
655 QFile fin(fileName); |
|
656 if (!fin.open(QFile::ReadOnly | QFile::Text)) { |
|
657 fin.setFileName(fileName + ".qdoc"); |
|
658 if (!fin.open(QFile::ReadOnly | QFile::Text)) |
|
659 location.fatal(tr("Cannot open file '%1': %2").arg(fileName).arg(fin.errorString())); |
|
660 } |
|
661 |
|
662 QString text = fin.readAll(); |
|
663 text += QLatin1String("\n\n"); |
|
664 text += QChar('\0'); |
|
665 fin.close(); |
|
666 |
|
667 location.push(fileName); |
|
668 location.start(); |
|
669 |
|
670 int i = 0; |
|
671 QChar c = text.at(0); |
|
672 uint cc = c.unicode(); |
|
673 while (i < (int) text.length()) { |
|
674 if (cc == 0) |
|
675 ++i; |
|
676 else if (c.isSpace()) { |
|
677 SKIP_CHAR(); |
|
678 } |
|
679 else if (cc == '#') { |
|
680 do { |
|
681 SKIP_CHAR(); |
|
682 } while (cc != '\n'); |
|
683 } |
|
684 else if (isMetaKeyChar(c)) { |
|
685 Location keyLoc = location; |
|
686 bool plus = false; |
|
687 QString stringValue; |
|
688 QStringList stringListValue; |
|
689 QString word; |
|
690 bool inQuote = false; |
|
691 bool prevWordQuoted = true; |
|
692 bool metWord = false; |
|
693 |
|
694 MetaStack stack; |
|
695 do { |
|
696 stack.process(c, location); |
|
697 SKIP_CHAR(); |
|
698 } while (isMetaKeyChar(c)); |
|
699 |
|
700 QStringList keys = stack.getExpanded(location); |
|
701 SKIP_SPACES(); |
|
702 |
|
703 if (keys.count() == 1 && keys.first() == "include") { |
|
704 QString includeFile; |
|
705 |
|
706 if (cc != '(') |
|
707 location.fatal(tr("Bad include syntax")); |
|
708 SKIP_CHAR(); |
|
709 SKIP_SPACES(); |
|
710 while (!c.isSpace() && cc != '#' && cc != ')') { |
|
711 includeFile += c; |
|
712 SKIP_CHAR(); |
|
713 } |
|
714 SKIP_SPACES(); |
|
715 if (cc != ')') |
|
716 location.fatal(tr("Bad include syntax")); |
|
717 SKIP_CHAR(); |
|
718 SKIP_SPACES(); |
|
719 if (cc != '#' && cc != '\n') |
|
720 location.fatal(tr("Trailing garbage")); |
|
721 |
|
722 /* |
|
723 Here is the recursive call. |
|
724 */ |
|
725 load(location, |
|
726 QFileInfo(QFileInfo(fileName).dir(), includeFile) |
|
727 .filePath()); |
|
728 } |
|
729 else { |
|
730 /* |
|
731 It wasn't an include statement, so it;s something else. |
|
732 */ |
|
733 if (cc == '+') { |
|
734 plus = true; |
|
735 SKIP_CHAR(); |
|
736 } |
|
737 if (cc != '=') |
|
738 location.fatal(tr("Expected '=' or '+=' after key")); |
|
739 SKIP_CHAR(); |
|
740 SKIP_SPACES(); |
|
741 |
|
742 for (;;) { |
|
743 if (cc == '\\') { |
|
744 int metaCharPos; |
|
745 |
|
746 SKIP_CHAR(); |
|
747 if (cc == '\n') { |
|
748 SKIP_CHAR(); |
|
749 } |
|
750 else if (cc > '0' && cc < '8') { |
|
751 word += QChar(c.digitValue()); |
|
752 SKIP_CHAR(); |
|
753 } |
|
754 else if ((metaCharPos = QString::fromLatin1("abfnrtv").indexOf(c)) != -1) { |
|
755 word += "\a\b\f\n\r\t\v"[metaCharPos]; |
|
756 SKIP_CHAR(); |
|
757 } |
|
758 else { |
|
759 PUT_CHAR(); |
|
760 } |
|
761 } |
|
762 else if (c.isSpace() || cc == '#') { |
|
763 if (inQuote) { |
|
764 if (cc == '\n') |
|
765 location.fatal(tr("Unterminated string")); |
|
766 PUT_CHAR(); |
|
767 } |
|
768 else { |
|
769 if (!word.isEmpty()) { |
|
770 if (metWord) |
|
771 stringValue += QLatin1Char(' '); |
|
772 stringValue += word; |
|
773 stringListValue << word; |
|
774 metWord = true; |
|
775 word.clear(); |
|
776 prevWordQuoted = false; |
|
777 } |
|
778 if (cc == '\n' || cc == '#') |
|
779 break; |
|
780 SKIP_SPACES(); |
|
781 } |
|
782 } |
|
783 else if (cc == '"') { |
|
784 if (inQuote) { |
|
785 if (!prevWordQuoted) |
|
786 stringValue += QLatin1Char(' '); |
|
787 stringValue += word; |
|
788 if (!word.isEmpty()) |
|
789 stringListValue << word; |
|
790 metWord = true; |
|
791 word.clear(); |
|
792 prevWordQuoted = true; |
|
793 } |
|
794 inQuote = !inQuote; |
|
795 SKIP_CHAR(); |
|
796 } |
|
797 else if (cc == '$') { |
|
798 QString var; |
|
799 SKIP_CHAR(); |
|
800 while (c.isLetterOrNumber() || cc == '_') { |
|
801 var += c; |
|
802 SKIP_CHAR(); |
|
803 } |
|
804 if (!var.isEmpty()) { |
|
805 char *val = getenv(var.toLatin1().data()); |
|
806 if (val == 0) { |
|
807 location.fatal(tr("Environment variable '%1' undefined").arg(var)); |
|
808 } |
|
809 else { |
|
810 word += QString(val); |
|
811 } |
|
812 } |
|
813 } |
|
814 else { |
|
815 if (!inQuote && cc == '=') |
|
816 location.fatal(tr("Unexpected '='")); |
|
817 PUT_CHAR(); |
|
818 } |
|
819 } |
|
820 |
|
821 QStringList::ConstIterator key = keys.begin(); |
|
822 while (key != keys.end()) { |
|
823 if (!keySyntax.exactMatch(*key)) |
|
824 keyLoc.fatal(tr("Invalid key '%1'").arg(*key)); |
|
825 |
|
826 if (plus) { |
|
827 if (locMap[*key].isEmpty()) { |
|
828 locMap[*key] = keyLoc; |
|
829 } |
|
830 else { |
|
831 locMap[*key].setEtc(true); |
|
832 } |
|
833 if (stringValueMap[*key].isEmpty()) { |
|
834 stringValueMap[*key] = stringValue; |
|
835 } |
|
836 else { |
|
837 stringValueMap[*key] += |
|
838 QLatin1Char(' ') + stringValue; |
|
839 } |
|
840 stringListValueMap[*key] += stringListValue; |
|
841 } |
|
842 else { |
|
843 locMap[*key] = keyLoc; |
|
844 stringValueMap[*key] = stringValue; |
|
845 stringListValueMap[*key] = stringListValue; |
|
846 } |
|
847 ++key; |
|
848 } |
|
849 } |
|
850 } |
|
851 else { |
|
852 location.fatal(tr("Unexpected character '%1' at beginning of line") |
|
853 .arg(c)); |
|
854 } |
|
855 } |
|
856 } |
|
857 |
|
858 QStringList Config::getFilesHere(const QString& dir, |
|
859 const QString& nameFilter, |
|
860 const QSet<QString> &excludedDirs) |
|
861 { |
|
862 QStringList result; |
|
863 if (excludedDirs.contains(dir)) |
|
864 return result; |
|
865 |
|
866 QDir dirInfo(dir); |
|
867 QStringList fileNames; |
|
868 QStringList::const_iterator fn; |
|
869 |
|
870 dirInfo.setNameFilters(nameFilter.split(' ')); |
|
871 dirInfo.setSorting(QDir::Name); |
|
872 dirInfo.setFilter(QDir::Files); |
|
873 fileNames = dirInfo.entryList(); |
|
874 fn = fileNames.constBegin(); |
|
875 while (fn != fileNames.constEnd()) { |
|
876 if (!fn->startsWith(QLatin1Char('~'))) |
|
877 result.append(dirInfo.filePath(*fn)); |
|
878 ++fn; |
|
879 } |
|
880 |
|
881 dirInfo.setNameFilters(QStringList("*")); |
|
882 dirInfo.setFilter(QDir::Dirs|QDir::NoDotAndDotDot); |
|
883 fileNames = dirInfo.entryList(); |
|
884 fn = fileNames.constBegin(); |
|
885 while (fn != fileNames.constEnd()) { |
|
886 result += getFilesHere(dirInfo.filePath(*fn), nameFilter, excludedDirs); |
|
887 ++fn; |
|
888 } |
|
889 return result; |
|
890 } |
|
891 |
|
892 QT_END_NAMESPACE |