/**************************************************************************** ** ** Copyright (C) 2017-2020 Ford Motor Company ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtRemoteObjects module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include "cppcodegenerator.h" #include "pythoncodegenerator.h" #include "repcodegenerator.h" #include "repparser.h" #include "utils.h" #include #define PROGRAM_NAME "repc" #define REPC_VERSION "1.0.0" enum Mode { InRep = 1, InJson = 2, OutRep = 4, OutSource = 8, OutReplica = 16, OutPython = 32, OutPyside = 64, OutMerged = OutSource | OutReplica, OutPyVariant = OutPython | OutPyside, OutSelected = OutRep | OutMerged | OutPyVariant }; static const QLatin1String REP("rep"); static const QLatin1String JSON("json"); static const QLatin1String PY("py"); static const QLatin1String PYTHON("python"); static const QLatin1String PYSIDE("pyside"); static const QLatin1String REPLICA("replica"); static const QLatin1String SOURCE("source"); static const QLatin1String MERGED("merged"); QT_USE_NAMESPACE int main(int argc, char **argv) { QCoreApplication app(argc, argv); QCoreApplication::setApplicationVersion(QString::fromLatin1(REPC_VERSION)); QString outputFile; QString inputFile; int mode = 0; QCommandLineParser parser; parser.setApplicationDescription(QStringLiteral("repc tool v%1 (Qt %2).\n") .arg(QStringLiteral(REPC_VERSION), QString::fromLatin1(QT_VERSION_STR))); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption inputTypeOption(QStringLiteral("i")); inputTypeOption.setDescription(QLatin1String("Input file type:\n" "rep: replicant template files.\n" "json: JSON output from moc of a Qt header file.")); inputTypeOption.setValueName(QStringLiteral("rep|json")); parser.addOption(inputTypeOption); QCommandLineOption outputTypeOption(QStringLiteral("o")); outputTypeOption.setDescription(QLatin1String("Output file type:\n" "source: generates source header. Is incompatible with \"-i src\" option.\n" "replica: generates replica header.\n" "merged: generates combined replica/source header.\n" "rep: generates replicant template file from C++ QOject classes. Is not compatible with \"-i rep\" option.")); outputTypeOption.setValueName(QStringLiteral("source|replica|merged|rep")); parser.addOption(outputTypeOption); QCommandLineOption includePathOption(QStringLiteral("I")); includePathOption.setDescription(QStringLiteral("Add dir to the include path for header files. This parameter is needed only if the input file type is src (.h file).")); includePathOption.setValueName(QStringLiteral("dir")); parser.addOption(includePathOption); QCommandLineOption alwaysClassOption(QStringLiteral("c")); alwaysClassOption.setDescription(QStringLiteral("Always output `class` type for .rep files and never `POD`.")); parser.addOption(alwaysClassOption); QCommandLineOption debugOption(QStringLiteral("d")); debugOption.setDescription(QStringLiteral("Print out parsing debug information (for troubleshooting).")); parser.addOption(debugOption); parser.addPositionalArgument(QStringLiteral("[json-file/rep-file]"), QStringLiteral("Input json/rep file to read from, otherwise stdin.")); parser.addPositionalArgument(QStringLiteral("[rep-file/header-file]"), QStringLiteral("Output header/rep file to write to, otherwise stdout.")); parser.process(app.arguments()); const QStringList files = parser.positionalArguments(); if (files.count() > 2) { fprintf(stderr, "%s", qPrintable(QLatin1String(PROGRAM_NAME ": Too many input, output files specified: '") + files.join(QStringLiteral("' '")) + QStringLiteral("\'.\n"))); parser.showHelp(1); } if (parser.isSet(inputTypeOption)) { const QString &inputType = parser.value(inputTypeOption); if (inputType == REP) mode = InRep; else if (inputType == JSON) mode = InJson; else { fprintf(stderr, PROGRAM_NAME ": Unknown input type\"%s\".\n", qPrintable(inputType)); parser.showHelp(1); } } if (parser.isSet(outputTypeOption)) { const QString &outputType = parser.value(outputTypeOption); if (outputType == REP) mode |= OutRep; else if (outputType == REPLICA) mode |= OutReplica; else if (outputType == SOURCE) mode |= OutSource; else if (outputType == MERGED) mode |= OutMerged; else if (outputType == PY || outputType == PYTHON) mode |= OutPython; else if (outputType == PYSIDE) mode |= OutPyside; else { fprintf(stderr, PROGRAM_NAME ": Unknown output type\"%s\".\n", qPrintable(outputType)); parser.showHelp(1); } } switch (files.count()) { case 2: outputFile = files.last(); if (!(mode & (OutRep | OutSource | OutReplica | OutPython | OutPyside))) { // try to figure out the Out mode from file extension if (outputFile.endsWith(QLatin1String(".rep"))) mode |= OutRep; if (outputFile.endsWith(QLatin1String(".py"))) mode |= OutPython; } Q_FALLTHROUGH(); case 1: inputFile = files.first(); if (!(mode & (InRep | InJson))) { // try to figure out the In mode from file extension if (inputFile.endsWith(QLatin1String(".rep"))) mode |= InRep; else mode |= InJson; } break; } // check mode sanity if (!(mode & (InRep | InJson))) { fprintf(stderr, PROGRAM_NAME ": Unknown input type, please use -i option to specify one.\n"); parser.showHelp(1); } if (!(mode & (OutSelected))) { fprintf(stderr, PROGRAM_NAME ": Unknown output type, please use -o option to specify one.\n"); parser.showHelp(1); } if (mode & InRep && mode & OutRep) { fprintf(stderr, PROGRAM_NAME ": Invalid input/output type combination, both are rep files.\n"); parser.showHelp(1); } if (mode & InJson && mode & OutSource) { fprintf(stderr, PROGRAM_NAME ": Invalid input/output type combination, both are source header files.\n"); parser.showHelp(1); } QFile input; if (inputFile.isEmpty()) { inputFile = QStringLiteral(""); input.open(stdin, QIODevice::ReadOnly); } else { input.setFileName(inputFile); if (!input.open(QIODevice::ReadOnly)) { fprintf(stderr, PROGRAM_NAME ": %s: No such file.\n", qPrintable(inputFile)); return 1; } } QFile output; if (outputFile.isEmpty()) { output.open(stdout, QIODevice::WriteOnly); } else { output.setFileName(outputFile); if (!output.open(QIODevice::WriteOnly)) { fprintf(stderr, PROGRAM_NAME ": could not open output file '%s': %s.\n", qPrintable(outputFile), qPrintable(output.errorString())); return 1; } } if (mode & InJson) { QJsonDocument doc(QJsonDocument::fromJson(input.readAll())); input.close(); if (!doc.isObject()) { fprintf(stderr, PROGRAM_NAME ": Unable to read json input.\n"); return 0; } QJsonObject json = doc.object(); if (!json.contains(QLatin1String("classes")) || !json[QLatin1String("classes")].isArray()) { fprintf(stderr, PROGRAM_NAME ": No QObject classes found.\n"); return 0; } QJsonArray classes = json[QLatin1String("classes")].toArray(); if (mode & OutRep) { CppCodeGenerator generator(&output); generator.generate(classes, parser.isSet(alwaysClassOption)); } else if (mode & OutReplica) { RepCodeGenerator generator(&output); generator.generate(classList2AST(classes), GeneratorBase::Mode::Replica, outputFile); } else if (mode & OutPyVariant) { PythonCodeGenerator generator(&output); if (mode & OutPython) generator.generate(classList2AST(classes), PythonCodeGenerator::OutputStyle::DataStream); else generator.generate(classList2AST(classes), PythonCodeGenerator::OutputStyle::PySide); } } else { Q_ASSERT(!(mode & OutRep)); RepParser repparser(input); if (parser.isSet(debugOption)) repparser.setDebug(); if (!repparser.parse()) { fprintf(stderr, PROGRAM_NAME ": %s:%d: error: %s\n", qPrintable(inputFile), repparser.lineNumber(), qPrintable(repparser.errorString())); // if everything is okay and only the input was malformed => remove the output file // let's not create an empty file -- make sure the build system tries to run repc again // this is the same behavior other code generators exhibit (e.g. flex) output.remove(); return 1; } input.close(); if (mode & OutPyVariant) { PythonCodeGenerator generator(&output); if (mode & OutPython) generator.generate(repparser.ast(), PythonCodeGenerator::OutputStyle::DataStream); else generator.generate(repparser.ast(), PythonCodeGenerator::OutputStyle::PySide); } else { RepCodeGenerator generator(&output); if ((mode & OutMerged) == OutMerged) generator.generate(repparser.ast(), GeneratorBase::Mode::Merged, outputFile); else if (mode & OutReplica) generator.generate(repparser.ast(), GeneratorBase::Mode::Replica, outputFile); else if (mode & OutSource) generator.generate(repparser.ast(), GeneratorBase::Mode::Source, outputFile); else { fprintf(stderr, PROGRAM_NAME ": Unknown mode.\n"); return 1; } } } output.close(); return 0; }