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 "sourcefiletemplate.h" 0008 #include "templaterenderer.h" 0009 #include <debug.h> 0010 0011 #include <interfaces/icore.h> 0012 0013 #include <KArchive> 0014 #include <KConfig> 0015 #include <KZip> 0016 #include <KTar> 0017 #include <KConfigGroup> 0018 0019 #include <QFileInfo> 0020 #include <QDomDocument> 0021 #include <QStandardPaths> 0022 #include <QDir> 0023 0024 using namespace KDevelop; 0025 using ConfigOption = SourceFileTemplate::ConfigOption; 0026 0027 class KDevelop::SourceFileTemplatePrivate 0028 { 0029 public: 0030 KArchive* archive; 0031 QString descriptionFileName; 0032 QStringList searchLocations; 0033 0034 ConfigOption readEntry(const QDomElement& element, TemplateRenderer* renderer) const; 0035 }; 0036 0037 ConfigOption SourceFileTemplatePrivate::readEntry(const QDomElement& element, 0038 TemplateRenderer* renderer) const 0039 { 0040 ConfigOption entry; 0041 0042 entry.name = element.attribute(QStringLiteral("name")); 0043 entry.type = element.attribute(QStringLiteral("type"), QStringLiteral("String")); 0044 0045 bool isDefaultValueSet = false; 0046 for (QDomElement e = element.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { 0047 QString tag = e.tagName(); 0048 0049 if (tag == QLatin1String("label")) { 0050 entry.label = e.text(); 0051 } else if (tag == QLatin1String("tooltip")) { 0052 if (entry.label.isEmpty()) { 0053 entry.label = e.text(); 0054 } 0055 entry.context = e.text(); 0056 } else if (tag == QLatin1String("whatsthis")) { 0057 if (entry.label.isEmpty()) { 0058 entry.label = e.text(); 0059 } 0060 if (entry.context.isEmpty()) { 0061 entry.context = e.text(); 0062 } 0063 } else if (tag == QLatin1String("min")) { 0064 entry.minValue = e.text(); 0065 } else if (tag == QLatin1String("max")) { 0066 entry.maxValue = e.text(); 0067 } else if (tag == QLatin1String("default")) { 0068 entry.value = renderer->render(e.text(), entry.name); 0069 isDefaultValueSet = true; 0070 } else if (tag == QLatin1String("choices")) { 0071 QStringList values; 0072 QDomNodeList choices = element.elementsByTagName(QStringLiteral("choice")); 0073 values.reserve(choices.size()); 0074 for (int j = 0; j < choices.size(); ++j) { 0075 QDomElement choiceElement = choices.at(j).toElement(); 0076 values << choiceElement.attribute(QStringLiteral("name")); 0077 } 0078 0079 Q_ASSERT(!values.isEmpty()); 0080 if (values.isEmpty()) { 0081 qCWarning(LANGUAGE) << "Entry " << entry.name << "has an enum without any choices"; 0082 } 0083 entry.values = values; 0084 } 0085 } 0086 0087 qCDebug(LANGUAGE) << "Read entry" << entry.name << "with default value" << entry.value; 0088 0089 // preset value for enum if needed 0090 if (!entry.values.isEmpty()) { 0091 if (isDefaultValueSet) { 0092 const bool isSaneDefaultValue = entry.values.contains(entry.value.toString()); 0093 Q_ASSERT(isSaneDefaultValue); 0094 if (!isSaneDefaultValue) { 0095 qCWarning(LANGUAGE) << "Default value" << entry.value << "not in enum" << entry.values; 0096 entry.value = entry.values.at(0); 0097 } 0098 } else { 0099 entry.value = entry.values.at(0); 0100 } 0101 } 0102 return entry; 0103 } 0104 0105 SourceFileTemplate::SourceFileTemplate (const QString& templateDescription) 0106 : d_ptr(new KDevelop::SourceFileTemplatePrivate) 0107 { 0108 Q_D(SourceFileTemplate); 0109 0110 d->archive = nullptr; 0111 setTemplateDescription(templateDescription); 0112 } 0113 0114 SourceFileTemplate::SourceFileTemplate() 0115 : d_ptr(new KDevelop::SourceFileTemplatePrivate) 0116 { 0117 Q_D(SourceFileTemplate); 0118 0119 d->archive = nullptr; 0120 } 0121 0122 SourceFileTemplate::SourceFileTemplate (const SourceFileTemplate& other) 0123 : d_ptr(new KDevelop::SourceFileTemplatePrivate) 0124 { 0125 Q_D(SourceFileTemplate); 0126 0127 d->archive = nullptr; 0128 *this = other; 0129 } 0130 0131 SourceFileTemplate::~SourceFileTemplate() 0132 { 0133 Q_D(SourceFileTemplate); 0134 0135 delete d->archive; 0136 } 0137 0138 SourceFileTemplate& SourceFileTemplate::operator=(const SourceFileTemplate& other) 0139 { 0140 Q_D(SourceFileTemplate); 0141 0142 if (other.d_ptr == d_ptr) { 0143 return *this; 0144 } 0145 0146 delete d->archive; 0147 if (other.d_ptr->archive) { 0148 if (other.d_ptr->archive->fileName().endsWith(QLatin1String(".zip"))) { 0149 d->archive = new KZip(other.d_ptr->archive->fileName()); 0150 } else { 0151 d->archive = new KTar(other.d_ptr->archive->fileName()); 0152 } 0153 d->archive->open(QIODevice::ReadOnly); 0154 } else { 0155 d->archive = nullptr; 0156 } 0157 d->descriptionFileName = other.d_ptr->descriptionFileName; 0158 return *this; 0159 } 0160 0161 void SourceFileTemplate::setTemplateDescription(const QString& templateDescription) 0162 { 0163 Q_D(SourceFileTemplate); 0164 0165 delete d->archive; 0166 0167 d->descriptionFileName = templateDescription; 0168 QString archiveFileName; 0169 0170 const QString templateBaseName = QFileInfo(templateDescription).baseName(); 0171 0172 d->searchLocations.append(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, 0173 QStringLiteral("/kdevfiletemplates/templates/"), 0174 QStandardPaths::LocateDirectory)); 0175 0176 for (const QString& dir : qAsConst(d->searchLocations)) { 0177 const auto fileEntries = QDir(dir).entryInfoList(QDir::Files); 0178 for (const auto& entry : fileEntries) { 0179 if (entry.baseName() == templateBaseName) { 0180 archiveFileName = entry.absoluteFilePath(); 0181 qCDebug(LANGUAGE) << "Found template archive" << archiveFileName; 0182 break; 0183 } 0184 } 0185 } 0186 0187 if (archiveFileName.isEmpty() || !QFileInfo::exists(archiveFileName)) { 0188 qCWarning(LANGUAGE) << "Could not find a template archive for description" << templateDescription << 0189 ", archive file" << archiveFileName; 0190 d->archive = nullptr; 0191 } else { 0192 QFileInfo info(archiveFileName); 0193 0194 if (info.suffix() == QLatin1String("zip")) { 0195 d->archive = new KZip(archiveFileName); 0196 } else { 0197 d->archive = new KTar(archiveFileName); 0198 } 0199 0200 if (!d->archive->open(QIODevice::ReadOnly)) { 0201 qCWarning(LANGUAGE) << "Could not open the template archive for description" << templateDescription 0202 << ", archive file" << archiveFileName << ':' << d->archive->errorString(); 0203 delete d->archive; 0204 d->archive = nullptr; 0205 } 0206 } 0207 } 0208 0209 bool SourceFileTemplate::isValid() const 0210 { 0211 Q_D(const SourceFileTemplate); 0212 0213 return d->archive; 0214 } 0215 0216 QString SourceFileTemplate::name() const 0217 { 0218 Q_D(const SourceFileTemplate); 0219 0220 KConfig templateConfig(d->descriptionFileName); 0221 KConfigGroup cg(&templateConfig, "General"); 0222 return cg.readEntry("Name"); 0223 } 0224 0225 QString SourceFileTemplate::type() const 0226 { 0227 Q_D(const SourceFileTemplate); 0228 0229 KConfig templateConfig(d->descriptionFileName); 0230 KConfigGroup cg(&templateConfig, "General"); 0231 return cg.readEntry("Type", QString()); 0232 } 0233 0234 QString SourceFileTemplate::languageName() const 0235 { 0236 Q_D(const SourceFileTemplate); 0237 0238 KConfig templateConfig(d->descriptionFileName); 0239 KConfigGroup cg(&templateConfig, "General"); 0240 return cg.readEntry("Language", QString()); 0241 } 0242 0243 QStringList SourceFileTemplate::category() const 0244 { 0245 Q_D(const SourceFileTemplate); 0246 0247 KConfig templateConfig(d->descriptionFileName); 0248 KConfigGroup cg(&templateConfig, "General"); 0249 return cg.readEntry("Category", QStringList()); 0250 } 0251 0252 QStringList SourceFileTemplate::defaultBaseClasses() const 0253 { 0254 Q_D(const SourceFileTemplate); 0255 0256 KConfig templateConfig(d->descriptionFileName); 0257 KConfigGroup cg(&templateConfig, "General"); 0258 return cg.readEntry("BaseClasses", QStringList()); 0259 } 0260 0261 const KArchiveDirectory* SourceFileTemplate::directory() const 0262 { 0263 Q_D(const SourceFileTemplate); 0264 0265 Q_ASSERT(isValid()); 0266 return d->archive->directory(); 0267 } 0268 0269 QVector<SourceFileTemplate::OutputFile> SourceFileTemplate::outputFiles() const 0270 { 0271 Q_D(const SourceFileTemplate); 0272 0273 QVector<SourceFileTemplate::OutputFile> outputFiles; 0274 0275 KConfig templateConfig(d->descriptionFileName); 0276 KConfigGroup group(&templateConfig, "General"); 0277 0278 const QStringList files = group.readEntry("Files", QStringList()); 0279 qCDebug(LANGUAGE) << "Files in template" << files; 0280 0281 outputFiles.reserve(files.size()); 0282 for (const QString& fileGroup : files) { 0283 KConfigGroup cg(&templateConfig, fileGroup); 0284 OutputFile f; 0285 f.identifier = cg.name(); 0286 f.label = cg.readEntry("Name"); 0287 f.fileName = cg.readEntry("File"); 0288 f.outputName = cg.readEntry("OutputFile"); 0289 outputFiles << f; 0290 } 0291 0292 return outputFiles; 0293 } 0294 0295 bool SourceFileTemplate::hasCustomOptions() const 0296 { 0297 Q_D(const SourceFileTemplate); 0298 0299 Q_ASSERT(isValid()); 0300 0301 KConfig templateConfig(d->descriptionFileName); 0302 KConfigGroup cg(&templateConfig, "General"); 0303 bool hasOptions = d->archive->directory()->entries().contains(cg.readEntry("OptionsFile", "options.kcfg")); 0304 0305 qCDebug(LANGUAGE) << cg.readEntry("OptionsFile", "options.kcfg") << hasOptions; 0306 return hasOptions; 0307 } 0308 0309 QVector<SourceFileTemplate::ConfigOptionGroup> SourceFileTemplate::customOptions(TemplateRenderer* renderer) const 0310 { 0311 Q_D(const SourceFileTemplate); 0312 0313 Q_ASSERT(isValid()); 0314 0315 KConfig templateConfig(d->descriptionFileName); 0316 KConfigGroup cg(&templateConfig, "General"); 0317 const KArchiveEntry* entry = d->archive->directory()->entry(cg.readEntry("OptionsFile", "options.kcfg")); 0318 0319 QVector<ConfigOptionGroup> optionGroups; 0320 0321 if (!entry->isFile()) { 0322 return optionGroups; 0323 } 0324 const auto* file = static_cast<const KArchiveFile*>(entry); 0325 0326 /* 0327 * Copied from kconfig_compiler.kcfg 0328 */ 0329 QDomDocument doc; 0330 QString errorMsg; 0331 int errorRow; 0332 int errorCol; 0333 if (!doc.setContent(file->data(), &errorMsg, &errorRow, &errorCol)) { 0334 qCDebug(LANGUAGE) << "Unable to load document."; 0335 qCDebug(LANGUAGE) << "Parse error in line " << errorRow << ", col " << errorCol << ": " << errorMsg; 0336 return optionGroups; 0337 } 0338 0339 QDomElement cfgElement = doc.documentElement(); 0340 if (cfgElement.isNull()) { 0341 qCDebug(LANGUAGE) << "No document in kcfg file"; 0342 return optionGroups; 0343 } 0344 0345 QDomNodeList groups = cfgElement.elementsByTagName(QStringLiteral("group")); 0346 optionGroups.reserve(groups.size()); 0347 for (int i = 0; i < groups.size(); ++i) { 0348 QDomElement group = groups.at(i).toElement(); 0349 ConfigOptionGroup optionGroup; 0350 optionGroup.name = group.attribute(QStringLiteral("name")); 0351 0352 QDomNodeList entries = group.elementsByTagName(QStringLiteral("entry")); 0353 optionGroup.options.reserve(entries.size()); 0354 for (int j = 0; j < entries.size(); ++j) { 0355 QDomElement entry = entries.at(j).toElement(); 0356 optionGroup.options << d->readEntry(entry, renderer); 0357 } 0358 0359 optionGroups << optionGroup; 0360 } 0361 0362 return optionGroups; 0363 } 0364 0365 void SourceFileTemplate::addAdditionalSearchLocation(const QString& location) 0366 { 0367 Q_D(SourceFileTemplate); 0368 0369 if (!d->searchLocations.contains(location)) 0370 d->searchLocations.append(location); 0371 }