Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
projectdescriptionreader.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
5#include "fmt.h"
6
7#include <QtCore/qcoreapplication.h>
8#include <QtCore/qfile.h>
9#include <QtCore/qjsonarray.h>
10#include <QtCore/qjsondocument.h>
11#include <QtCore/qjsonobject.h>
12#include <QtCore/qset.h>
13
14#include <algorithm>
15#include <functional>
16
17using std::placeholders::_1;
18using namespace Qt::Literals::StringLiterals;
19
20class Validator
21{
22public:
23 Validator(QString *errorString)
25 {
26 }
27
28 bool isValidProjectDescription(const QJsonArray &projects)
29 {
30 return std::all_of(projects.begin(), projects.end(),
31 std::bind(&Validator::isValidProjectObject, this, _1));
32 }
33
34private:
35 bool isValidProject(const QJsonObject &project)
36 {
37 static const QSet<QString> requiredKeys = {
38 QStringLiteral("projectFile"),
39 };
40 static const QSet<QString> allowedKeys
41 = QSet<QString>(requiredKeys)
42 << QStringLiteral("codec")
43 << QStringLiteral("excluded")
44 << QStringLiteral("includePaths")
45 << QStringLiteral("sources")
46 << QStringLiteral("compileCommands")
47 << QStringLiteral("subProjects")
48 << QStringLiteral("translations");
49 QSet<QString> actualKeys;
50 for (auto it = project.constBegin(), end = project.constEnd(); it != end; ++it)
51 actualKeys.insert(it.key());
52 const QSet<QString> missingKeys = requiredKeys - actualKeys;
53 if (!missingKeys.isEmpty()) {
54 *m_errorString = FMT::tr("Missing keys in project description: %1.")
55 .arg(missingKeys.values().join(", "_L1));
56 return false;
57 }
58 const QSet<QString> unexpected = actualKeys - allowedKeys;
59 if (!unexpected.isEmpty()) {
60 *m_errorString = FMT::tr("Unexpected keys in project %1: %2")
61 .arg(project.value(QStringLiteral("projectFile")).toString(),
62 unexpected.values().join(", "_L1));
63 return false;
64 }
65 return isValidProjectDescription(project.value(QStringLiteral("subProjects")).toArray());
66 }
67
68 bool isValidProjectObject(const QJsonValue &v)
69 {
70 if (!v.isObject()) {
71 *m_errorString = FMT::tr("JSON object expected.");
72 return false;
73 }
74 return isValidProject(v.toObject());
75 }
76
77 QString *m_errorString;
78};
79
80static QJsonArray readRawProjectDescription(const QString &filePath, QString *errorString)
81{
82 errorString->clear();
83 QFile file(filePath);
84 if (!file.open(QIODevice::ReadOnly)) {
85 *errorString = FMT::tr("Cannot open project description file '%1'.\n")
86 .arg(filePath);
87 return {};
88 }
89 QJsonParseError parseError;
90 QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &parseError);
91 if (doc.isNull()) {
92 *errorString = FMT::tr("%1 in %2 at offset %3.\n")
93 .arg(parseError.errorString(), filePath)
94 .arg(parseError.offset);
95 return {};
96 }
97 QJsonArray result = doc.isArray() ? doc.array() : QJsonArray{doc.object()};
98 Validator validator(errorString);
99 if (!validator.isValidProjectDescription(result))
100 return {};
101 return result;
102}
103
105{
106public:
107 ProjectConverter(QString *errorString)
109 {
110 }
111
112 Projects convertProjects(const QJsonArray &rawProjects)
113 {
114 Projects result;
115 result.reserve(rawProjects.size());
116 for (const QJsonValue &rawProject : rawProjects) {
117 Project project = convertProject(rawProject);
118 if (!m_errorString.isEmpty())
119 break;
120 result.push_back(std::move(project));
121 }
122 return result;
123 }
124
125private:
126 Project convertProject(const QJsonValue &v)
127 {
128 if (!v.isObject())
129 return {};
130 Project result;
131 QJsonObject obj = v.toObject();
132 result.filePath = stringValue(obj, "projectFile"_L1);
133 result.codec = stringValue(obj, "codec"_L1);
134 result.excluded = wildcardsToRegExes(stringListValue(obj, "excluded"_L1));
135 result.includePaths = stringListValue(obj, "includePaths"_L1);
136 result.sources = stringListValue(obj, "sources"_L1);
137 if (obj.contains("translations"_L1))
138 result.translations = stringListValue(obj, "translations"_L1);
139 result.subProjects = convertProjects(obj.value("subProjects"_L1).toArray());
140 return result;
141 }
142
143 bool checkType(const QJsonValue &v, QJsonValue::Type t, const QString &key)
144 {
145 if (v.type() == t)
146 return true;
147 m_errorString = FMT::tr("Key %1 should be %2 but is %3.").arg(key, jsonTypeName(t),
148 jsonTypeName(v.type()));
149 return false;
150 }
151
152 static QString jsonTypeName(QJsonValue::Type t)
153 {
154 // ### If QJsonValue::Type was declared with Q_ENUM we could just query QMetaEnum.
155 switch (t) {
156 case QJsonValue::Null:
157 return QStringLiteral("null");
158 case QJsonValue::Bool:
159 return QStringLiteral("bool");
160 case QJsonValue::Double:
161 return QStringLiteral("double");
162 case QJsonValue::String:
163 return QStringLiteral("string");
164 case QJsonValue::Array:
165 return QStringLiteral("array");
166 case QJsonValue::Object:
167 return QStringLiteral("object");
168 case QJsonValue::Undefined:
169 return QStringLiteral("undefined");
170 }
171 return QStringLiteral("unknown");
172 }
173
174 static QVector<QRegularExpression> wildcardsToRegExes(const QStringList &wildcardPatterns)
175 {
176 QVector<QRegularExpression> result;
177 result.reserve(wildcardPatterns.size());
178 for (const QString &wildcardPattern : wildcardPatterns)
179 result.append(wildcardToRegEx(wildcardPattern));
180 return result;
181 }
182
183 // Return a QRegularExpression object for a TR_EXCLUDE / QT_EXCLUDE_SOURCES_FROM_TRANSLATION
184 // wildcard pattern. The regular expression is only anchored at the beginning to allow matching
185 // subdirectories.
186 static QRegularExpression wildcardToRegEx(const QString &wildcardPattern)
187 {
188 return QRegularExpression(
189 "\\A"_L1
190 + QRegularExpression::wildcardToRegularExpression(
191 wildcardPattern,
192 QRegularExpression::UnanchoredWildcardConversion));
193 }
194
195 QString stringValue(const QJsonObject &obj, const QString &key)
196 {
197 if (!m_errorString.isEmpty())
198 return {};
199 QJsonValue v = obj.value(key);
200 if (v.isUndefined())
201 return {};
202 if (!checkType(v, QJsonValue::String, key))
203 return {};
204 return v.toString();
205 }
206
207 QStringList stringListValue(const QJsonObject &obj, const QString &key)
208 {
209 if (!m_errorString.isEmpty())
210 return {};
211 QJsonValue v = obj.value(key);
212 if (v.isUndefined())
213 return {};
214 if (!checkType(v, QJsonValue::Array, key))
215 return {};
216 return toStringList(v, key);
217 }
218
219 QStringList toStringList(const QJsonValue &v, const QString &key)
220 {
221 QStringList result;
222 const QJsonArray a = v.toArray();
223 result.reserve(a.count());
224 for (const QJsonValue &v : a) {
225 if (!v.isString()) {
226 m_errorString = FMT::tr("Unexpected type %1 in string array in key %2.")
227 .arg(jsonTypeName(v.type()), key);
228 return {};
229 }
230 result.append(v.toString());
231 }
232 return result;
233 }
234
235 QString &m_errorString;
236};
237
238QT_BEGIN_NAMESPACE
239
240Projects projectDescriptionFromFile(const QString &filePath, QString *errorString)
241{
242 const QJsonArray rawProjects = readRawProjectDescription(filePath, errorString);
243 if (!errorString->isEmpty())
244 return {};
245 ProjectConverter converter(errorString);
246 Projects result = converter.convertProjects(rawProjects);
247 if (!errorString->isEmpty())
248 return {};
249 return result;
250}
251
252Projects projectDescriptionFromJson(const QJsonArray &rawProjects, QString *errorString)
253{
254 errorString->clear();
255 Validator validator(errorString);
256 if (!validator.isValidProjectDescription(rawProjects))
257 return {};
258 ProjectConverter converter(errorString);
259 Projects result = converter.convertProjects(rawProjects);
260 if (!errorString->isEmpty())
261 return {};
262 return result;
263}
264
265QT_END_NAMESPACE
ProjectConverter(QString *errorString)
Projects convertProjects(const QJsonArray &rawProjects)
static QJsonArray readRawProjectDescription(const QString &filePath, QString *errorString)
std::vector< Project > Projects
Projects projectDescriptionFromJson(const QJsonArray &rawProjects, QString *errorString)
Validator(QString *errorString)
bool isValidProjectDescription(const QJsonArray &projects)