/tools/mrscribe/Svnwatch/plugin.py |
---|
0,0 → 1,139 |
### |
# Copyright (c) 2007, Oleksandr Moskalenko |
# All rights reserved. |
# |
# This program is free software: you can redistribute it and/or modify |
# it under the terms of the GNU General Public License as published by |
# the Free Software Foundation, either version 3 of the License, or |
# (at your option) any later version. |
# |
# This program is distributed in the hope that it will be useful, |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
# GNU General Public License for more details. |
# |
# You should have received a copy of the GNU General Public License |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
### |
import svnxmlparser |
import threading |
import time |
from supybot.commands import * |
import supybot.conf as conf |
import supybot.utils as utils |
import supybot.plugins as plugins |
import supybot.ircutils as ircutils |
import supybot.ircmsgs as ircmsgs |
import supybot.registry as registry |
import supybot.callbacks as callbacks |
class Svnwatch(callbacks.Plugin): |
"""Main class of the plugin.""" |
threaded = True |
def __init__(self, irc): |
self.__parent = super(Svnwatch, self) |
self.__parent.__init__(irc) |
try: |
self.xmluri = self.registryValue('xmluri') |
self.wait = self.registryValue('fetchPeriod') |
self.postfrequency = self.registryValue('postFrequency') |
self.channels = self.registryValue('channels') |
self.lastrev = self.registryValue('lastRevision') |
self.lastfetch = time.time() - self.wait - 1 |
self.log.debug("%s; %s, %s, %s, %s" % (self.xmluri, self.wait, self.postfrequency, self.channels, self.lastrev)) |
except registry.NonExistentRegistryEntry: |
self.log.debug('%s is not a registered URI.', self.xmluri) |
irc.error('Can\'t get configuration variables.', Raise=True) |
self.getCommit(irc) |
def canGetNewCommit(self, irc): |
"""Checks if more then fetchPeriod time lapsed since our last fetch. |
Returns True is that's the case and False if we need to wait longer.""" |
self.now = time.time() |
self.log.debug("Current time: %s; Last fetch: %s; Difference: %d sec" % (self.now, self.lastfetch, int(self.now - self.lastfetch))) |
if self.now - self.lastfetch > self.wait: |
self.log.debug("Enough time passed, fetching new commit...") |
return True |
else: |
self.log.debug("Not enough time passed, standing by...") |
return False |
def getHead(self, xmluri): |
"""Fetches the head revision number for internal processing. Returns 'HEAD' |
in case of error or misconfiguration.""" |
self.xmluri = xmluri |
self.rev = "HEAD" |
self.rawxml = svnxmlparser.fetchXml(self.xmluri, self.rev) |
self.commitdata = svnxmlparser.getCommit(self.rawxml) |
if self.commitdata == None: |
self.log.info("Cannot fetch revision %s" % (self.rev)) |
# raise "Cannot fetch head revision id" |
return None |
else: |
return self.commitdata.keys()[0] |
def getCommit(self, irc): |
self.channels = self.registryValue('channels') |
self.log.debug("Reporting channels: %s", self.channels) |
self.lastrev = self.registryValue('lastRevision') |
# if self.canGetNewCommit(irc): |
try: |
self.log.debug('Fetching HEAD revision number from %s', self.xmluri) |
self.headid = self.getHead(self.xmluri) |
self.log.debug('Current HEAD revision number: %s', self.headid) |
if self.headid == None: |
self.log.debug('Error occured while fetching HEAD revision from %s', self.xmluri) |
self.now = time.time() |
self.lastfetch = self.now |
self.log.debug("Failed to fetch 'HEAD' at %s" % (self.now)) |
elif int(self.headid) == self.lastrev: |
self.log.debug("Fetched 'HEAD' - no new commmits at %s", self.xmluri) |
self.now = time.time() |
self.lastfetch = self.now |
self.log.debug("Fetched new 'HEAD' revision at %s" % (self.now)) |
else: |
self.oldestnewrev = self.lastrev + 1 |
self.revid = "%s:HEAD" % self.oldestnewrev |
self.revuri = self.xmluri + "?" + self.revid |
self.log.debug("Fetching new commit(s) for %s" % self.revuri) |
self.rawxml = svnxmlparser.fetchXml(self.xmluri, self.revid) |
self.log.debug("Fetched xml %s" % self.rawxml) |
self.resultcommitdata = svnxmlparser.getCommit(self.rawxml) |
self.log.debug("Fetched data %s" % self.resultcommitdata) |
self.shortdata = svnxmlparser.shortoutput(self.resultcommitdata) |
self.log.debug("Short data: %s" % self.shortdata) |
if len(self.shortdata) == 1: |
self.revisiondata = " ".join(self.shortdata) |
self.log.debug("Formatted data: %s" % self.revisiondata) |
self.lastrev = int(self.revisiondata.split(" ")[1]) |
self.log.debug("Revision ID: %s" % self.lastrev) |
for self.channel in self.channels: |
irc.replies(self.revisiondata, joiner='', to=self.channel, prefixNick=False, private=True) |
else: |
for self.revisiondata in self.shortdata: |
self.log.debug("Revision data: %s" % self.revisiondata) |
self.lastrev = int(self.revisiondata.split(" ")[1]) |
self.log.debug("Last revision ID: %s" % self.lastrev) |
for self.channel in self.channels: |
irc.replies(self.revisiondata, joiner='', to=self.channel, prefixNick=False, private=True) |
self.now = time.time() |
self.lastfetch = self.now |
conf.supybot.plugins.Svnwatch.lastRevision.setValue(self.lastrev) |
self.log.debug("Fetched new commit(s) up to revision %s at %s" |
% (self.lastrev, self.now)) |
except: |
raise |
def __call__(self, irc, msg): |
self.__parent.__call__(irc, msg) |
irc = callbacks.SimpleProxy(irc, msg) |
if self.canGetNewCommit(irc): |
self.log.debug("Invoking __call__") |
self.getCommit(irc) |
Class = Svnwatch |
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: |
/tools/mrscribe/Svnwatch/__init__.py |
---|
0,0 → 1,56 |
### |
# Supybot plugin for querying SVN commits and posting commit log |
# Copyright (c) 2007, Oleksandr Moskalenko |
# All rights reserved. |
# |
# This program is free software: you can redistribute it and/or modify |
# it under the terms of the GNU General Public License as published by |
# the Free Software Foundation, either version 3 of the License, or |
# (at your option) any later version. |
# |
# This program is distributed in the hope that it will be useful, |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
# GNU General Public License for more details. |
# |
# You should have received a copy of the GNU General Public License |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
### |
""" |
This plugin posts SVN commit summary to a channell and allows queries for |
specific revisions and partial commit data """ |
import supybot |
import supybot.world as world |
# Use this for the version of this plugin. You may wish to put a CVS keyword |
# in here if you're keeping the plugin in CVS or some similar system. |
__version__ = "0.1" |
# Replace this with an appropriate author or supybot.Author instance. |
__author__ = supybot.Author('Oleksandr Moskalenko', 'malex', 'malex@tagancha.org') |
# This is a dictionary mapping supybot.Author instances to lists of |
# contributions. |
__contributors__ = {} |
# This is a url where the most recent plugin package can be downloaded. |
__url__ = 'http://linux.tagancha.org/software/supybot/Svnwatch' |
import config |
import plugin |
import svnxmlparser |
reload(plugin) # In case we're being reloaded. |
reload(svnxmlparser) # In case we're being reloaded |
# Add more reloads here if you add third-party modules and want them to be |
# reloaded when this plugin is reloaded. Don't forget to import them as well! |
if world.testing: |
import test |
Class = plugin.Class |
configure = config.configure |
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: |
/tools/mrscribe/Svnwatch/test.py |
---|
0,0 → 1,14 |
### |
# Copyright (c) 2007, Oleksandr Moskalenko |
# All rights reserved. |
# |
# |
### |
from supybot.test import * |
class SvnwatchTestCase(PluginTestCase): |
plugins = ('Svnwatch',) |
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: |
/tools/mrscribe/Svnwatch/README.txt |
---|
0,0 → 1,4 |
This plugin will query a svn web front-end and fetch svn XML data related to |
the commit information. It will immediately post the newest commit data to the |
configured IRC channel(s). It will also create a local database of commits and |
return full or partial commit data on request to registered users. |
/tools/mrscribe/Svnwatch/svnxmlparser.py |
---|
0,0 → 1,209 |
#!/usr/bin/python |
# Copyright (c) 2007, Oleksandr Moskalenko, Craig Ringer |
# All rights reserved. |
# |
# This program is free software: you can redistribute it and/or modify |
# it under the terms of the GNU General Public License as published by |
# the Free Software Foundation, either version 3 of the License, or |
# (at your option) any later version. |
# |
# This program is distributed in the hope that it will be useful, |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
# GNU General Public License for more details. |
# |
# You should have received a copy of the GNU General Public License |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
### |
"""Subversion XML parser |
""" |
__version__ = "0.1" |
__license__ = "GPLv2" |
__copyright__ = "Copyright 2007, Oleksandr Moskalenko" |
__author__ = "Oleksandr Moskalenko <malex@tagancha.org>" |
__contributors__ = ["Craig Ringer <craig@postnewspapers.com.au>"] |
_debug = 1 |
# HTTP "User-Agent" header to send to servers when downloading Svn XML. |
USER_AGENT = "SubversionXMLParser/%s +http://linux.tagancha.org/svnxmlparser" % __version__ |
# HTTP "Accept" header to send to servers when downloading Svn XML. If you don't |
# want to send an Accept header, set this to None. |
ACCEPT_HEADER = "text/xml" |
# Default URI: |
URI = "https://www.postnewspapers.com.au/cgi-bin/xmllog" |
def fetchXml(uri, rev): |
"""Fetches a document from the given URI (with gzip encoding). Returns the |
document as a string, or an empty string in case of a transmission problem.""" |
import urllib2, gzip, StringIO |
try: |
revuri = "%s?%s" % (uri,rev) |
request = urllib2.Request(revuri) |
request.add_header('Accept-encoding', 'gzip') |
opener = urllib2.build_opener() |
handle = opener.open(request) |
compressedData = handle.read() |
# print 'Fetched ', len(compressedData), ' bytes.' |
compressedStream = StringIO.StringIO(compressedData) |
gzipper = gzip.GzipFile(fileobj=compressedStream,mode='r') |
xml = gzipper.read() |
if len(xml) <= 28: |
print "No commit for this revision\n" |
return None |
return xml |
except: |
return None |
def longest_common_prefix_for_paths(paths): |
''' |
Find the longest common prefix of `paths' and return it. |
`paths' must be a list of strings, using / as a path component |
separator. |
''' |
if len(paths) == 1: |
# use just this path |
return paths[0] |
else: |
# find common prefix |
p = [ x.split('/') for x in paths ] |
maxi = reduce( min, map( len, p ) ) |
for i in range(0, maxi): |
if not reduce( lambda a,b: a and (b[i] == paths[0][i]),\ |
paths[1:], True): |
break |
return '/'.join(p[0][0:i]) |
def getText(nodelist): |
rc = "" |
for node in nodelist: |
if node.nodeType == node.TEXT_NODE: |
rc = rc + node.data |
return rc |
def getCommit (xml): |
"""Parses Svn XML and returns a dictionary containing commit data.""" |
if xml == None: |
return xml |
from xml.dom import minidom, Node |
xmltree = minidom.parseString(xml) |
logentries = xmltree.getElementsByTagName('logentry') |
logdata = {} |
for logentry in logentries: |
if logentry.hasAttributes(): |
revision = logentry.getAttribute('revision') |
logdata[revision]= {} |
logdata[revision]['author'] = logentry.getElementsByTagName('author')[0].childNodes[0].data |
logdata[revision]['date'] = logentry.getElementsByTagName('date')[0].childNodes[0].data |
try: |
logdata[revision]['msg'] = logentry.getElementsByTagName('msg')[0].childNodes[0].data |
except IndexError: |
logdata[revision]['msg'] = "" |
paths = [] |
for path in logentry.getElementsByTagName("paths")[0].childNodes: |
if path.nodeType == Node.ELEMENT_NODE: |
paths.append(path.childNodes[0].data) |
logdata[revision]['paths'] = paths |
common_path = longest_common_prefix_for_paths(paths) |
logdata[revision]['common_path'] = common_path |
short_paths = [] |
for path in paths: |
try: |
short_paths.append(path.split(common_path)[1][1:]) |
except IndexError: |
pass |
logdata[revision]['short_paths'] = short_paths |
return logdata |
def printoutput(commitdata): |
datakeys = commitdata.keys() |
datakeys.sort() |
for i in datakeys: |
logentry = commitdata[i] |
logentry.keys().sort() |
print "Revision: " + str(i) |
for j in logentry.keys(): |
if type(logentry[j]) != list: |
print "%s: \t %s" % (j,logentry[j].strip()) |
else: |
print j+":" |
for k in logentry[j]: |
print "\t "+k |
def getbranch(logentry): |
import sys |
fullpath = logentry['common_path'] |
pathelements = fullpath.split("/") |
if pathelements[1] == 'trunk': |
return 'trunk' |
else: |
return "%s" % pathelements[2] |
def shortoutput(commitdata): |
outlist = [] |
datakeys = commitdata.keys() |
datakeys.sort() |
for i in datakeys: |
logentry = commitdata[i] |
logentry.keys().sort() |
from datetime import datetime |
from time import strptime |
indatetime = logentry["date"].split('.')[0] |
rawdatetime = datetime(*strptime(indatetime, "%Y-%m-%dT%H:%M:%S")[0:6]) |
outdate,outtime = rawdatetime.strftime("%Y-%m-%d %H:%M:%S").split(" ") |
branch = getbranch(logentry) |
outstr = "revision %s by %s in %s on %s at %s - %s" % (i, logentry["author"], branch, outdate, outtime, logentry["msg"]) |
finalstr = outstr.replace("\n", " ") |
outlist.append(finalstr) |
return outlist |
# for j in logentry.keys(): |
# #FIXME |
# if type(logentry[j]) != list: |
# print "%s: \t %s" % (j,logentry[j].strip()) |
# else: |
# print j+":" |
# for k in logentry[j]: |
# print "\t "+k |
def processUri(xmluri,rev): |
print xmluri |
rawxml = fetchXml(xmluri,rev) |
commitdata = getCommit(rawxml) |
if commitdata == None: |
return None |
else: |
outdata = shortoutput(commitdata) |
return outdata |
def getHead(xmluri): |
"""Fetches the head revision number for internal processing. Returns 'HEAD' |
in case of error or misconfiguration.""" |
rev = "HEAD" |
rawxml = fetchXml(xmluri,rev) |
commitdata = getCommit(rawxml) |
if commitdata == None: |
return None |
else: |
return commitdata.keys()[0] |
def Main (): |
import sys |
"""Called when the program is used interactively.""" |
if len(sys.argv)>1: |
args = sys.argv[1:] |
# print args |
else: |
args = ['HEAD'] |
for i in args: |
print "Fetching commit information for '%s':" % i |
xml = fetchXml(URI,i) |
commitdata = getCommit(xml) |
if commitdata == None: |
sys.exit() |
out = shortoutput(commitdata) |
for i in out: |
print i |
sys.exit() |
if __name__ == '__main__': |
Main() |
/tools/mrscribe/Svnwatch/config.py |
---|
0,0 → 1,59 |
### |
# Copyright (c) 2007, Oleksandr Moskalenko |
# All rights reserved. |
# |
# This program is free software: you can redistribute it and/or modify |
# it under the terms of the GNU General Public License as published by |
# the Free Software Foundation, either version 3 of the License, or |
# (at your option) any later version. |
# |
# This program is distributed in the hope that it will be useful, |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
# GNU General Public License for more details. |
# |
# You should have received a copy of the GNU General Public License |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
### |
import supybot.conf as conf |
import supybot.registry as registry |
import supybot.callbacks as callbacks |
def configure(advanced): |
# This will be called by supybot to configure this module. advanced is |
# a bool that specifies whether the user identified himself as an advanced |
# user or not. You should effect your configuration by manipulating the |
# registry as appropriate. |
from supybot.questions import expect, anything, something, yn |
conf.registerPlugin('Svnwatch', True) |
output('The default SVN xml URI is https://www.postnewspapers.com.au/cgi-bin/xmllog') |
if yn('Would you like to specify a SVN xml URI?'): |
xmluri = something('SVN XML URI?') |
conf.supybot.plugins.Svnwatch.set(xmluri) |
#class PostChannels(registry.SpaceSeparated): |
# List = callbacks.CanonicalNameSet |
Svnwatch = conf.registerPlugin('Svnwatch') |
# This is where your configuration variables (if any) should go. For example: |
# conf.registerGlobalValue(Svnwatch, 'someConfigVariableName', |
# registry.Boolean(False, """Help for someConfigVariableName.""")) |
conf.registerGlobalValue(Svnwatch, 'xmluri', |
registry.String('https://www.postnewspapers.com.au/cgi-bin/xmllog', |
"""The uri for the exported SVN XML source.""")) |
conf.registerGlobalValue(Svnwatch, 'fetchPeriod', |
registry.PositiveInteger(60, """Indicates how many seconds the |
bot will wait between retrieving new svn commit |
information.""")) |
conf.registerGlobalValue(Svnwatch, 'lastRevision', |
registry.PositiveInteger(1000, """Indicates the last revision published in |
the channels.""")) |
conf.registerGlobalValue(Svnwatch, 'postFrequency', registry.PositiveInteger(5, |
"""Indicates how many seconds the bot will wait between posting svn commit |
information messages.""")) |
conf.registerGlobalValue(Svnwatch, |
'channels', conf.SpaceSeparatedSetOfChannels([], """Determines |
which channels the bot will post SVN commit information to.""")) |
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: |