LCOV - code coverage report
Current view: top level - src/app - renderer.cpp (source / functions) Coverage Total Hit
Project: Smithy Qt Lines: 0.0 % 107 0
Version: 0.1.0-pre Functions: 0.0 % 13 0

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2013-2025 Paul Colby <git@colby.id.au>
       2              : // SPDX-License-Identifier: LGPL-3.0-or-later
       3              : 
       4              : #include "renderer.h"
       5              : 
       6              : #include <QDebug>
       7              : #include <QDirIterator>
       8              : #include <QJsonParseError>
       9              : #include <QRegularExpression>
      10              : 
      11              : #if defined USE_CUTELEE
      12              : #include <cutelee/cachingloaderdecorator.h>
      13              : #include <cutelee/templateloader.h>
      14              : #elif defined USE_GRANTLEE
      15              : #include <grantlee/cachingloaderdecorator.h>
      16              : #include <grantlee/templateloader.h>
      17              : #elif defined USE_KTEXTTEMPLATE
      18              : #include <KTextTemplate/CachingLoaderDecorator>
      19              : #include <KTextTemplate/TemplateLoader>
      20              : #endif
      21              : 
      22              : #define QSL(str) QStringLiteral(str) // Shorten the QStringLiteral macro for readability.
      23              : 
      24            0 : Renderer::Renderer()
      25              : {
      26            0 :     engine.setSmartTrimEnabled(true);
      27            0 : }
      28              : 
      29            0 : bool Renderer::loadTemplates(const QString &dir)
      30              : {
      31            0 :     qCInfo(lc).noquote() << tr("Loading Textlee templates from %1").arg(dir);
      32              : 
      33              :     // Setup the template loader.
      34              :     #if defined USE_CUTELEE
      35              :     auto loader = std::make_shared<Textlee::FileSystemTemplateLoader>();
      36              :     auto cachedLoader = std::make_shared<Textlee::CachingLoaderDecorator>(loader);
      37              :     #elif defined USE_GRANTLEE or defined USE_KTEXTTEMPLATE
      38            0 :     auto loader = QSharedPointer<Textlee::FileSystemTemplateLoader>::create();
      39            0 :     auto cachedLoader = QSharedPointer<Textlee::CachingLoaderDecorator>::create(loader);
      40              :     #endif
      41              :     // Note, {% include "<filename>" %} will look for files relative to templateDirs.
      42            0 :     loader->setTemplateDirs(QStringList{dir});
      43            0 :     engine.addTemplateLoader(cachedLoader);
      44              : 
      45              :     // Load the templates.
      46            0 :     QDirIterator iter{QDir::cleanPath(dir), QDir::Files, QDirIterator::Subdirectories};
      47            0 :     while (iter.hasNext()) {
      48            0 :         const QString name = iter.next().mid(iter.path().size()+1);
      49            0 :         qCDebug(lc).noquote() << tr("Loading template: %1").arg(name);
      50            0 :         const Textlee::Template tmplate = engine.loadByName(name);
      51            0 :         if (tmplate->error()) {
      52            0 :             qCritical().noquote() << tr("Error loading template %1: %2")
      53            0 :                 .arg(name, tmplate->errorString());
      54              :             return false;
      55              :         }
      56            0 :         templates.append(name);
      57            0 :     }
      58            0 :     qDebug(lc).noquote() << tr("Loaded %n template/s from %1", nullptr, templates.size()).arg(dir);
      59            0 :     return true;
      60            0 : }
      61              : 
      62            0 : QStringList Renderer::templatesNames() const
      63              : {
      64            0 :     return templates;
      65              : }
      66              : 
      67              : // Textlee output stream that does *no* content escaping.
      68            0 : class NoEscapeStream : public Textlee::OutputStream {
      69              : public:
      70            0 :     explicit NoEscapeStream(QTextStream * stream) : Textlee::OutputStream(stream) { }
      71              : 
      72              :     // cppcheck-suppress unusedFunction
      73            0 :     virtual QString escape(const QString &input) const { return input; }
      74              : 
      75              :     // cppcheck-suppress unusedFunction
      76              :     #if defined USE_CUTELEE
      77            0 :     virtual std::shared_ptr<OutputStream> clone(QTextStream *stream) const {
      78            0 :         return std::shared_ptr<OutputStream>(new NoEscapeStream(stream));
      79              :     }
      80              :     #elif defined USE_GRANTLEE
      81            0 :     virtual QSharedPointer<OutputStream> clone(QTextStream *stream) const {
      82            0 :         return QSharedPointer<OutputStream>(new NoEscapeStream(stream));
      83              :     }
      84              :     #endif
      85              : };
      86              : 
      87            0 : void Renderer::push(const QVariantHash &context)
      88              : {
      89            0 :     this->context.push();
      90            0 :     for (auto iter = context.constBegin(); iter != context.constEnd(); ++iter) {
      91            0 :         this->context.insert(sanitise(iter.key()), sanitise(iter.value()));
      92              :     }
      93            0 : }
      94              : 
      95            0 : void Renderer::pop()
      96              : {
      97            0 :     context.pop();
      98            0 : }
      99              : 
     100            0 : bool Renderer::render(const QString &templateName, const QString &outputPathName,
     101              :             const QVariantHash &additionalContext)
     102              : {
     103            0 :     qCDebug(lc).noquote() << tr("Rendering %1 to %2").arg(templateName, outputPathName);
     104            0 :     if (!templates.contains(templateName)) {
     105            0 :         qCCritical(lc).noquote() << tr("Template %1 has not been loaded").arg(templateName);
     106            0 :         return false;
     107              :     }
     108              : 
     109            0 :     const QDir dir = QFileInfo(outputPathName).dir();
     110            0 :     if (!dir.mkpath(QSL("./"))) {
     111            0 :         qCCritical(lc).noquote() << tr("Failed to create directory path %1").arg(dir.path());
     112            0 :         return false;
     113              :     }
     114              : 
     115            0 :     QFile file(outputPathName);
     116            0 :     if (!file.open(QFile::WriteOnly)) {
     117            0 :         qCCritical(lc).noquote() << tr("Failed to open %1 for writing: %2")
     118            0 :             .arg(outputPathName, file.errorString());
     119            0 :         return false;
     120              :     }
     121              : 
     122            0 :     push(additionalContext);
     123            0 :     QTextStream textStream(&file);
     124              :     NoEscapeStream noEscapeStream(&textStream);
     125            0 :     Textlee::Template tmplate = engine.loadByName(templateName);
     126            0 :     if (!tmplate) {
     127            0 :         qCCritical(lc).noquote() << tr("Failed to fetch template %1").arg(outputPathName);
     128            0 :         pop();
     129              :         return false;
     130              :     }
     131            0 :     tmplate->render(&noEscapeStream, &context);
     132            0 :     if (tmplate->error()) {
     133            0 :         qCCritical(lc).noquote() << tr("Failed to render %1: %2").arg(outputPathName, tmplate->errorString());
     134            0 :         pop();
     135              :         return false;
     136              :     }
     137            0 :     pop();
     138              :     return true;
     139            0 : }
     140              : 
     141            0 : QString Renderer::sanitise(const QString &key)
     142              : {
     143              :     QString newKey = key;
     144              :     int pos;
     145            0 :     while ((pos = newKey.indexOf(QLatin1Char('.'))) >= 0) {
     146            0 :         newKey = newKey.mid(0, pos) + newKey.mid(pos+1,1).toUpper() + newKey.mid(pos+2);
     147              :     }
     148            0 :     newKey.replace(QRegularExpression{QSL("[^a-zA-Z0-9_]")}, QSL("_"));
     149            0 :     newKey.replace(QRegularExpression{QSL("^[0-9_]")}, QLatin1String());
     150            0 :     return newKey;
     151            0 : }
     152              : 
     153            0 : QVariant Renderer::sanitise(const QVariant &variant)
     154              : {
     155              :     #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
     156              :     const int typeId = variant.typeId();
     157              :     #else
     158            0 :     const int typeId = variant.type();
     159              :     #endif
     160            0 :     if (typeId == QMetaType::QVariantHash) {
     161            0 :         return sanitise(variant.toHash());
     162            0 :     } else if (typeId == QMetaType::QVariantMap) {
     163            0 :         return sanitise(variant.toMap());
     164              :     }
     165            0 :     return variant;
     166              : }
     167              : 
     168            0 : QVariantHash Renderer::sanitise(const QVariantHash &hash)
     169              : {
     170            0 :     QVariantHash newHash;
     171            0 :     for (auto iter = hash.begin(); iter != hash.end(); ++iter) {
     172            0 :         const QString saneKey = sanitise(iter.key());
     173            0 :         QVariant saneValue = iter.value();
     174              :         #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
     175              :         const int typeId = iter->typeId();
     176              :         #else
     177            0 :         const int typeId = iter->type();
     178              :         #endif
     179            0 :         if (typeId == QMetaType::QVariantHash) {
     180            0 :             saneValue = sanitise(iter->toHash());
     181            0 :         } else if (typeId == QMetaType::QVariantMap) {
     182            0 :             saneValue = sanitise(iter->toMap());
     183              :         }
     184            0 :         newHash.insert(saneKey, saneValue);
     185            0 :     }
     186              :     Q_ASSERT(hash.size() == newHash.size());
     187            0 :     return newHash;
     188            0 : }
     189              : 
     190            0 : QVariantMap Renderer::sanitise(const QVariantMap &map)
     191              : {
     192            0 :     QVariantMap newMap;
     193            0 :     for (auto iter = map.begin(); iter != map.end(); ++iter) {
     194            0 :         const QString saneKey = sanitise(iter.key());
     195            0 :         QVariant saneValue = iter.value();
     196              :         #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
     197              :         const int typeId = iter->typeId();
     198              :         #else
     199            0 :         const int typeId = iter->type();
     200              :         #endif
     201            0 :         if (typeId == QMetaType::QVariantHash) {
     202            0 :             saneValue = sanitise(iter->toHash());
     203            0 :         } else if (typeId == QMetaType::QVariantMap) {
     204            0 :             saneValue = sanitise(iter->toMap());
     205              :         }
     206            0 :         newMap.insert(saneKey, saneValue);
     207            0 :     }
     208              :     Q_ASSERT(map.size() == newMap.size());
     209            0 :     return newMap;
     210            0 : }
        

Generated by: LCOV version 2.2-1