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
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
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
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
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"))
QString wrapper() const
Returns the template wrapper.
bool isCaching() const
Returns true if caching is enabled.
Cutelee::Engine * engine() const
QString templateExtension() const
Returns the template extension.
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...
QStringList includePaths() const
Returns the list of include paths.
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
View(QObject *parent, const QString &name)
Definition view.cpp:18
The Cutelyst namespace holds all public Cutelyst API.
Definition Mainpage.dox:8
QMultiMap< QString, QString > ParamsMultiMap
QString currentPath()
QFileInfoList entryInfoList(Filters filters, SortFlags sort) const const
bool exists() const const
bool empty() const const
Language language() const const
QObject(QObject *parent)
Q_EMITQ_EMIT
QObject * parent() const const
T qobject_cast(QObject *object)
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()