22:469ce066f4a4 draft
Anton Shestakov <av6@dwimlabs.net>, Wed, 21 Nov 2018 18:13:37 +0800
plugin: generate changelog-using commands easier Also, "using capital letters is a good way of assuring that you don’t accidentally expose helper functions to users as commands". Oh yes, and so self-explanatory too!

next change 23:78c3d66226a4
previous change 21:36132d921a97

plugin.py

Permissions: -rw-r--r--

Other formats: Feeds:
###
# Copyright (c) 2007, 2009 Brendan Cully
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from supybot import utils, plugins, ircutils, callbacks
from supybot.commands import *
import os
import subprocess
import urllib
import logging
fields = {'issue':'title',
'msg':'content',
'file':'description',
}
def fetch_item(nodeid, kind='issue', tracker='http://mercurial.selenic.com/bts'):
"""Use Roundup's CSV exports to fetch arbitrary item descriptions
Default behavior is to return subject for a new issue:
>>> fetch_item(1201569)
'[issue1201569] allow running multiple instances of IDLE'
Can also fetch message contents:
>>> fetch_item(108, 'msg')
'Assigned to Guido for obvious reasons.'
Multiline messages have spurious quotes, thanks to CSV format:
>>> print fetch_item(81362, 'msg')
"Incorporated as:
2.7: r69419
3.1: r69421"
File descriptions work too:
>>> fetch_item(102, 'file')
'Example of message from frustrated user on c.l.py'
Missing descriptions show as 'None':
>>> fetch_item(12227, 'file')
'None'
"""
# This should be the smallest query able to fetch what we need
query_tpl = [('@action', 'export_csv'), ('@filter', 'id'),
('id', nodeid), ('@columns', fields[kind])]
# Request path + query
item_url = '/%s?%s' % (kind, urllib.urlencode(query_tpl))
content = urllib.urlopen(tracker + item_url).read().split('\r\n')
if content[0] == 'title':
# Got a single data row CSV, format and return it
return '[issue%s] %s' % (nodeid, content[1].strip())
elif content[0] == 'content':
return content[1].strip()
elif content[0] == 'description':
return content[1].strip()
def fetch(nodeid, debug=True):
''' Format input for fetch_item, add debug messages '''
kind = 'issue'
if nodeid.startswith('msg'):
kind = 'msg'
elif nodeid.startswith('file'):
kind = 'file'
nodeid = nodeid.replace(kind, '')
result = fetch_item(int(nodeid), kind)
if not result:
result = 'No item found for %s%s' % (kind, nodeid)
if debug:
logging.info('Fetched "%s: %s"' % (kind, result))
return result
def revparse(irc, msg, args, state):
if ':' in args[0]:
state.errorInvalid('revision', args[0],
'Contains ":".')
state.args.append(args.pop(0))
addConverter('revision', revparse)
class Hg(object):
def __init__(self, path):
self.path = path
def run(self, args):
with open(os.devnull) as null:
p = subprocess.Popen([self.path] + args, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stdin=null,
env={'HGPLAIN': '1', 'HGENCODING': 'UTF-8'})
out, err = p.communicate()
return (out.decode('utf-8'), err.decode('utf-8'))
class Mercurial(callbacks.Plugin):
"""Add the help for "@plugin help Mercurial" here
This should describe *how* to use this plugin."""
threaded = True
def __init__(self, irc):
super(Mercurial, self).__init__(irc)
self.path = self.registryValue('path')
self.hg = Hg(self.path)
def hghelp(self, irc, msg, args, cmd):
"""runs hg help with the given args"""
def fmt(text):
lines = text.splitlines()
out = []
for line in lines[:3]:
line = line.strip()
if not line or line.startswith('aliases:'):
continue
if line.startswith(('options', 'enabled extensions:')):
break
out.append(line)
if out and ' extension - ' in out[0]:
out[0] = ircutils.bold(out[0])
elif len(out) > 1:
out[1] = ircutils.bold(out[1])
out = out[:10]
return out
out, err = self.hg.run(['help', cmd])
if err:
irc.reply(err.splitlines()[0])
else:
irc.replies(fmt(out))
hghelp = wrap(hghelp, ['text'])
def _changelog(self, irc, repo, rev):
tmpl = '{rev}:{node|short}\n{date|age}\n{author|person}\n{files}\n{desc}'
out, err = self.hg.run([
'-R', repo, 'log', '-r', rev, '-l', '1', '--template', tmpl, '-v'
])
if err:
return irc.reply(err.splitlines()[0])
if not out:
return irc.reply('no changesets found')
lines = [x for x in out.strip().splitlines() if x]
node, date, user, files = lines[:4]
lines = lines[4:]
node = ircutils.bold(node)
user = ircutils.mircColor(user, fg='green')
lines = ['%s %s %s' % (node, user, date)] + lines
baseurl, err = self.hg.run(['-R', repo, 'paths', 'default'])
url = "%s/rev/%s" % (baseurl.strip("\n/"), rev)
irc.replies(lines[:2] + [url])
def _changelogcmd(repo):
def cmd(self, irc, msg, args, rev):
"""gets the changelog message for the given revision of the repository"""
self._changelog(irc, repo, rev)
return wrap(cmd, ['revision'])
main = _changelogcmd('/home/hg/repos/hg')
crew = _changelogcmd('/home/brendan/hg/mirror/mercurial/crew')
def bts(self, irc, msg, args, issue):
"""usage: bts (url|<issueno>)
url gets the url of the bts, <issueno> summarizes that bug
"""
url = 'http://mercurial.selenic.com/bts'
if issue == 'url':
irc.reply(url)
return None
title = fetch(issue)
link = None
if title.startswith('[issue'):
end = title.find(']')
link = '/'.join([url, title[1:end]])
title = ircutils.bold(title[:end+1]) + title[end+1:]
title = title.replace('\n', ' ')
irc.reply(title)
if link:
irc.reply(link)
bts = wrap(bts, ['text'])
def glossary(self, irc, msg, args, term):
"""usage: glossary <term>"""
raw, err = self.hg.run(['help', 'glossary'])
found = False
done = False
answer = []
for line in raw.splitlines():
if not found and line.lower().startswith(' ' + term.lower()):
found = True
elif found and not (line.startswith(' ') or not line):
done = True
line = line.strip()
if found and not done and line:
answer.append(line)
if answer:
irc.replies(answer[1:], joiner=' ')
else:
irc.reply('no match found')
glossary = wrap(glossary, ['text'])
Class = Mercurial