Kate
katescriptmanager.cpp
Go to the documentation of this file.
00001 // This file is part of the KDE libraries 00002 // Copyright (C) 2005 Christoph Cullmann <cullmann@kde.org> 00003 // Copyright (C) 2005 Joseph Wenninger <jowenn@kde.org> 00004 // Copyright (C) 2006, 2009 Dominik Haumann <dhaumann kde org> 00005 // Copyright (C) 2008 Paul Giannaros <paul@giannaros.org> 00006 // Copyright (C) 2010 Joseph Wenninger <jowenn@kde.org> 00007 // 00008 // This library is free software; you can redistribute it and/or 00009 // modify it under the terms of the GNU Library General Public 00010 // License version 2 as published by the Free Software Foundation. 00011 // 00012 // This library is distributed in the hope that it will be useful, 00013 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 // Library General Public License for more details. 00016 // 00017 // You should have received a copy of the GNU Library General Public License 00018 // along with this library; see the file COPYING.LIB. If not, write to 00019 // the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00020 // Boston, MA 02110-1301, USA. 00021 00022 #include "katescriptmanager.h" 00023 00024 #include <sys/types.h> 00025 #include <sys/stat.h> 00026 #include <unistd.h> 00027 00028 #include <QFile> 00029 #include <QFileInfo> 00030 #include <QStringList> 00031 #include <QMap> 00032 #include <QUuid> 00033 00034 #include <kconfig.h> 00035 #include <kconfiggroup.h> 00036 #include <kstandarddirs.h> 00037 #include <kde_file.h> 00038 00039 #include "kateglobal.h" 00040 #include "katecmd.h" 00041 00042 KateScriptManager::KateScriptManager() : QObject(), KTextEditor::Command() 00043 { 00044 KateCmd::self()->registerCommand (this); 00045 00046 // false = force (ignore cache) 00047 collect("katepartscriptrc", "katepart/script/*.js", false); 00048 } 00049 00050 KateScriptManager::~KateScriptManager() 00051 { 00052 KateCmd::self()->unregisterCommand (this); 00053 qDeleteAll(m_indentationScripts); 00054 qDeleteAll(m_commandLineScripts); 00055 } 00056 00057 KateIndentScript *KateScriptManager::indenter(const QString &language) 00058 { 00059 KateIndentScript *highestPriorityIndenter = 0; 00060 foreach(KateIndentScript *indenter, m_languageToIndenters.value(language.toLower())) { 00061 // don't overwrite if there is already a result with a higher priority 00062 if(highestPriorityIndenter && indenter->indentHeader().priority() < highestPriorityIndenter->indentHeader().priority()) { 00063 #ifdef DEBUG_SCRIPTMANAGER 00064 kDebug(13050) << "Not overwriting indenter for" 00065 << language << "as the priority isn't big enough (" << 00066 indenter->indentHeader().priority() << '<' 00067 << highestPriorityIndenter->indentHeader().priority() << ')'; 00068 #endif 00069 } 00070 else { 00071 highestPriorityIndenter = indenter; 00072 } 00073 } 00074 00075 #ifdef DEBUG_SCRIPTMANAGER 00076 if(highestPriorityIndenter) { 00077 kDebug(13050) << "Found indenter" << highestPriorityIndenter->url() << "for" << language; 00078 } else { 00079 kDebug(13050) << "No indenter for" << language; 00080 } 00081 #endif 00082 00083 return highestPriorityIndenter; 00084 } 00085 00086 void KateScriptManager::collect(const QString& resourceFile, 00087 const QString& directory, 00088 bool force) 00089 { 00090 KConfig cfgFile(resourceFile, KConfig::NoGlobals); 00091 00092 force = false; 00093 { 00094 KConfigGroup config(&cfgFile, "General"); 00095 // If KatePart version does not match, better force a true reload 00096 if(KateGlobal::katePartVersion() != config.readEntry("kate-version", QString("0.0"))) { 00097 config.writeEntry("kate-version", KateGlobal::katePartVersion()); 00098 force = true; 00099 } 00100 } 00101 // get a list of all .js files 00102 const QStringList list = KGlobal::dirs()->findAllResources("data", directory, KStandardDirs::NoDuplicates); 00103 // clear out the old scripts and reserve enough space 00104 qDeleteAll(m_indentationScripts); 00105 qDeleteAll(m_commandLineScripts); 00106 m_indentationScripts.clear(); 00107 m_commandLineScripts.clear(); 00108 00109 m_languageToIndenters.clear(); 00110 m_indentationScriptMap.clear(); 00111 00112 // iterate through the files and read info out of cache or file 00113 foreach(const QString &fileName, list) { 00114 // get abs filename.... 00115 QFileInfo fi(fileName); 00116 const QString absPath = fi.absoluteFilePath(); 00117 const QString baseName = fi.baseName (); 00118 00119 // each file has a group 00120 const QString group = "Cache "+ fileName; 00121 KConfigGroup config(&cfgFile, group); 00122 00123 // stat the file to get the last-modified-time 00124 KDE_struct_stat sbuf; 00125 memset(&sbuf, 0, sizeof(sbuf)); 00126 KDE::stat(fileName, &sbuf); 00127 00128 // check whether file is already cached 00129 bool useCache = false; 00130 if(!force && cfgFile.hasGroup(group)) { 00131 useCache = (sbuf.st_mtime == config.readEntry("last-modified", 0)); 00132 } 00133 00134 // read key/value pairs from the cached file if possible 00135 // otherwise, parse it and then save the needed information to the cache. 00136 QHash<QString, QString> pairs; 00137 if(useCache) { 00138 const QMap<QString, QString> entries = config.entryMap(); 00139 for(QMap<QString, QString>::ConstIterator entry = entries.constBegin(); 00140 entry != entries.constEnd(); 00141 ++entry) 00142 pairs[entry.key()] = entry.value(); 00143 } 00144 else if(parseMetaInformation(fileName, pairs)) { 00145 config.writeEntry("last-modified", int(sbuf.st_mtime)); 00146 // iterate keys and save cache 00147 for(QHash<QString, QString>::ConstIterator item = pairs.constBegin(); 00148 item != pairs.constEnd(); 00149 ++item) 00150 config.writeEntry(item.key(), item.value()); 00151 } 00152 else { 00153 // parseMetaInformation will have informed the user of the problem 00154 continue; 00155 } 00156 // make sure we have the necessary meta data items we need for proper execution 00157 KateScriptHeader generalHeader; 00158 QString type = pairs.take("type"); 00159 if(type == "indentation") { 00160 generalHeader.setScriptType(Kate::IndentationScript); 00161 } else if (type == "commands") { 00162 generalHeader.setScriptType(Kate::CommandLineScript); 00163 } else { 00164 kDebug(13050) << "Script value error: No type specified in script meta data: " 00165 << qPrintable(fileName) << '\n'; 00166 continue; 00167 } 00168 00169 generalHeader.setLicense(pairs.take("license")); 00170 generalHeader.setAuthor(pairs.take("author")); 00171 generalHeader.setRevision(pairs.take("revision").toInt()); 00172 generalHeader.setKateVersion(pairs.take("kate-version")); 00173 generalHeader.setCatalog(pairs.take("i18n-catalog")); 00174 00175 // now, cast accordingly based on type 00176 switch(generalHeader.scriptType()) { 00177 case Kate::IndentationScript: { 00178 KateIndentScriptHeader indentHeader; 00179 indentHeader.setName(pairs.take("name")); 00180 indentHeader.setBaseName(baseName); 00181 if (indentHeader.name().isNull()) { 00182 kDebug( 13050 ) << "Script value error: No name specified in script meta data: " 00183 << qPrintable(fileName) << '\n' << "-> skipping indenter" << '\n'; 00184 continue; 00185 } 00186 00187 // required style? 00188 indentHeader.setRequiredStyle(pairs.take("required-syntax-style")); 00189 // which languages does this support? 00190 QString indentLanguages = pairs.take("indent-languages"); 00191 if(!indentLanguages.isNull()) { 00192 indentHeader.setIndentLanguages(indentLanguages.split(',')); 00193 } 00194 else { 00195 indentHeader.setIndentLanguages(QStringList() << indentHeader.name()); 00196 00197 #ifdef DEBUG_SCRIPTMANAGER 00198 kDebug( 13050 ) << "Script value warning: No indent-languages specified for indent " 00199 << "script " << qPrintable(fileName) << ". Using the name (" 00200 << qPrintable(indentHeader.name()) << ")\n"; 00201 #endif 00202 } 00203 // priority? 00204 bool convertedToInt; 00205 int priority = pairs.take("priority").toInt(&convertedToInt); 00206 00207 #ifdef DEBUG_SCRIPTMANAGER 00208 if(!convertedToInt) { 00209 kDebug( 13050 ) << "Script value warning: Unexpected or no priority value " 00210 << "in: " << qPrintable(fileName) << ". Setting priority to 0\n"; 00211 } 00212 #endif 00213 00214 indentHeader.setPriority(convertedToInt ? priority : 0); 00215 KateIndentScript *script = new KateIndentScript(fileName, indentHeader); 00216 script->setGeneralHeader(generalHeader); 00217 foreach(const QString &language, indentHeader.indentLanguages()) { 00218 m_languageToIndenters[language.toLower()].push_back(script); 00219 } 00220 00221 m_indentationScriptMap.insert(indentHeader.baseName(), script); 00222 m_indentationScripts.append(script); 00223 break; 00224 } 00225 case Kate::CommandLineScript: { 00226 KateCommandLineScriptHeader commandHeader; 00227 commandHeader.setFunctions(pairs.take("functions").split(QRegExp("\\s*,\\s*"), QString::SkipEmptyParts)); 00228 if (commandHeader.functions().isEmpty()) { 00229 kDebug(13050) << "Script value error: No functions specified in script meta data: " 00230 << qPrintable(fileName) << '\n' << "-> skipping script" << '\n'; 00231 continue; 00232 } 00233 KateCommandLineScript* script = new KateCommandLineScript(fileName, commandHeader); 00234 script->setGeneralHeader(generalHeader); 00235 m_commandLineScripts.push_back(script); 00236 break; 00237 } 00238 case Kate::UnknownScript: 00239 default: 00240 kDebug( 13050 ) << "Script value warning: Unknown type ('" << qPrintable(type) << "'): " 00241 << qPrintable(fileName) << '\n'; 00242 } 00243 } 00244 00245 00246 00247 #ifdef DEBUG_SCRIPTMANAGER 00248 // XX Test 00249 if(indenter("Python")) { 00250 kDebug( 13050 ) << "Python: " << indenter("Python")->global("triggerCharacters").isValid() << "\n"; 00251 kDebug( 13050 ) << "Python: " << indenter("Python")->function("triggerCharacters").isValid() << "\n"; 00252 kDebug( 13050 ) << "Python: " << indenter("Python")->global("blafldsjfklas").isValid() << "\n"; 00253 kDebug( 13050 ) << "Python: " << indenter("Python")->function("indent").isValid() << "\n"; 00254 } 00255 if(indenter("C")) 00256 kDebug( 13050 ) << "C: " << qPrintable(indenter("C")->url()) << "\n"; 00257 if(indenter("lisp")) 00258 kDebug( 13050 ) << "LISP: " << qPrintable(indenter("Lisp")->url()) << "\n"; 00259 #endif 00260 00261 cfgFile.sync(); 00262 } 00263 00264 00265 bool KateScriptManager::parseMetaInformation(const QString& url, 00266 QHash<QString, QString> &pairs) 00267 { 00268 // a valid script file -must- have the following format: 00269 // The first line must contain the string 'kate-script'. 00270 // All following lines have to have the format 'key : value'. So the value 00271 // is separated by a colon. Leading non-letter characters are ignored, that 00272 // include C and C++ comments for example. 00273 // Parsing the header stops at the first line with no ':'. 00274 00275 QFile file(url); 00276 if(!file.open(QIODevice::ReadOnly)) { 00277 kDebug( 13050 ) << "Script parse error: Cannot open file " << qPrintable(url) << '\n'; 00278 return false; 00279 } 00280 00281 kDebug(13050) << "Update script: " << url; 00282 QTextStream ts(&file); 00283 ts.setCodec("UTF-8"); 00284 if(!ts.readLine().contains("kate-script")) { 00285 kDebug( 13050 ) << "Script parse error: No header found in " << qPrintable(url) << '\n'; 00286 file.close(); 00287 return false; 00288 } 00289 00290 QString line; 00291 while(!(line = ts.readLine()).isNull()) { 00292 int colon = line.indexOf(':'); 00293 if(colon <= 0) 00294 break; // no colon -> end of header found 00295 00296 // if -1 then 0. if >= 0, move after star. 00297 int start = 0; // start points to first letter. idea: skip '*' and '//' 00298 while(start < line.length() && !line.at(start).isLetter()) 00299 ++start; 00300 00301 QString key = line.mid(start, colon - start).trimmed(); 00302 QString value = line.right(line.length() - (colon + 1)).trimmed(); 00303 pairs[key] = value; 00304 00305 #ifdef DEBUG_SCRIPTMANAGER 00306 kDebug(13050) << "KateScriptManager::parseMetaInformation: found pair: " 00307 << "(" << key << " | " << value << ")"; 00308 #endif 00309 } 00310 00311 file.close(); 00312 return true; 00313 } 00314 00315 void KateScriptManager::reload() 00316 { 00317 KateScript::reloadScriptingApi(); 00318 collect("katepartscriptrc", "katepart/script/*.js", true); 00319 emit reloaded(); 00320 } 00321 00323 00324 bool KateScriptManager::exec(KTextEditor::View *view, const QString &_cmd, QString &errorMsg) 00325 { 00326 QStringList args(_cmd.split(QRegExp("\\s+"), QString::SkipEmptyParts)); 00327 QString cmd(args.first()); 00328 args.removeFirst(); 00329 00330 if(!view) { 00331 errorMsg = i18n("Could not access view"); 00332 return false; 00333 } 00334 00335 if (cmd == "reload-scripts") { 00336 reload(); 00337 return true; 00338 } else { 00339 errorMsg = i18n("Command not found: %1", cmd); 00340 return false; 00341 } 00342 } 00343 00344 bool KateScriptManager::help(KTextEditor::View *view, const QString &cmd, QString &msg) 00345 { 00346 Q_UNUSED(view) 00347 00348 if (cmd == "reload-scripts") { 00349 msg = i18n("Reload all JavaScript files (indenters, command line scripts, etc)."); 00350 return true; 00351 } else { 00352 msg = i18n("Command not found: %1", cmd); 00353 return false; 00354 } 00355 } 00356 00357 const QStringList &KateScriptManager::cmds() 00358 { 00359 static QStringList l; 00360 00361 l.clear(); 00362 l << "reload-scripts"; 00363 00364 return l; 00365 } 00366 00367 00368 00369 KTextEditor::TemplateScript* KateScriptManager::registerTemplateScript (QObject* owner, const QString& script) 00370 { 00371 KateTemplateScript* kts = new KateTemplateScript(script); 00372 m_templateScripts.append(kts); 00373 00374 connect(owner, SIGNAL(destroyed(QObject*)), 00375 this, SLOT(slotTemplateScriptOwnerDestroyed(QObject*))); 00376 m_ownerScript.insertMulti(owner, kts); 00377 return kts; 00378 } 00379 00380 void KateScriptManager::unregisterTemplateScript(KTextEditor::TemplateScript* templateScript) 00381 { 00382 int index = m_templateScripts.indexOf(templateScript); 00383 if (index != -1) { 00384 QObject* k = m_ownerScript.key(templateScript); 00385 m_ownerScript.remove(k, templateScript); 00386 m_templateScripts.removeAt(index); 00387 delete templateScript; 00388 } 00389 } 00390 00391 void KateScriptManager::slotTemplateScriptOwnerDestroyed(QObject* owner) 00392 { 00393 while (m_ownerScript.contains(owner)) { 00394 KTextEditor::TemplateScript* templateScript = m_ownerScript.take(owner); 00395 kDebug() << "Destroying template script" << templateScript; 00396 m_templateScripts.removeAll(templateScript); 00397 delete templateScript; 00398 } 00399 } 00400 00401 KateTemplateScript* KateScriptManager::templateScript(KTextEditor::TemplateScript* templateScript) 00402 { 00403 if (m_templateScripts.contains(templateScript)) { 00404 return static_cast<KateTemplateScript*>(templateScript); 00405 } 00406 00407 return 0; 00408 } 00409 00410 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE 4.6 API Reference