1// Copyright (C) 2017-2020 Ford Motor Company
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include <qcommandlineoption.h>
5#include <qcommandlineparser.h>
6#include <qcoreapplication.h>
7#include <qfileinfo.h>
8#include <qjsondocument.h>
9#include <qjsonobject.h>
10#include <qjsonarray.h>
11#include <qhashfunctions.h>
12
13#include "cppcodegenerator.h"
14#include "repcodegenerator.h"
15#include "repparser.h"
16#include "utils.h"
17
18#include <cstdio>
19
20#define PROGRAM_NAME "repc"
21#define REPC_VERSION "1.0.0"
22
23enum Mode {
24 InRep = 1,
25 InJson = 2,
26 OutRep = 4,
27 OutSource = 8,
28 OutReplica = 16,
29 OutMerged = OutSource | OutReplica
30};
31
32static const QLatin1String REP("rep");
33
34QT_USE_NAMESPACE
35
36int main(int argc, char **argv)
37{
38 QHashSeed::setDeterministicGlobalSeed();
39
40 QCoreApplication app(argc, argv);
41 QCoreApplication::setApplicationVersion(QString::fromLatin1(REPC_VERSION));
42
43 QString outputFile;
44 QString inputFile;
45 int mode = 0;
46 QCommandLineParser parser;
47 parser.setApplicationDescription(QStringLiteral("repc tool v%1 (Qt %2).\n")
48 .arg(QStringLiteral(REPC_VERSION), args: QString::fromLatin1(QT_VERSION_STR)));
49 parser.addHelpOption();
50 parser.addVersionOption();
51 QCommandLineOption inputTypeOption(QStringLiteral("i"));
52 inputTypeOption.setDescription(QLatin1String("Input file type:\n"
53 "rep: replicant template files.\n"
54 "json: JSON output from moc of a Qt header file."));
55 inputTypeOption.setValueName(QStringLiteral("rep|json"));
56 parser.addOption(commandLineOption: inputTypeOption);
57
58 QCommandLineOption outputTypeOption(QStringLiteral("o"));
59 outputTypeOption.setDescription(QLatin1String("Output file type:\n"
60 "source: generates source header. Is incompatible with \"-i src\" option.\n"
61 "replica: generates replica header.\n"
62 "merged: generates combined replica/source header.\n"
63 "rep: generates replicant template file from C++ QOject classes. Is not compatible with \"-i rep\" option."));
64 outputTypeOption.setValueName(QStringLiteral("source|replica|merged|rep"));
65 parser.addOption(commandLineOption: outputTypeOption);
66
67 QCommandLineOption includePathOption(QStringLiteral("I"));
68 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)."));
69 includePathOption.setValueName(QStringLiteral("dir"));
70 parser.addOption(commandLineOption: includePathOption);
71
72 QCommandLineOption alwaysClassOption(QStringLiteral("c"));
73 alwaysClassOption.setDescription(QStringLiteral("Always output `class` type for .rep files and never `POD`."));
74 parser.addOption(commandLineOption: alwaysClassOption);
75
76 QCommandLineOption debugOption(QStringLiteral("d"));
77 debugOption.setDescription(QStringLiteral("Print out parsing debug information (for troubleshooting)."));
78 parser.addOption(commandLineOption: debugOption);
79
80 parser.addPositionalArgument(QStringLiteral("[json-file/rep-file]"),
81 QStringLiteral("Input json/rep file to read from, otherwise stdin."));
82
83 parser.addPositionalArgument(QStringLiteral("[rep-file/header-file]"),
84 QStringLiteral("Output header/rep file to write to, otherwise stdout."));
85
86 parser.process(arguments: app.arguments());
87
88 const QStringList files = parser.positionalArguments();
89
90 if (files.size() > 2) {
91 fprintf(stderr, format: "%s", qPrintable(QLatin1String(PROGRAM_NAME ": Too many input, output files specified: '") + files.join(QStringLiteral("' '")) + QStringLiteral("\'.\n")));
92 parser.showHelp(exitCode: 1);
93 }
94
95 if (parser.isSet(option: inputTypeOption)) {
96 const QString &inputType = parser.value(option: inputTypeOption);
97 if (inputType == REP)
98 mode = InRep;
99 else if (inputType == u"json")
100 mode = InJson;
101 else {
102 fprintf(stderr, PROGRAM_NAME ": Unknown input type\"%s\".\n", qPrintable(inputType));
103 parser.showHelp(exitCode: 1);
104 }
105 }
106
107 if (parser.isSet(option: outputTypeOption)) {
108 const QString &outputType = parser.value(option: outputTypeOption);
109 if (outputType == REP)
110 mode |= OutRep;
111 else if (outputType == u"replica")
112 mode |= OutReplica;
113 else if (outputType == u"source")
114 mode |= OutSource;
115 else if (outputType == u"merged")
116 mode |= OutMerged;
117 else {
118 fprintf(stderr, PROGRAM_NAME ": Unknown output type\"%s\".\n", qPrintable(outputType));
119 parser.showHelp(exitCode: 1);
120 }
121 }
122
123 switch (files.size()) {
124 case 2:
125 outputFile = files.last();
126 if (!(mode & (OutRep | OutSource | OutReplica))) {
127 // try to figure out the Out mode from file extension
128 if (outputFile.endsWith(s: QLatin1String(".rep")))
129 mode |= OutRep;
130 }
131 Q_FALLTHROUGH();
132 case 1:
133 inputFile = files.first();
134 if (!(mode & (InRep | InJson))) {
135 // try to figure out the In mode from file extension
136 if (inputFile.endsWith(s: QLatin1String(".rep")))
137 mode |= InRep;
138 else
139 mode |= InJson;
140 }
141 break;
142 }
143 // check mode sanity
144 if (!(mode & (InRep | InJson))) {
145 fprintf(stderr, PROGRAM_NAME ": Unknown input type, please use -i option to specify one.\n");
146 parser.showHelp(exitCode: 1);
147 }
148 if (!(mode & (OutRep | OutSource | OutReplica))) {
149 fprintf(stderr, PROGRAM_NAME ": Unknown output type, please use -o option to specify one.\n");
150 parser.showHelp(exitCode: 1);
151 }
152 if (mode & InRep && mode & OutRep) {
153 fprintf(stderr, PROGRAM_NAME ": Invalid input/output type combination, both are rep files.\n");
154 parser.showHelp(exitCode: 1);
155 }
156 if (mode & InJson && mode & OutSource) {
157 fprintf(stderr, PROGRAM_NAME ": Invalid input/output type combination, both are source header files.\n");
158 parser.showHelp(exitCode: 1);
159 }
160
161 QFile input;
162 if (inputFile.isEmpty()) {
163 inputFile = QStringLiteral("<stdin>");
164 if (!input.open(stdin, ioFlags: QIODevice::ReadOnly)) {
165 fprintf(stderr, PROGRAM_NAME ": Failed to open stdin for reading: %s\n",
166 qPrintable(input.errorString()));
167 return 1;
168 }
169 } else {
170 input.setFileName(inputFile);
171 if (!input.open(flags: QIODevice::ReadOnly)) {
172 fprintf(stderr, PROGRAM_NAME ": %s: No such file.\n", qPrintable(inputFile));
173 return 1;
174 }
175 }
176
177 QFile output;
178 if (outputFile.isEmpty()) {
179 if (!output.open(stdout, ioFlags: QIODevice::WriteOnly)) {
180 fprintf(stderr, PROGRAM_NAME ": could not open output file '%s': %s.\n",
181 qPrintable(outputFile), qPrintable(output.errorString()));
182 return 1;
183 }
184 } else {
185 output.setFileName(outputFile);
186 if (!output.open(flags: QIODevice::WriteOnly)) {
187 fprintf(stderr, PROGRAM_NAME ": could not open output file '%s': %s.\n",
188 qPrintable(outputFile), qPrintable(output.errorString()));
189 return 1;
190 }
191 }
192
193 if (mode & InJson) {
194 QJsonDocument doc(QJsonDocument::fromJson(json: input.readAll()));
195 input.close();
196 if (!doc.isObject()) {
197 fprintf(stderr, PROGRAM_NAME ": Unable to read json input.\n");
198 return 0;
199 }
200
201 QJsonObject json = doc.object();
202
203 if (!json.contains(key: QLatin1String("classes")) || !json[QLatin1String("classes")].isArray()) {
204 fprintf(stderr, PROGRAM_NAME ": No QObject classes found.\n");
205 return 0;
206 }
207
208 QJsonArray classes = json[QLatin1String("classes")].toArray();
209
210 if (mode & OutRep) {
211 CppCodeGenerator generator(&output);
212 generator.generate(classList: classes, alwaysGenerateClass: parser.isSet(option: alwaysClassOption));
213 } else {
214 Q_ASSERT(mode & OutReplica);
215 RepCodeGenerator generator(&output, classList2AST(classes));
216 generator.generate(mode: RepCodeGenerator::REPLICA, fileName: outputFile);
217 }
218 } else {
219 Q_ASSERT(!(mode & OutRep));
220 RepParser repparser(input);
221 if (parser.isSet(option: debugOption))
222 repparser.setDebug();
223 if (!repparser.parse()) {
224 fprintf(stderr, PROGRAM_NAME ": %s:%d: error: %s\n", qPrintable(inputFile), repparser.lineNumber(), qPrintable(repparser.errorString()));
225 // if everything is okay and only the input was malformed => remove the output file
226 // let's not create an empty file -- make sure the build system tries to run repc again
227 // this is the same behavior other code generators exhibit (e.g. flex)
228 output.remove();
229 return 1;
230 }
231
232 input.close();
233
234 RepCodeGenerator generator(&output, repparser.ast());
235 if ((mode & OutMerged) == OutMerged)
236 generator.generate(mode: RepCodeGenerator::MERGED, fileName: outputFile);
237 else if (mode & OutReplica)
238 generator.generate(mode: RepCodeGenerator::REPLICA, fileName: outputFile);
239 else if (mode & OutSource)
240 generator.generate(mode: RepCodeGenerator::SOURCE, fileName: outputFile);
241 else {
242 fprintf(stderr, PROGRAM_NAME ": Unknown mode.\n");
243 return 1;
244 }
245 }
246
247 output.close();
248 return 0;
249}
250

source code of qtremoteobjects/tools/repc/main.cpp