add -list-languages option to lupdate
[qt:qt.git] / tools / linguist / lupdate / main.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 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 Qt Linguist of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "lupdate.h"
43
44 #include <translator.h>
45 #include <profileparser.h>
46 #include <profileevaluator.h>
47
48 #include <QtCore/QCoreApplication>
49 #include <QtCore/QDebug>
50 #include <QtCore/QDir>
51 #include <QtCore/QFile>
52 #include <QtCore/QFileInfo>
53 #include <QtCore/QString>
54 #include <QtCore/QStringList>
55 #include <QtCore/QTextCodec>
56 #include <QtCore/QTranslator>
57 #include <QtCore/QLibraryInfo>
58
59 #include <iostream>
60
61 static QString m_defaultExtensions;
62
63 static void printOut(const QString & out)
64 {
65     QTextStream stream(stdout);
66     stream << out;
67 }
68
69 static void printErr(const QString & out)
70 {
71     QTextStream stream(stderr);
72     stream << out;
73 }
74
75 class LU {
76     Q_DECLARE_TR_FUNCTIONS(LUpdate)
77 };
78
79 static void recursiveFileInfoList(const QDir &dir,
80     const QSet<QString> &nameFilters, QDir::Filters filter,
81     QFileInfoList *fileinfolist)
82 {
83     foreach (const QFileInfo &fi, dir.entryInfoList(filter))
84         if (fi.isDir())
85             recursiveFileInfoList(QDir(fi.absoluteFilePath()), nameFilters, filter, fileinfolist);
86         else if (nameFilters.contains(fi.suffix()))
87             fileinfolist->append(fi);
88 }
89
90 static void printUsage()
91 {
92     printOut(LU::tr(
93         "Usage:\n"
94         "    lupdate [options] [project-file]...\n"
95         "    lupdate [options] [source-file|path|@lst-file]... -ts ts-files|@lst-file\n\n"
96         "lupdate is part of Qt's Linguist tool chain. It extracts translatable\n"
97         "messages from Qt UI files, C++, Java and JavaScript/QtScript source code.\n"
98         "Extracted messages are stored in textual translation source files (typically\n"
99         "Qt TS XML). New and modified messages can be merged into existing TS files.\n\n"
100         "Options:\n"
101         "    -help  Display this information and exit.\n"
102         "    -no-obsolete\n"
103         "           Drop all obsolete strings.\n"
104         "    -extensions <ext>[,<ext>]...\n"
105         "           Process files with the given extensions only.\n"
106         "           The extension list must be separated with commas, not with whitespace.\n"
107         "           Default: '%1'.\n"
108         "    -pluralonly\n"
109         "           Only include plural form messages.\n"
110         "    -silent\n"
111         "           Do not explain what is being done.\n"
112         "    -no-sort\n"
113         "           Do not sort contexts in TS files.\n"
114         "    -no-recursive\n"
115         "           Do not recursively scan the following directories.\n"
116         "    -recursive\n"
117         "           Recursively scan the following directories (default).\n"
118         "    -I <includepath> or -I<includepath>\n"
119         "           Additional location to look for include files.\n"
120         "           May be specified multiple times.\n"
121         "    -locations {absolute|relative|none}\n"
122         "           Specify/override how source code references are saved in TS files.\n"
123         "           Default is absolute.\n"
124         "    -no-ui-lines\n"
125         "           Do not record line numbers in references to UI files.\n"
126         "    -disable-heuristic {sametext|similartext|number}\n"
127         "           Disable the named merge heuristic. Can be specified multiple times.\n"
128         "    -pro <filename>\n"
129         "           Name of a .pro file. Useful for files with .pro file syntax but\n"
130         "           different file suffix. Projects are recursed into and merged.\n"
131         "    -source-language <language>[_<region>]\n"
132         "           Specify the language of the source strings for new files.\n"
133         "           Defaults to POSIX if not specified.\n"
134         "    -target-language <language>[_<region>]\n"
135         "           Specify the language of the translations for new files.\n"
136         "           Guessed from the file name if not specified.\n"
137         "    -ts <ts-file>...\n"
138         "           Specify the output file(s). This will override the TRANSLATIONS\n"
139         "           and nullify the CODECFORTR from possibly specified project files.\n"
140         "    -codecfortr <codec>\n"
141         "           Specify the codec assumed for tr() calls. Effective only with -ts.\n"
142         "    -version\n"
143         "           Display the version of lupdate and exit.\n"
144         "    @lst-file\n"
145         "           Read additional file names (one per line) from lst-file.\n"
146     ).arg(m_defaultExtensions));
147 }
148
149 static void updateTsFiles(const Translator &fetchedTor, const QStringList &tsFileNames,
150     bool setCodec, const QString &sourceLanguage, const QString &targetLanguage,
151     UpdateOptions options, bool *fail)
152 {
153     QDir dir;
154     QString err;
155     foreach (const QString &fileName, tsFileNames) {
156         QString fn = dir.relativeFilePath(fileName);
157         ConversionData cd;
158         Translator tor;
159         cd.m_sortContexts = !(options & NoSort);
160         if (QFile(fileName).exists()) {
161             if (!tor.load(fileName, cd, QLatin1String("auto"))) {
162                 printErr(cd.error());
163                 *fail = true;
164                 continue;
165             }
166             tor.resolveDuplicates();
167             cd.clearErrors();
168             if (setCodec && fetchedTor.codec() != tor.codec())
169                 printErr(LU::tr("lupdate warning: Codec for tr() '%1' disagrees with"
170                                 " existing file's codec '%2'. Expect trouble.\n")
171                          .arg(QString::fromLatin1(fetchedTor.codecName()),
172                               QString::fromLatin1(tor.codecName())));
173             if (!targetLanguage.isEmpty() && targetLanguage != tor.languageCode())
174                 printErr(LU::tr("lupdate warning: Specified target language '%1' disagrees with"
175                                 " existing file's language '%2'. Ignoring.\n")
176                          .arg(targetLanguage, tor.languageCode()));
177             if (!sourceLanguage.isEmpty() && sourceLanguage != tor.sourceLanguageCode())
178                 printErr(LU::tr("lupdate warning: Specified source language '%1' disagrees with"
179                                 " existing file's language '%2'. Ignoring.\n")
180                          .arg(sourceLanguage, tor.sourceLanguageCode()));
181         } else {
182             if (setCodec)
183                 tor.setCodec(fetchedTor.codec());
184             if (!targetLanguage.isEmpty())
185                 tor.setLanguageCode(targetLanguage);
186             else
187                 tor.setLanguageCode(Translator::guessLanguageCodeFromFileName(fileName));
188             if (!sourceLanguage.isEmpty())
189                 tor.setSourceLanguageCode(sourceLanguage);
190         }
191         tor.makeFileNamesAbsolute(QFileInfo(fileName).absoluteDir());
192         if (options & NoLocations)
193             tor.setLocationsType(Translator::NoLocations);
194         else if (options & RelativeLocations)
195             tor.setLocationsType(Translator::RelativeLocations);
196         else if (options & AbsoluteLocations)
197             tor.setLocationsType(Translator::AbsoluteLocations);
198         if (options & Verbose)
199             printOut(LU::tr("Updating '%1'...\n").arg(fn));
200
201         UpdateOptions theseOptions = options;
202         if (tor.locationsType() == Translator::NoLocations) // Could be set from file
203             theseOptions |= NoLocations;
204         Translator out = merge(tor, fetchedTor, theseOptions, err);
205         if (setCodec)
206             out.setCodec(fetchedTor.codec());
207
208         if ((options & Verbose) && !err.isEmpty()) {
209             printOut(err);
210             err.clear();
211         }
212         if (options & PluralOnly) {
213             if (options & Verbose)
214                 printOut(LU::tr("Stripping non plural forms in '%1'...\n").arg(fn));
215             out.stripNonPluralForms();
216         }
217         if (options & NoObsolete)
218             out.stripObsoleteMessages();
219         out.stripEmptyContexts();
220
221         out.normalizeTranslations(cd);
222         if (!cd.errors().isEmpty()) {
223             printErr(cd.error());
224             cd.clearErrors();
225         }
226         if (!out.save(fileName, cd, QLatin1String("auto"))) {
227             printErr(cd.error());
228             *fail = true;
229         }
230     }
231 }
232
233 static void print(const QString &fileName, int lineNo, const QString &msg)
234 {
235     if (lineNo)
236         printErr(QString::fromLatin1("%2(%1): %3").arg(lineNo).arg(fileName, msg));
237     else
238         printErr(msg);
239 }
240
241 class ParseHandler : public ProFileParserHandler {
242 public:
243     virtual void parseError(const QString &fileName, int lineNo, const QString &msg)
244         { if (verbose) print(fileName, lineNo, msg); }
245
246     bool verbose;
247 };
248
249 class EvalHandler : public ProFileEvaluatorHandler {
250 public:
251     virtual void configError(const QString &msg)
252         { printErr(msg); }
253     virtual void evalError(const QString &fileName, int lineNo, const QString &msg)
254         { if (verbose) print(fileName, lineNo, msg); }
255     virtual void fileMessage(const QString &msg)
256         { printErr(msg); }
257
258     virtual void aboutToEval(ProFile *, ProFile *, EvalFileType) {}
259     virtual void doneWithEval(ProFile *) {}
260
261     bool verbose;
262 };
263
264 static ParseHandler parseHandler;
265 static EvalHandler evalHandler;
266
267 static QStringList getSources(const char *var, const char *vvar, const QStringList &baseVPaths,
268                               const QString &projectDir, const ProFileEvaluator &visitor)
269 {
270     QStringList vPaths = visitor.absolutePathValues(QLatin1String(vvar), projectDir);
271     vPaths += baseVPaths;
272     vPaths.removeDuplicates();
273     return visitor.absoluteFileValues(QLatin1String(var), projectDir, vPaths, 0);
274 }
275
276 static QStringList getSources(const ProFileEvaluator &visitor, const QString &projectDir)
277 {
278     QStringList baseVPaths;
279     baseVPaths += visitor.absolutePathValues(QLatin1String("VPATH"), projectDir);
280     baseVPaths << projectDir; // QMAKE_ABSOLUTE_SOURCE_PATH
281     baseVPaths += visitor.absolutePathValues(QLatin1String("DEPENDPATH"), projectDir);
282     baseVPaths.removeDuplicates();
283
284     QStringList sourceFiles;
285
286     // app/lib template
287     sourceFiles += getSources("SOURCES", "VPATH_SOURCES", baseVPaths, projectDir, visitor);
288
289     sourceFiles += getSources("FORMS", "VPATH_FORMS", baseVPaths, projectDir, visitor);
290     sourceFiles += getSources("FORMS3", "VPATH_FORMS3", baseVPaths, projectDir, visitor);
291
292     QStringList vPathsInc = baseVPaths;
293     vPathsInc += visitor.absolutePathValues(QLatin1String("INCLUDEPATH"), projectDir);
294     vPathsInc.removeDuplicates();
295     sourceFiles += visitor.absoluteFileValues(QLatin1String("HEADERS"), projectDir, vPathsInc, 0);
296
297     sourceFiles.removeDuplicates();
298     sourceFiles.sort();
299
300     return sourceFiles;
301 }
302
303 static void processSources(Translator &fetchedTor,
304                            const QStringList &sourceFiles, ConversionData &cd)
305 {
306     QStringList sourceFilesCpp;
307     for (QStringList::const_iterator it = sourceFiles.begin(); it != sourceFiles.end(); ++it) {
308         if (it->endsWith(QLatin1String(".java"), Qt::CaseInsensitive))
309             loadJava(fetchedTor, *it, cd);
310         else if (it->endsWith(QLatin1String(".ui"), Qt::CaseInsensitive)
311                  || it->endsWith(QLatin1String(".jui"), Qt::CaseInsensitive))
312             loadUI(fetchedTor, *it, cd);
313         else if (it->endsWith(QLatin1String(".js"), Qt::CaseInsensitive)
314                  || it->endsWith(QLatin1String(".qs"), Qt::CaseInsensitive))
315             loadQScript(fetchedTor, *it, cd);
316         else if (it->endsWith(QLatin1String(".qml"), Qt::CaseInsensitive))
317             loadQml(fetchedTor, *it, cd);
318         else
319             sourceFilesCpp << *it;
320     }
321     loadCPP(fetchedTor, sourceFilesCpp, cd);
322     if (!cd.error().isEmpty())
323         printErr(cd.error());
324 }
325
326 static void processProjects(
327         bool topLevel, bool nestComplain, const QStringList &proFiles,
328         ProFileOption *option, ProFileParser *parser,
329         UpdateOptions options, const QByteArray &codecForSource,
330         const QString &targetLanguage, const QString &sourceLanguage,
331         Translator *parentTor, bool *fail);
332
333 static void processProject(
334         bool nestComplain, const QFileInfo &pfi,
335         ProFileOption *option, ProFileParser *parser, ProFileEvaluator &visitor,
336         UpdateOptions options, const QByteArray &_codecForSource,
337         const QString &targetLanguage, const QString &sourceLanguage,
338         Translator *fetchedTor, bool *fail)
339 {
340     QByteArray codecForSource = _codecForSource;
341     QStringList tmp = visitor.values(QLatin1String("CODECFORSRC"));
342     if (!tmp.isEmpty()) {
343         codecForSource = tmp.last().toLatin1();
344         if (!QTextCodec::codecForName(codecForSource)) {
345             printErr(LU::tr("lupdate warning: Codec for source '%1' is invalid."
346                             " Falling back to codec for tr().\n")
347                      .arg(QString::fromLatin1(codecForSource)));
348             codecForSource.clear();
349         }
350     }
351     if (visitor.templateType() == ProFileEvaluator::TT_Subdirs) {
352         QStringList subProFiles;
353         QDir proDir(pfi.absoluteDir());
354         foreach (const QString &subdir, visitor.values(QLatin1String("SUBDIRS"))) {
355             QString subPro = QDir::cleanPath(proDir.absoluteFilePath(subdir));
356             QFileInfo subInfo(subPro);
357             if (subInfo.isDir())
358                 subProFiles << (subPro + QLatin1Char('/')
359                                 + subInfo.fileName() + QLatin1String(".pro"));
360             else
361                 subProFiles << subPro;
362         }
363         processProjects(false, nestComplain, subProFiles, option, parser, options, codecForSource,
364                         targetLanguage, sourceLanguage, fetchedTor, fail);
365     } else {
366         ConversionData cd;
367         cd.m_noUiLines = options & NoUiLines;
368         cd.m_codecForSource = codecForSource;
369         cd.m_includePath = visitor.values(QLatin1String("INCLUDEPATH"));
370         QStringList sourceFiles = getSources(visitor, pfi.absolutePath());
371         QSet<QString> sourceDirs;
372         sourceDirs.insert(QDir::cleanPath(pfi.absolutePath()) + QLatin1Char('/'));
373         foreach (const QString &sf, sourceFiles)
374             sourceDirs.insert(sf.left(sf.lastIndexOf(QLatin1Char('/')) + 1));
375         QStringList rootList = sourceDirs.toList();
376         rootList.sort();
377         for (int prev = 0, curr = 1; curr < rootList.length(); )
378             if (rootList.at(curr).startsWith(rootList.at(prev)))
379                 rootList.removeAt(curr);
380             else
381                 prev = curr++;
382         cd.m_projectRoots = QSet<QString>::fromList(rootList);
383         processSources(*fetchedTor, sourceFiles, cd);
384     }
385 }
386
387 static void processProjects(
388         bool topLevel, bool nestComplain, const QStringList &proFiles,
389         ProFileOption *option, ProFileParser *parser,
390         UpdateOptions options, const QByteArray &codecForSource,
391         const QString &targetLanguage, const QString &sourceLanguage,
392         Translator *parentTor, bool *fail)
393 {
394     foreach (const QString &proFile, proFiles) {
395         QFileInfo pfi(proFile);
396
397         ProFileEvaluator visitor(option, parser, &evalHandler);
398         ProFile *pro;
399         if (!(pro = parser->parsedProFile(QDir::cleanPath(pfi.absoluteFilePath())))) {
400             if (topLevel)
401                 *fail = true;
402             continue;
403         }
404         if (!visitor.accept(pro)) {
405             if (topLevel)
406                 *fail = true;
407             pro->deref();
408             continue;
409         }
410
411         if (visitor.contains(QLatin1String("TRANSLATIONS"))) {
412             if (parentTor) {
413                 if (topLevel) {
414                     printErr(LU::tr("lupdate warning: TS files from command line "
415                                     "will override TRANSLATIONS in %1.\n").arg(proFile));
416                     goto noTrans;
417                 } else if (nestComplain) {
418                     printErr(LU::tr("lupdate warning: TS files from command line "
419                                     "prevent recursing into %1.\n").arg(proFile));
420                     pro->deref();
421                     continue;
422                 }
423             }
424             QStringList tsFiles;
425             QDir proDir(pfi.absolutePath());
426             foreach (const QString &tsFile, visitor.values(QLatin1String("TRANSLATIONS")))
427                 tsFiles << QFileInfo(proDir, tsFile).filePath();
428             if (tsFiles.isEmpty()) {
429                 // This might mean either a buggy PRO file or an intentional detach -
430                 // we can't know without seeing the actual RHS of the assignment ...
431                 // Just assume correctness and be silent.
432                 pro->deref();
433                 continue;
434             }
435             Translator tor;
436             bool setCodec = false;
437             QStringList tmp = visitor.values(QLatin1String("CODEC"))
438                               + visitor.values(QLatin1String("DEFAULTCODEC"))
439                               + visitor.values(QLatin1String("CODECFORTR"));
440             if (!tmp.isEmpty()) {
441                 tor.setCodecName(tmp.last().toLatin1());
442                 setCodec = true;
443             }
444             processProject(false, pfi, option, parser, visitor, options, codecForSource,
445                            targetLanguage, sourceLanguage, &tor, fail);
446             updateTsFiles(tor, tsFiles, setCodec, sourceLanguage, targetLanguage, options, fail);
447             pro->deref();
448             continue;
449         }
450       noTrans:
451         if (!parentTor) {
452             if (topLevel)
453                 printErr(LU::tr("lupdate warning: no TS files specified. Only diagnostics "
454                                 "will be produced for '%1'.\n").arg(proFile));
455             Translator tor;
456             processProject(nestComplain, pfi, option, parser, visitor, options, codecForSource,
457                            targetLanguage, sourceLanguage, &tor, fail);
458         } else {
459             processProject(nestComplain, pfi, option, parser, visitor, options, codecForSource,
460                            targetLanguage, sourceLanguage, parentTor, fail);
461         }
462         pro->deref();
463     }
464 }
465
466 int main(int argc, char **argv)
467 {
468     QCoreApplication app(argc, argv);
469 #ifndef Q_OS_WIN32
470     QTranslator translator;
471     QTranslator qtTranslator;
472     QString sysLocale = QLocale::system().name();
473     QString resourceDir = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
474     if (translator.load(QLatin1String("linguist_") + sysLocale, resourceDir)
475         && qtTranslator.load(QLatin1String("qt_") + sysLocale, resourceDir)) {
476         app.installTranslator(&translator);
477         app.installTranslator(&qtTranslator);
478     }
479 #endif // Q_OS_WIN32
480
481     m_defaultExtensions = QLatin1String("java,jui,ui,c,c++,cc,cpp,cxx,ch,h,h++,hh,hpp,hxx,js,qs,qml");
482
483     QStringList args = app.arguments();
484     QStringList tsFileNames;
485     QStringList proFiles;
486     QMultiHash<QString, QString> allCSources;
487     QSet<QString> projectRoots;
488     QStringList sourceFiles;
489     QStringList includePath;
490     QString targetLanguage;
491     QString sourceLanguage;
492     QByteArray codecForTr;
493
494     UpdateOptions options =
495         Verbose | // verbose is on by default starting with Qt 4.2
496         HeuristicSameText | HeuristicSimilarText | HeuristicNumber;
497     int numFiles = 0;
498     bool metTsFlag = false;
499     bool recursiveScan = true;
500
501     QString extensions = m_defaultExtensions;
502     QSet<QString> extensionsNameFilters;
503
504     for (int i = 1; i < argc; ++i) {
505         QString arg = args.at(i);
506         if (arg == QLatin1String("-help")
507                 || arg == QLatin1String("--help")
508                 || arg == QLatin1String("-h")) {
509             printUsage();
510             return 0;
511         } else if (arg == QLatin1String("-list-languages")) {
512             printOut(getNumerusInfoString());
513             return 0;
514         } else if (arg == QLatin1String("-pluralonly")) {
515             options |= PluralOnly;
516             continue;
517         } else if (arg == QLatin1String("-noobsolete")
518                 || arg == QLatin1String("-no-obsolete")) {
519             options |= NoObsolete;
520             continue;
521         } else if (arg == QLatin1String("-silent")) {
522             options &= ~Verbose;
523             continue;
524         } else if (arg == QLatin1String("-target-language")) {
525             ++i;
526             if (i == argc) {
527                 printErr(LU::tr("The option -target-language requires a parameter.\n"));
528                 return 1;
529             }
530             targetLanguage = args[i];
531             continue;
532         } else if (arg == QLatin1String("-source-language")) {
533             ++i;
534             if (i == argc) {
535                 printErr(LU::tr("The option -source-language requires a parameter.\n"));
536                 return 1;
537             }
538             sourceLanguage = args[i];
539             continue;
540         } else if (arg == QLatin1String("-disable-heuristic")) {
541             ++i;
542             if (i == argc) {
543                 printErr(LU::tr("The option -disable-heuristic requires a parameter.\n"));
544                 return 1;
545             }
546             arg = args[i];
547             if (arg == QLatin1String("sametext")) {
548                 options &= ~HeuristicSameText;
549             } else if (arg == QLatin1String("similartext")) {
550                 options &= ~HeuristicSimilarText;
551             } else if (arg == QLatin1String("number")) {
552                 options &= ~HeuristicNumber;
553             } else {
554                 printErr(LU::tr("Invalid heuristic name passed to -disable-heuristic.\n"));
555                 return 1;
556             }
557             continue;
558         } else if (arg == QLatin1String("-locations")) {
559             ++i;
560             if (i == argc) {
561                 printErr(LU::tr("The option -locations requires a parameter.\n"));
562                 return 1;
563             }
564             if (args[i] == QLatin1String("none")) {
565                 options |= NoLocations;
566             } else if (args[i] == QLatin1String("relative")) {
567                 options |= RelativeLocations;
568             } else if (args[i] == QLatin1String("absolute")) {
569                 options |= AbsoluteLocations;
570             } else {
571                 printErr(LU::tr("Invalid parameter passed to -locations.\n"));
572                 return 1;
573             }
574             continue;
575         } else if (arg == QLatin1String("-no-ui-lines")) {
576             options |= NoUiLines;
577             continue;
578         } else if (arg == QLatin1String("-verbose")) {
579             options |= Verbose;
580             continue;
581         } else if (arg == QLatin1String("-no-recursive")) {
582             recursiveScan = false;
583             continue;
584         } else if (arg == QLatin1String("-recursive")) {
585             recursiveScan = true;
586             continue;
587         } else if (arg == QLatin1String("-no-sort")
588                    || arg == QLatin1String("-nosort")) {
589             options |= NoSort;
590             continue;
591         } else if (arg == QLatin1String("-version")) {
592             printOut(QObject::tr("lupdate version %1\n").arg(QLatin1String(QT_VERSION_STR)));
593             return 0;
594         } else if (arg == QLatin1String("-codecfortr")) {
595             ++i;
596             if (i == argc) {
597                 printErr(LU::tr("The -codecfortr option should be followed by a codec name.\n"));
598                 return 1;
599             }
600             codecForTr = args[i].toLatin1();
601             continue;
602         } else if (arg == QLatin1String("-ts")) {
603             metTsFlag = true;
604             continue;
605         } else if (arg == QLatin1String("-extensions")) {
606             ++i;
607             if (i == argc) {
608                 printErr(LU::tr("The -extensions option should be followed by an extension list.\n"));
609                 return 1;
610             }
611             extensions = args[i];
612             continue;
613         } else if (arg == QLatin1String("-pro")) {
614             ++i;
615             if (i == argc) {
616                 printErr(LU::tr("The -pro option should be followed by a filename of .pro file.\n"));
617                 return 1;
618             }
619             proFiles += args[i];
620             numFiles++;
621             continue;
622         } else if (arg.startsWith(QLatin1String("-I"))) {
623             if (arg.length() == 2) {
624                 ++i;
625                 if (i == argc) {
626                     printErr(LU::tr("The -I option should be followed by a path.\n"));
627                     return 1;
628                 }
629                 includePath += args[i];
630             } else {
631                 includePath += args[i].mid(2);
632             }
633             continue;
634         } else if (arg.startsWith(QLatin1String("-")) && arg != QLatin1String("-")) {
635             printErr(LU::tr("Unrecognized option '%1'.\n").arg(arg));
636             return 1;
637         }
638
639         QStringList files;
640         if (arg.startsWith(QLatin1String("@"))) {
641             QFile lstFile(arg.mid(1));
642             if (!lstFile.open(QIODevice::ReadOnly)) {
643                 printErr(LU::tr("lupdate error: List file '%1' is not readable.\n")
644                          .arg(lstFile.fileName()));
645                 return 1;
646             }
647             while (!lstFile.atEnd())
648                 files << QString::fromLocal8Bit(lstFile.readLine().trimmed());
649         } else {
650             files << arg;
651         }
652         if (metTsFlag) {
653             foreach (const QString &file, files) {
654                 bool found = false;
655                 foreach (const Translator::FileFormat &fmt, Translator::registeredFileFormats()) {
656                     if (file.endsWith(QLatin1Char('.') + fmt.extension, Qt::CaseInsensitive)) {
657                         QFileInfo fi(file);
658                         if (!fi.exists() || fi.isWritable()) {
659                             tsFileNames.append(QFileInfo(file).absoluteFilePath());
660                         } else {
661                             printErr(LU::tr("lupdate warning: For some reason, '%1' is not writable.\n")
662                                      .arg(file));
663                         }
664                         found = true;
665                         break;
666                     }
667                 }
668                 if (!found) {
669                     printErr(LU::tr("lupdate error: File '%1' has no recognized extension.\n")
670                              .arg(file));
671                     return 1;
672                 }
673             }
674             numFiles++;
675         } else {
676             foreach (const QString &file, files) {
677                 QFileInfo fi(file);
678                 if (!fi.exists()) {
679                     printErr(LU::tr("lupdate error: File '%1' does not exist.\n").arg(file));
680                     return 1;
681                 }
682                 if (file.endsWith(QLatin1String(".pro"), Qt::CaseInsensitive)
683                     || file.endsWith(QLatin1String(".pri"), Qt::CaseInsensitive)) {
684                     proFiles << file;
685                 } else if (fi.isDir()) {
686                     if (options & Verbose)
687                         printOut(LU::tr("Scanning directory '%1'...\n").arg(file));
688                     QDir dir = QDir(fi.filePath());
689                     projectRoots.insert(dir.absolutePath() + QLatin1Char('/'));
690                     if (extensionsNameFilters.isEmpty()) {
691                         foreach (QString ext, extensions.split(QLatin1Char(','))) {
692                             ext = ext.trimmed();
693                             if (ext.startsWith(QLatin1Char('.')))
694                                 ext.remove(0, 1);
695                             extensionsNameFilters.insert(ext);
696                         }
697                     }
698                     QDir::Filters filters = QDir::Files | QDir::NoSymLinks;
699                     if (recursiveScan)
700                         filters |= QDir::AllDirs | QDir::NoDotAndDotDot;
701                     QFileInfoList fileinfolist;
702                     recursiveFileInfoList(dir, extensionsNameFilters, filters, &fileinfolist);
703                     int scanRootLen = dir.absolutePath().length();
704                     foreach (const QFileInfo &fi, fileinfolist) {
705                         QString fn = QDir::cleanPath(fi.absoluteFilePath());
706                         sourceFiles << fn;
707
708                         if (!fn.endsWith(QLatin1String(".java"))
709                             && !fn.endsWith(QLatin1String(".jui"))
710                             && !fn.endsWith(QLatin1String(".ui"))
711                             && !fn.endsWith(QLatin1String(".js"))
712                             && !fn.endsWith(QLatin1String(".qs"))
713                             && !fn.endsWith(QLatin1String(".qml"))) {
714                             int offset = 0;
715                             int depth = 0;
716                             do {
717                                 offset = fn.lastIndexOf(QLatin1Char('/'), offset - 1);
718                                 QString ffn = fn.mid(offset + 1);
719                                 allCSources.insert(ffn, fn);
720                             } while (++depth < 3 && offset > scanRootLen);
721                         }
722                     }
723                 } else {
724                     sourceFiles << QDir::cleanPath(fi.absoluteFilePath());;
725                     projectRoots.insert(fi.absolutePath() + QLatin1Char('/'));
726                 }
727             }
728             numFiles++;
729         }
730     } // for args
731
732     if (numFiles == 0) {
733         printUsage();
734         return 1;
735     }
736
737     if (!targetLanguage.isEmpty() && tsFileNames.count() != 1)
738         printErr(LU::tr("lupdate warning: -target-language usually only"
739                         " makes sense with exactly one TS file.\n"));
740     if (!codecForTr.isEmpty() && tsFileNames.isEmpty())
741         printErr(LU::tr("lupdate warning: -codecfortr has no effect without -ts.\n"));
742
743     bool fail = false;
744     if (proFiles.isEmpty()) {
745         if (tsFileNames.isEmpty())
746             printErr(LU::tr("lupdate warning:"
747                             " no TS files specified. Only diagnostics will be produced.\n"));
748
749         Translator fetchedTor;
750         ConversionData cd;
751         cd.m_noUiLines = options & NoUiLines;
752         cd.m_projectRoots = projectRoots;
753         cd.m_includePath = includePath;
754         cd.m_allCSources = allCSources;
755         fetchedTor.setCodecName(codecForTr);
756         processSources(fetchedTor, sourceFiles, cd);
757         updateTsFiles(fetchedTor, tsFileNames, !codecForTr.isEmpty(),
758                       sourceLanguage, targetLanguage, options, &fail);
759     } else {
760         if (!sourceFiles.isEmpty() || !includePath.isEmpty()) {
761             printErr(LU::tr("lupdate error:"
762                             " Both project and source files / include paths specified.\n"));
763             return 1;
764         }
765
766         parseHandler.verbose = evalHandler.verbose = !!(options & Verbose);
767         ProFileOption option;
768         option.initProperties(app.applicationDirPath() + QLatin1String("/qmake"));
769         option.setCommandLineArguments(QStringList() << QLatin1String("CONFIG+=lupdate_run"));
770         ProFileParser parser(0, &parseHandler);
771
772         if (!tsFileNames.isEmpty()) {
773             Translator fetchedTor;
774             fetchedTor.setCodecName(codecForTr);
775             processProjects(true, true, proFiles, &option, &parser, options, QByteArray(),
776                             targetLanguage, sourceLanguage, &fetchedTor, &fail);
777             updateTsFiles(fetchedTor, tsFileNames, !codecForTr.isEmpty(),
778                           sourceLanguage, targetLanguage, options, &fail);
779         } else {
780             processProjects(true, false, proFiles, &option, &parser, options, QByteArray(),
781                             targetLanguage, sourceLanguage, 0, &fail);
782         }
783     }
784     return fail ? 1 : 0;
785 }