cutelyst 3.9.1
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
cuteleeview.cpp
1/*
2 * SPDX-FileCopyrightText: (C) 2020-2022 Daniel Nicoletti <dantti12@gmail.com>
3 * SPDX-License-Identifier: BSD-3-Clause
4 */
5#include "action.h"
6#include "application.h"
7#include "config.h"
8#include "context.h"
9#include "cuteleeview_p.h"
10#include "cutelystcutelee.h"
11#include "response.h"
12
13#include <cutelee/metatype.h>
14#include <cutelee/qtlocalizer.h>
15
16#include <QDirIterator>
17#include <QString>
18#include <QTranslator>
19#include <QtCore/QLoggingCategory>
20
21Q_LOGGING_CATEGORY(CUTELYST_CUTELEE, "cutelyst.cutelee", QtWarningMsg)
22
23using namespace Cutelyst;
24
25CUTELEE_BEGIN_LOOKUP(ParamsMultiMap)
26return object.value(property);
27CUTELEE_END_LOOKUP
28
29CUTELEE_BEGIN_LOOKUP_PTR(Cutelyst::Request)
30return object->property(property.toLatin1().constData());
31CUTELEE_END_LOOKUP
32
34 : View(new CuteleeViewPrivate, parent, name)
35{
36 Q_D(CuteleeView);
37
38 Cutelee::registerMetaType<ParamsMultiMap>();
39 Cutelee::registerMetaType<Cutelyst::Request *>(); // To be able to access it's properties
40
41 d->loader = std::make_shared<Cutelee::FileSystemTemplateLoader>();
42
43 d->engine = new Cutelee::Engine(this);
44 d->engine->addTemplateLoader(d->loader);
45
46 d->initEngine();
47
48 auto app = qobject_cast<Application *>(parent);
49 if (app) {
50 // make sure templates can be found on the current directory
51 setIncludePaths({app->config(QStringLiteral("root")).toString()});
52
53 // If CUTELYST_VAR is set the template might have become
54 // {{ Cutelyst.req.base }} instead of {{ c.req.base }}
55 d->cutelystVar =
56 app->config(QStringLiteral("CUTELYST_VAR"), QStringLiteral("c")).toString();
57
58 app->loadTranslations(QStringLiteral("plugin_view_cutelee"));
59 } else {
60 // make sure templates can be found on the current directory
62 }
63}
64
65QStringList CuteleeView::includePaths() const
66{
67 Q_D(const CuteleeView);
68 return d->includePaths;
69}
70
72{
73 Q_D(CuteleeView);
74 d->loader->setTemplateDirs(paths);
75 d->includePaths = paths;
76 Q_EMIT changed();
77}
78
79QString CuteleeView::templateExtension() const
80{
81 Q_D(const CuteleeView);
82 return d->extension;
83}
84
86{
87 Q_D(CuteleeView);
88 d->extension = extension;
89 Q_EMIT changed();
90}
91
92QString CuteleeView::wrapper() const
93{
94 Q_D(const CuteleeView);
95 return d->wrapper;
96}
97
99{
100 Q_D(CuteleeView);
101 d->wrapper = name;
102 Q_EMIT changed();
103}
104
105void CuteleeView::setCache(bool enable)
106{
107 Q_D(CuteleeView);
108
109 if (enable && d->cache) {
110 return; // already enabled
111 }
112
113 delete d->engine;
114 d->engine = new Cutelee::Engine(this);
115
116 if (enable) {
117 d->cache = std::make_shared<Cutelee::CachingLoaderDecorator>(d->loader);
118 d->engine->addTemplateLoader(d->cache);
119 } else {
120 d->cache = {};
121 d->engine->addTemplateLoader(d->loader);
122 }
123 d->initEngine();
124 Q_EMIT changed();
125}
126
127Cutelee::Engine *CuteleeView::engine() const
128{
129 Q_D(const CuteleeView);
130 return d->engine;
131}
132
134{
135 Q_D(CuteleeView);
136
137 if (!isCaching()) {
138 setCache(true);
139 }
140
141 const auto includePaths = d->includePaths;
142 for (const QString &includePath : includePaths) {
143 QDirIterator it(includePath,
144 {QLatin1Char('*') + d->extension},
147 while (it.hasNext()) {
148 QString path = it.next();
149 path.remove(includePath);
150 if (path.startsWith(u'/')) {
151 path.remove(0, 1);
152 }
153
154 if (d->cache->canLoadTemplate(path)) {
155 d->cache->loadByName(path, d->engine);
156 }
157 }
158 }
159}
160
162{
163 Q_D(const CuteleeView);
164 return !!d->cache;
165}
166
168{
169 Q_D(const CuteleeView);
170
171 QByteArray ret;
172 c->setStash(d->cutelystVar, QVariant::fromValue(c));
173 const QVariantHash stash = c->stash();
174 auto it = stash.constFind(QStringLiteral("template"));
175 QString templateFile;
176 if (it != stash.constEnd()) {
177 templateFile = it.value().toString();
178 } else {
179 if (c->action() && !c->action()->reverse().isEmpty()) {
180 templateFile = c->action()->reverse() + d->extension;
181 if (templateFile.startsWith(u'/')) {
182 templateFile.remove(0, 1);
183 }
184 }
185
186 if (templateFile.isEmpty()) {
187 c->error(QStringLiteral(
188 "Cannot render template, template name or template stash key not defined"));
189 return ret;
190 }
191 }
192
193 qCDebug(CUTELYST_CUTELEE) << "Rendering template" << templateFile;
194
195 Cutelee::Context gc(stash);
196
197 auto localizer = std::make_shared<Cutelee::QtLocalizer>(c->locale());
198
199 auto transIt = d->translators.constFind(c->locale());
200 if (transIt != d->translators.constEnd()) {
201 localizer.get()->installTranslator(transIt.value(), transIt.key().name());
202 }
203
204 auto catalogIt = d->translationCatalogs.constBegin();
205 while (catalogIt != d->translationCatalogs.constEnd()) {
206 localizer.get()->loadCatalog(catalogIt.value(), catalogIt.key());
207 ++it;
208 }
209
210 gc.setLocalizer(localizer);
211
212 Cutelee::Template tmpl = d->engine->loadByName(templateFile);
213 if (tmpl->error() != Cutelee::NoError) {
214 c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
215 c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
216 return ret;
217 }
218
219 QString content = tmpl->render(&gc);
220 if (tmpl->error() != Cutelee::NoError) {
221 c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
222 c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
223 return ret;
224 }
225
226 if (!d->wrapper.isEmpty()) {
227 Cutelee::Template wrapper = d->engine->loadByName(d->wrapper);
228 if (tmpl->error() != Cutelee::NoError) {
229 c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
230 c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
231 return ret;
232 }
233
234 Cutelee::SafeString safeContent(content, true);
235 gc.insert(QStringLiteral("content"), safeContent);
236 content = wrapper->render(&gc);
237
238 if (wrapper->error() != Cutelee::NoError) {
239 c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
240 c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
241 return ret;
242 }
243 }
244
245 ret = content.toUtf8();
246 return ret;
247}
248
249void CuteleeView::addTranslator(const QLocale &locale, QTranslator *translator)
250{
251 Q_D(CuteleeView);
252 Q_ASSERT_X(translator, "add translator to CuteleeView", "invalid QTranslator object");
253 d->translators.insert(locale, translator);
254}
255
256void CuteleeView::addTranslator(const QString &locale, QTranslator *translator)
257{
258 addTranslator(QLocale(locale), translator);
259}
260
261void CuteleeView::addTranslationCatalog(const QString &path, const QString &catalog)
262{
263 Q_D(CuteleeView);
264 Q_ASSERT_X(!path.isEmpty(), "add translation catalog to CuteleeView", "empty path");
265 Q_ASSERT_X(!catalog.isEmpty(), "add translation catalog to CuteleeView", "empty catalog name");
266 d->translationCatalogs.insert(catalog, path);
267}
268
270{
271 Q_D(CuteleeView);
272 Q_ASSERT_X(!catalogs.empty(), "add translation catalogs to GranteleeView", "empty QHash");
273 d->translationCatalogs.unite(catalogs);
274}
275
277 const QString &directory,
278 const QString &prefix,
279 const QString &suffix)
280{
281 QVector<QLocale> locales;
282
283 if (Q_LIKELY(!filename.isEmpty() && !directory.isEmpty())) {
284 const QDir i18nDir(directory);
285 if (Q_LIKELY(i18nDir.exists())) {
286 const QString _prefix = prefix.isEmpty() ? QStringLiteral(".") : prefix;
287 const QString _suffix = suffix.isEmpty() ? QStringLiteral(".qm") : suffix;
288 const QStringList namesFilter =
289 QStringList({filename + _prefix + QLatin1Char('*') + _suffix});
290 const QFileInfoList tsFiles = i18nDir.entryInfoList(namesFilter, QDir::Files);
291 if (Q_LIKELY(!tsFiles.empty())) {
292 locales.reserve(tsFiles.size());
293 for (const QFileInfo &ts : tsFiles) {
294 const QString fn = ts.fileName();
295 const int prefIdx = fn.indexOf(_prefix);
296 const QString locString =
297 fn.mid(prefIdx + _prefix.length(),
298 fn.length() - prefIdx - _suffix.length() - _prefix.length());
299 QLocale loc(locString);
300 if (Q_LIKELY(loc.language() != QLocale::C)) {
301 auto trans = new QTranslator(this);
302 if (Q_LIKELY(trans->load(loc, filename, _prefix, directory))) {
303 addTranslator(loc, trans);
304 locales.append(loc);
305 qCDebug(CUTELYST_CUTELEE) << "Loaded translations for locale" << loc
306 << "from" << ts.absoluteFilePath();
307 } else {
308 delete trans;
309 qCWarning(CUTELYST_CUTELEE)
310 << "Can not load translations for locale" << loc;
311 }
312 } else {
313 qCWarning(CUTELYST_CUTELEE)
314 << "Can not load translations for invalid locale string" << locString;
315 }
316 }
317 locales.squeeze();
318 } else {
319 qCWarning(CUTELYST_CUTELEE) << "Can not find translation files for" << filename
320 << "in directory" << directory;
321 }
322 } else {
323 qCWarning(CUTELYST_CUTELEE)
324 << "Can not load translations from not existing directory:" << directory;
325 }
326 } else {
327 qCWarning(CUTELYST_CUTELEE)
328 << "Can not load translations for empty file name or empty path.";
329 }
330
331 return locales;
332}
333
334void CuteleeViewPrivate::initEngine()
335{
336 // Set also the paths from CUTELYST_PLUGINS_DIR env variable as plugin paths of cutelee engine
337 const QByteArrayList dirs = QByteArrayList{QByteArrayLiteral(CUTELYST_PLUGINS_DIR)} +
338 qgetenv("CUTELYST_PLUGINS_DIR").split(';');
339 for (const QByteArray &dir : dirs) {
340 engine->addPluginPath(QString::fromLocal8Bit(dir));
341 }
342
343 engine->insertDefaultLibrary(QStringLiteral("cutelee_cutelyst"), new CutelystCutelee(engine));
344}
345
346#include "moc_cuteleeview.cpp"
QString name() const
Definition component.cpp:33
QString reverse() const
Definition component.cpp:45
The Cutelyst Context.
Definition context.h:39
void stash(const QVariantHash &unite)
Definition context.cpp:566
QLocale locale() const noexcept
Definition context.cpp:466
Response * res() const noexcept
Definition context.cpp:102
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition context.cpp:490
void setStash(const QString &key, const QVariant &value)
Definition context.cpp:217
bool error() const noexcept
Returns true if an error was set.
Definition context.cpp:49
void setWrapper(const QString &name)
Sets the template wrapper name, the template will be rendered into content variable in which the wrap...
CuteleeView(QObject *parent=nullptr, const QString &name=QString())
Constructs a CuteleeView object with the given parent and name.
void addTranslator(const QLocale &locale, QTranslator *translator)
QByteArray render(Context *c) const final
void setTemplateExtension(const QString &extension)
Sets the template extension, defaults to ".html".
void addTranslationCatalogs(const QMultiHash< QString, QString > &catalogs)
QVector< QLocale > loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
bool isCaching() const
Returns true if caching is enabled.
Cutelee::Engine * engine() const
void addTranslationCatalog(const QString &path, const QString &catalog)
void setCache(bool enable)
Sets if template caching should be done, this increases performance at the cost of higher memory usag...
void setIncludePaths(const QStringList &paths)
Sets the list of include paths which will be looked for when resolving templates files.
void setBody(QIODevice *body)
Definition response.cpp:100
Cutelyst View abstract view component
Definition view.h:22
The Cutelyst namespace holds all public Cutelyst API.
Definition Mainpage.dox:8
QString currentPath()
QFileInfoList entryInfoList(QDir::Filters filters, QDir::SortFlags sort) const const
bool exists() const const
bool empty() const const
QLocale::Language language() const const
Q_EMITQ_EMIT
QObject * parent() const const
QString fromLocal8Bit(const char *str, int size)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
int length() const const
QString mid(int position, int n) const const
QString & remove(int position, int n)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
QVariant fromValue(const T &value)
void append(const T &value)
void reserve(int size)
void squeeze()