Revision control
++ // Copyright (C) 2017-2020 Ford Motor Company
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++
++ #include <qcommandlineoption.h>
++ #include <qcommandlineparser.h>
++ #include <qcoreapplication.h>
++ #include <qfileinfo.h>
++ #include <qjsondocument.h>
++ #include <qjsonobject.h>
++ #include <qjsonarray.h>
++ #include <qhashfunctions.h>
++
++ #include "cppcodegenerator.h"
++ #include "repcodegenerator.h"
++ #include "repparser.h"
++ #include "utils.h"
++
++ #include <cstdio>
++
++ #define PROGRAM_NAME "repc"
++ #define REPC_VERSION "1.0.0"
++
++ enum Mode {
++ InRep = 1,
++ InJson = 2,
++ OutRep = 4,
++ OutSource = 8,
++ OutReplica = 16,
++ OutMerged = OutSource | OutReplica
++ };
++
++ static const QLatin1String REP("rep");
++
++ QT_USE_NAMESPACE
++
++ int main(int argc, char **argv)
++ {
++ QHashSeed::setDeterministicGlobalSeed();
++
++ 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.size() > 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 == u"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 == u"replica")
++ mode |= OutReplica;
++ else if (outputType == u"source")
++ mode |= OutSource;
++ else if (outputType == u"merged")
++ mode |= OutMerged;
++ else {
++ fprintf(stderr, PROGRAM_NAME ": Unknown output type\"%s\".\n", qPrintable(outputType));
++ parser.showHelp(1);
++ }
++ }
++
++ switch (files.size()) {
++ case 2:
++ outputFile = files.last();
++ if (!(mode & (OutRep | OutSource | OutReplica))) {
++ // try to figure out the Out mode from file extension
++ if (outputFile.endsWith(QLatin1String(".rep")))
++ mode |= OutRep;
++ }
++ 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 & (OutRep | OutSource | OutReplica))) {
++ 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("<stdin>");
++ if (!input.open(stdin, QIODevice::ReadOnly)) {
++ fprintf(stderr, PROGRAM_NAME ": Failed to open stdin for reading: %s\n",
++ qPrintable(input.errorString()));
++ return 1;
++ }
++ } 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()) {
++ if (!output.open(stdout, QIODevice::WriteOnly)) {
++ fprintf(stderr, PROGRAM_NAME ": could not open output file '%s': %s.\n",
++ qPrintable(outputFile), qPrintable(output.errorString()));
++ return 1;
++ }
++ } 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 {
++ Q_ASSERT(mode & OutReplica);
++ RepCodeGenerator generator(&output, classList2AST(classes));
++ generator.generate(RepCodeGenerator::REPLICA, outputFile);
++ }
++ } 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();
++
++ RepCodeGenerator generator(&output, repparser.ast());
++ if ((mode & OutMerged) == OutMerged)
++ generator.generate(RepCodeGenerator::MERGED, outputFile);
++ else if (mode & OutReplica)
++ generator.generate(RepCodeGenerator::REPLICA, outputFile);
++ else if (mode & OutSource)
++ generator.generate(RepCodeGenerator::SOURCE, outputFile);
++ else {
++ fprintf(stderr, PROGRAM_NAME ": Unknown mode.\n");
++ return 1;
++ }
++ }
++
++ output.close();
++ return 0;
++ }