File indexing completed on 2024-05-12 04:37:48
0001 /* 0002 SPDX-FileCopyrightText: 2012 Miha Čančula <miha@noughmad.eu> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "templaterenderer.h" 0008 0009 #include "documentchangeset.h" 0010 #include "sourcefiletemplate.h" 0011 #include "templateengine.h" 0012 #include "templateengine_p.h" 0013 #include "archivetemplateloader.h" 0014 #include <debug.h> 0015 0016 #include <serialization/indexedstring.h> 0017 0018 #include <grantlee/context.h> 0019 0020 #include <QDir> 0021 #include <QFile> 0022 #include <QUrl> 0023 0024 #include <KArchive> 0025 0026 using namespace Grantlee; 0027 0028 class NoEscapeStream 0029 : public OutputStream 0030 { 0031 public: 0032 NoEscapeStream(); 0033 explicit NoEscapeStream (QTextStream* stream); 0034 0035 QString escape (const QString& input) const override; 0036 QSharedPointer<OutputStream> clone (QTextStream* stream) const override; 0037 }; 0038 0039 NoEscapeStream::NoEscapeStream() : OutputStream() 0040 { 0041 } 0042 0043 NoEscapeStream::NoEscapeStream(QTextStream* stream) : OutputStream(stream) 0044 { 0045 } 0046 0047 QString NoEscapeStream::escape(const QString& input) const 0048 { 0049 return input; 0050 } 0051 0052 QSharedPointer<OutputStream> NoEscapeStream::clone(QTextStream* stream) const 0053 { 0054 QSharedPointer<OutputStream> clonedStream = QSharedPointer<OutputStream>(new NoEscapeStream(stream)); 0055 return clonedStream; 0056 } 0057 0058 using namespace KDevelop; 0059 0060 namespace KDevelop { 0061 class TemplateRendererPrivate 0062 { 0063 public: 0064 Engine* engine; 0065 Grantlee::Context context; 0066 TemplateRenderer::EmptyLinesPolicy emptyLinesPolicy; 0067 QString errorString; 0068 }; 0069 } 0070 0071 TemplateRenderer::TemplateRenderer() 0072 : d_ptr(new TemplateRendererPrivate) 0073 { 0074 Q_D(TemplateRenderer); 0075 0076 d->engine = &TemplateEngine::self()->d_ptr->engine; 0077 d->emptyLinesPolicy = KeepEmptyLines; 0078 } 0079 0080 TemplateRenderer::~TemplateRenderer() = default; 0081 0082 void TemplateRenderer::addVariables(const QVariantHash& variables) 0083 { 0084 Q_D(TemplateRenderer); 0085 0086 QVariantHash::const_iterator it = variables.constBegin(); 0087 QVariantHash::const_iterator end = variables.constEnd(); 0088 for (; it != end; ++it) { 0089 d->context.insert(it.key(), it.value()); 0090 } 0091 } 0092 0093 void TemplateRenderer::addVariable(const QString& name, const QVariant& value) 0094 { 0095 Q_D(TemplateRenderer); 0096 0097 d->context.insert(name, value); 0098 } 0099 0100 QVariantHash TemplateRenderer::variables() const 0101 { 0102 Q_D(const TemplateRenderer); 0103 0104 return d->context.stackHash(0); 0105 } 0106 0107 QString TemplateRenderer::render(const QString& content, const QString& name) 0108 { 0109 Q_D(TemplateRenderer); 0110 0111 Template t = d->engine->newTemplate(content, name); 0112 0113 QString output; 0114 QTextStream textStream(&output); 0115 NoEscapeStream stream(&textStream); 0116 t->render(&stream, &d->context); 0117 0118 if (t->error() != Grantlee::NoError) { 0119 d->errorString = t->errorString(); 0120 } else 0121 { 0122 d->errorString.clear(); 0123 } 0124 0125 if (d->emptyLinesPolicy == TrimEmptyLines && output.contains(QLatin1Char('\n'))) { 0126 QStringList lines = output.split(QLatin1Char('\n'), Qt::KeepEmptyParts); 0127 QMutableStringListIterator it(lines); 0128 0129 // Remove empty lines from the start of the document 0130 while (it.hasNext()) { 0131 if (it.next().trimmed().isEmpty()) { 0132 it.remove(); 0133 } else 0134 { 0135 break; 0136 } 0137 } 0138 0139 // Remove single empty lines 0140 it.toFront(); 0141 bool prePreviousEmpty = false; 0142 bool previousEmpty = false; 0143 while (it.hasNext()) { 0144 bool currentEmpty = it.peekNext().trimmed().isEmpty(); 0145 if (!prePreviousEmpty && previousEmpty && !currentEmpty) { 0146 it.remove(); 0147 } 0148 prePreviousEmpty = previousEmpty; 0149 previousEmpty = currentEmpty; 0150 it.next(); 0151 } 0152 0153 // Compress multiple empty lines 0154 it.toFront(); 0155 previousEmpty = false; 0156 while (it.hasNext()) { 0157 bool currentEmpty = it.next().trimmed().isEmpty(); 0158 if (currentEmpty && previousEmpty) { 0159 it.remove(); 0160 } 0161 previousEmpty = currentEmpty; 0162 } 0163 0164 // Remove empty lines from the end 0165 it.toBack(); 0166 while (it.hasPrevious()) { 0167 if (it.previous().trimmed().isEmpty()) { 0168 it.remove(); 0169 } else 0170 { 0171 break; 0172 } 0173 } 0174 0175 // Add a newline to the end of file 0176 it.toBack(); 0177 it.insert(QString()); 0178 0179 output = lines.join(QLatin1Char('\n')); 0180 } else if (d->emptyLinesPolicy == RemoveEmptyLines) { 0181 QStringList lines = output.split(QLatin1Char('\n'), Qt::SkipEmptyParts); 0182 QMutableStringListIterator it(lines); 0183 while (it.hasNext()) { 0184 if (it.next().trimmed().isEmpty()) { 0185 it.remove(); 0186 } 0187 } 0188 it.toBack(); 0189 if (lines.size() > 1) { 0190 it.insert(QString()); 0191 } 0192 output = lines.join(QLatin1Char('\n')); 0193 } 0194 0195 return output; 0196 } 0197 0198 QString TemplateRenderer::renderFile(const QUrl& url, const QString& name) 0199 { 0200 QFile file(url.toLocalFile()); 0201 file.open(QIODevice::ReadOnly); 0202 0203 const QString content = QString::fromUtf8(file.readAll()); 0204 qCDebug(LANGUAGE) << content; 0205 0206 return render(content, name); 0207 } 0208 0209 QStringList TemplateRenderer::render(const QStringList& contents) 0210 { 0211 Q_D(TemplateRenderer); 0212 0213 qCDebug(LANGUAGE) << d->context.stackHash(0); 0214 QStringList ret; 0215 ret.reserve(contents.size()); 0216 for (const QString& content : contents) { 0217 ret << render(content); 0218 } 0219 0220 return ret; 0221 } 0222 0223 void TemplateRenderer::setEmptyLinesPolicy(TemplateRenderer::EmptyLinesPolicy policy) 0224 { 0225 Q_D(TemplateRenderer); 0226 0227 d->emptyLinesPolicy = policy; 0228 } 0229 0230 TemplateRenderer::EmptyLinesPolicy TemplateRenderer::emptyLinesPolicy() const 0231 { 0232 Q_D(const TemplateRenderer); 0233 0234 return d->emptyLinesPolicy; 0235 } 0236 0237 DocumentChangeSet TemplateRenderer::renderFileTemplate(const SourceFileTemplate& fileTemplate, 0238 const QUrl& baseUrl, 0239 const QHash<QString, QUrl>& fileUrls) 0240 { 0241 Q_ASSERT(fileTemplate.isValid()); 0242 0243 DocumentChangeSet changes; 0244 const QDir baseDir(baseUrl.path()); 0245 0246 QRegExp nonAlphaNumeric(QStringLiteral("\\W")); 0247 for (QHash<QString, QUrl>::const_iterator it = fileUrls.constBegin(); it != fileUrls.constEnd(); ++it) { 0248 QString cleanName = it.key().toLower(); 0249 cleanName.replace(nonAlphaNumeric, QStringLiteral("_")); 0250 const QString path = it.value().toLocalFile(); 0251 addVariable(QLatin1String("output_file_") + cleanName, baseDir.relativeFilePath(path)); 0252 addVariable(QLatin1String("output_file_") + cleanName + QLatin1String("_absolute"), path); 0253 } 0254 0255 const KArchiveDirectory* directory = fileTemplate.directory(); 0256 ArchiveTemplateLocation location(directory); 0257 const auto outputFiles = fileTemplate.outputFiles(); 0258 for (const SourceFileTemplate::OutputFile& outputFile : outputFiles) { 0259 const KArchiveEntry* entry = directory->entry(outputFile.fileName); 0260 if (!entry) { 0261 qCWarning(LANGUAGE) << "Entry" << outputFile.fileName << "is mentioned in group" << outputFile.identifier << 0262 "but is not present in the archive"; 0263 continue; 0264 } 0265 0266 const auto* file = dynamic_cast<const KArchiveFile*>(entry); 0267 if (!file) { 0268 qCWarning(LANGUAGE) << "Entry" << entry->name() << "is not a file"; 0269 continue; 0270 } 0271 0272 QUrl url = fileUrls[outputFile.identifier]; 0273 IndexedString document(url); 0274 KTextEditor::Range range(KTextEditor::Cursor(0, 0), 0); 0275 0276 DocumentChange change(document, range, QString(), 0277 render(QString::fromUtf8(file->data()), outputFile.identifier)); 0278 changes.addChange(change); 0279 qCDebug(LANGUAGE) << "Added change for file" << document.str(); 0280 } 0281 0282 return changes; 0283 } 0284 0285 QString TemplateRenderer::errorString() const 0286 { 0287 Q_D(const TemplateRenderer); 0288 0289 return d->errorString; 0290 }