#!/usr/bin/python # -*- encoding: utf-8 -*- """ Script to check if your scripts are up to date with list located at http://weechat.flashtux.org/plugins.php, only loaded scripts are checked. Parsing the information related to loaded scripts is full of hacks, but it works somehow. :) The script works with weechat 0.2.6 and needs curl to fetch data from the website. Curl is used as I'm a bit afraid of using threads in weechat. The script adds command /obsolete that will show you list of packages that has different version and/or are not registered at the scrtips repository. The script is in the public domain. Leonid Evdokimov (weechat at darkk dot net dot ru) http://darkk.net.ru/weechat/obsolete.py 0.1 - initial commit """ import weechat import sre import sys from subprocess import Popen from tempfile import TemporaryFile from xml.dom import Node from xml.dom.minidom import parse from itertools import ifilter NAME = 'obsolete' VERSION = '0.1' class WeePrint(object): """ thanks EgS for cool idea (copy-paste from toggle.py)""" def write(self, text): text = text.rstrip(' \0\n') # strip the null byte appended by pythons print if text: text = '[' + NAME + '] '+ text weechat.prnt(text,'') def isPluginDiv(node): return node.getAttributeNode('class') and node.getAttributeNode('class').nodeValue == u'pluginlist' def fmtAssert(bool): if not bool: raise RuntimeError, "webpage format changed" def getTextNodeChild(node): fmtAssert(len(node.childNodes) == 1) child = node.firstChild fmtAssert(child.nodeType == Node.TEXT_NODE) return child.nodeValue def xor(a, b): return (a and not b) or (not a and b) downloaders = [] def Downloader_poll(): if len(downloaders) == 0: weechat.remove_timer_handler("Downloader_poll") for d in downloaders[:]: if d.poll() != None: downloaders.remove(d) d.run() return weechat.PLUGIN_RC_OK class Downloader(Popen): def __init__(self, url, ok): self._stdout = TemporaryFile() self._stderr = TemporaryFile() self._ok = ok Popen.__init__(self, ['curl', '--silent', '--show-error', '--fail', url], stdout = self._stdout, stderr = self._stderr) add_timer = not downloaders downloaders.append(self) if add_timer: weechat.add_timer_handler(1, "Downloader_poll") def run(self): if self.poll() == 0: f = self._ok else: def fail(stdout, stderr): print "curl failed, exit code %i:" % self.poll() for line in stderr: print line f = fail self._stdout.seek(0) self._stderr.seek(0) f(self._stdout, self._stderr) class Version: def __init__(self, str): self._ver = [int(chunk) for chunk in str.split('.')] def __str__(self): return '.'.join([str(chunk) for chunk in self._ver]) def __eq__(self, that): return self._ver == that._ver def __lt__(self, that): return self._ver < that._ver class Plugin: def __init__(self, name, ver, lang, url = None): self.name = name self.url = url self.ver = Version(ver) self.lang = lang def __str__(self): return "%s/%s" % (self.lang, self.name) def parse_published(fd): plugins = {} doc = parse(fd) for div in ifilter(isPluginDiv, doc.getElementsByTagName('div')): for table in ifilter(lambda n: n.nodeName == u'table', div.childNodes): for tr in ifilter(lambda n: n.nodeName == u'tr', table.childNodes): ths = tuple(ifilter(lambda n: n.nodeName == u'th', tr.childNodes)) tds = tuple(ifilter(lambda n: n.nodeName == u'td', tr.childNodes)) fmtAssert(xor(ths, tds)) if ths: headers = [] for th in ths[0:3]: fmtAssert(len(th.childNodes) == 1) a = th.childNodes[0] fmtAssert(a.nodeName == u'a') headers.append(getTextNodeChild(a)) fmtAssert(tuple(headers) == (u'Name', u'Version', u'Language')) if tds: fmtAssert(len(tds[0].childNodes) == 1) a = tds[0].firstChild fmtAssert(a.nodeName == u'a') href = a.getAttributeNode('href') fmtAssert(href) url = getTextNodeChild(href) name = getTextNodeChild(a) ver = getTextNodeChild(tds[1]) lang = getTextNodeChild(tds[2]) plugins.setdefault(lang.lower(), {}) plugins[lang.lower()][name.lower()] = Plugin(name, ver, lang, url) return plugins def get_cmd_output(cmd, serv, channel = ''): offset = len(weechat.get_buffer_data(serv, channel)) weechat.command(cmd, channel, serv) data = weechat.get_buffer_data(serv, channel) data.reverse() data = data[offset:] return [l['data'] for l in data] def get_known_languages(serv): lines = get_cmd_output('/plugin', serv) pairs = { '-P- Perl v': 'Perl', '-P- Python v': 'Python', '-P- Lua v': 'Lua', '-P- Ruby v': 'Ruby', } retval = [] for line in lines: for key, value in pairs.iteritems(): if line.lower().find(key.lower()) == 0: retval.append(value) return retval def get_installed(serv, lang): cmd = '/%s' % lang.lower() lines = get_cmd_output(cmd, serv) parse = False retval = [] for line in lines: if parse: match = sre.match(r'-P- (.*) v([0-9\.]+) - .*', line) if match: name = match.group(1) ver = match.group(2) retval.append(Plugin(name, ver, lang)) if line == ('-P- Registered %s scripts:' % lang): parse = True if line == ('-P- %s message handlers:' % lang): break return retval def do_obsolete(serv, args): def callback(stdout, stderr): plugins = parse_published(stdout) do_obsolete_finish(serv, plugins) Downloader('http://weechat.flashtux.org/plugins.php', callback) return weechat.PLUGIN_RC_OK def do_obsolete_finish(serv, plugins): known_languages = get_known_languages(serv) installed = {} for lang in known_languages: installed[lang.lower()] = get_installed(serv, lang) for lang in known_languages: for script in installed[lang.lower()]: last = plugins[lang.lower()].get(script.name.lower()) if last: if last.ver > script.ver: print "%s is not up to date: %s installed, %s exists (%s)" % (script, script.ver, last.ver, last.url) elif last.ver < script.ver: print "%s is newer then published one: %s installed, %s published" % (script, script.ver, last.ver) else: print "%s is not published at all" % (script) if weechat.register(NAME, VERSION, "", "tracks up to date information about scripts"): sys.stdout = WeePrint() weechat.add_command_handler("obsolete", "do_obsolete", "show loaded plugins that are obsolete") # vim:set tabstop=4 softtabstop=4 shiftwidth=4: # vim:set foldmethod=marker foldlevel=32 foldmarker={{{,}}}: # vim:set expandtab: