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 }