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